Authentification stable - WIP

This commit is contained in:
DahoudG
2025-09-19 12:35:46 +00:00
parent 63fe107f98
commit 098894bdc1
383 changed files with 13072 additions and 93334 deletions

View File

@@ -1,418 +0,0 @@
import 'package:json_annotation/json_annotation.dart';
import '../../domain/entities/notification.dart';
part 'notification_model.g.dart';
/// Modèle de données pour les actions de notification
@JsonSerializable()
class ActionNotificationModel extends ActionNotification {
const ActionNotificationModel({
required super.id,
required super.libelle,
required super.typeAction,
super.description,
super.icone,
super.couleur,
super.url,
super.route,
super.parametres,
super.fermeNotification = true,
super.necessiteConfirmation = false,
super.estDestructive = false,
super.ordre = 0,
super.estActivee = true,
});
factory ActionNotificationModel.fromJson(Map<String, dynamic> json) =>
_$ActionNotificationModelFromJson(json);
@override
Map<String, dynamic> toJson() => _$ActionNotificationModelToJson(this);
factory ActionNotificationModel.fromEntity(ActionNotification entity) {
return ActionNotificationModel(
id: entity.id,
libelle: entity.libelle,
typeAction: entity.typeAction,
description: entity.description,
icone: entity.icone,
couleur: entity.couleur,
url: entity.url,
route: entity.route,
parametres: entity.parametres,
fermeNotification: entity.fermeNotification,
necessiteConfirmation: entity.necessiteConfirmation,
estDestructive: entity.estDestructive,
ordre: entity.ordre,
estActivee: entity.estActivee,
);
}
ActionNotification toEntity() {
return ActionNotification(
id: id,
libelle: libelle,
typeAction: typeAction,
description: description,
icone: icone,
couleur: couleur,
url: url,
route: route,
parametres: parametres,
fermeNotification: fermeNotification,
necessiteConfirmation: necessiteConfirmation,
estDestructive: estDestructive,
ordre: ordre,
estActivee: estActivee,
);
}
}
/// Modèle de données pour les notifications
@JsonSerializable()
class NotificationModel extends NotificationEntity {
const NotificationModel({
required super.id,
required super.typeNotification,
required super.statut,
required super.titre,
required super.message,
super.messageCourt,
super.expediteurId,
super.expediteurNom,
required super.destinatairesIds,
super.organisationId,
super.donneesPersonnalisees,
super.imageUrl,
super.iconeUrl,
super.actionClic,
super.parametresAction,
super.actionsRapides,
required super.dateCreation,
super.dateEnvoiProgramme,
super.dateEnvoi,
super.dateExpiration,
super.dateDerniereLecture,
super.priorite = 3,
super.estLue = false,
super.estImportante = false,
super.estArchivee = false,
super.nombreAffichages = 0,
super.nombreClics = 0,
super.tags,
super.campagneId,
super.plateforme,
super.tokenFCM,
});
factory NotificationModel.fromJson(Map<String, dynamic> json) =>
_$NotificationModelFromJson(json);
@override
Map<String, dynamic> toJson() => _$NotificationModelToJson(this);
factory NotificationModel.fromEntity(NotificationEntity entity) {
return NotificationModel(
id: entity.id,
typeNotification: entity.typeNotification,
statut: entity.statut,
titre: entity.titre,
message: entity.message,
messageCourt: entity.messageCourt,
expediteurId: entity.expediteurId,
expediteurNom: entity.expediteurNom,
destinatairesIds: entity.destinatairesIds,
organisationId: entity.organisationId,
donneesPersonnalisees: entity.donneesPersonnalisees,
imageUrl: entity.imageUrl,
iconeUrl: entity.iconeUrl,
actionClic: entity.actionClic,
parametresAction: entity.parametresAction,
actionsRapides: entity.actionsRapides?.map((action) =>
ActionNotificationModel.fromEntity(action)).toList(),
dateCreation: entity.dateCreation,
dateEnvoiProgramme: entity.dateEnvoiProgramme,
dateEnvoi: entity.dateEnvoi,
dateExpiration: entity.dateExpiration,
dateDerniereLecture: entity.dateDerniereLecture,
priorite: entity.priorite,
estLue: entity.estLue,
estImportante: entity.estImportante,
estArchivee: entity.estArchivee,
nombreAffichages: entity.nombreAffichages,
nombreClics: entity.nombreClics,
tags: entity.tags,
campagneId: entity.campagneId,
plateforme: entity.plateforme,
tokenFCM: entity.tokenFCM,
);
}
NotificationEntity toEntity() {
return NotificationEntity(
id: id,
typeNotification: typeNotification,
statut: statut,
titre: titre,
message: message,
messageCourt: messageCourt,
expediteurId: expediteurId,
expediteurNom: expediteurNom,
destinatairesIds: destinatairesIds,
organisationId: organisationId,
donneesPersonnalisees: donneesPersonnalisees,
imageUrl: imageUrl,
iconeUrl: iconeUrl,
actionClic: actionClic,
parametresAction: parametresAction,
actionsRapides: actionsRapides?.map((action) =>
(action as ActionNotificationModel).toEntity()).toList(),
dateCreation: dateCreation,
dateEnvoiProgramme: dateEnvoiProgramme,
dateEnvoi: dateEnvoi,
dateExpiration: dateExpiration,
dateDerniereLecture: dateDerniereLecture,
priorite: priorite,
estLue: estLue,
estImportante: estImportante,
estArchivee: estArchivee,
nombreAffichages: nombreAffichages,
nombreClics: nombreClics,
tags: tags,
campagneId: campagneId,
plateforme: plateforme,
tokenFCM: tokenFCM,
);
}
/// Crée un modèle depuis une notification Firebase
factory NotificationModel.fromFirebaseMessage(Map<String, dynamic> data) {
// Extraction des données de base
final id = data['id'] ?? data['notification_id'] ?? '';
final titre = data['title'] ?? data['titre'] ?? '';
final message = data['body'] ?? data['message'] ?? '';
final messageCourt = data['short_message'] ?? data['message_court'];
// Parsing du type de notification
TypeNotification typeNotification = TypeNotification.annonceGenerale;
if (data['type'] != null) {
try {
typeNotification = TypeNotification.values.firstWhere(
(type) => type.name == data['type'] || type.toString().split('.').last == data['type'],
orElse: () => TypeNotification.annonceGenerale,
);
} catch (e) {
// Utilise le type par défaut en cas d'erreur
}
}
// Parsing du statut
StatutNotification statut = StatutNotification.recue;
if (data['status'] != null) {
try {
statut = StatutNotification.values.firstWhere(
(s) => s.name == data['status'] || s.toString().split('.').last == data['status'],
orElse: () => StatutNotification.recue,
);
} catch (e) {
// Utilise le statut par défaut
}
}
// Parsing des actions rapides
List<ActionNotification>? actionsRapides;
if (data['actions'] != null && data['actions'] is List) {
try {
actionsRapides = (data['actions'] as List)
.map((actionData) => ActionNotificationModel.fromJson(
actionData is Map<String, dynamic> ? actionData : {}))
.toList();
} catch (e) {
// Ignore les erreurs de parsing des actions
}
}
// Parsing des destinataires
List<String> destinatairesIds = [];
if (data['recipients'] != null) {
if (data['recipients'] is List) {
destinatairesIds = List<String>.from(data['recipients']);
} else if (data['recipients'] is String) {
destinatairesIds = [data['recipients']];
}
}
// Parsing des tags
List<String>? tags;
if (data['tags'] != null && data['tags'] is List) {
tags = List<String>.from(data['tags']);
}
// Parsing des dates
DateTime dateCreation = DateTime.now();
if (data['created_at'] != null) {
try {
if (data['created_at'] is int) {
dateCreation = DateTime.fromMillisecondsSinceEpoch(data['created_at']);
} else if (data['created_at'] is String) {
dateCreation = DateTime.parse(data['created_at']);
}
} catch (e) {
// Utilise la date actuelle en cas d'erreur
}
}
DateTime? dateExpiration;
if (data['expires_at'] != null) {
try {
if (data['expires_at'] is int) {
dateExpiration = DateTime.fromMillisecondsSinceEpoch(data['expires_at']);
} else if (data['expires_at'] is String) {
dateExpiration = DateTime.parse(data['expires_at']);
}
} catch (e) {
// Ignore les erreurs de parsing de date
}
}
// Parsing des données personnalisées
Map<String, dynamic>? donneesPersonnalisees;
if (data['custom_data'] != null && data['custom_data'] is Map) {
donneesPersonnalisees = Map<String, dynamic>.from(data['custom_data']);
}
return NotificationModel(
id: id,
typeNotification: typeNotification,
statut: statut,
titre: titre,
message: message,
messageCourt: messageCourt,
expediteurId: data['sender_id'],
expediteurNom: data['sender_name'],
destinatairesIds: destinatairesIds,
organisationId: data['organization_id'],
donneesPersonnalisees: donneesPersonnalisees,
imageUrl: data['image_url'],
iconeUrl: data['icon_url'],
actionClic: data['click_action'],
parametresAction: data['action_params'] != null
? Map<String, String>.from(data['action_params'])
: null,
actionsRapides: actionsRapides,
dateCreation: dateCreation,
dateExpiration: dateExpiration,
priorite: data['priority'] ?? 3,
tags: tags,
campagneId: data['campaign_id'],
plateforme: data['platform'],
tokenFCM: data['fcm_token'],
);
}
/// Convertit vers le format Firebase
Map<String, dynamic> toFirebaseData() {
final data = <String, dynamic>{
'id': id,
'type': typeNotification.name,
'status': statut.name,
'title': titre,
'body': message,
'recipients': destinatairesIds,
'created_at': dateCreation.millisecondsSinceEpoch,
'priority': priorite,
};
if (messageCourt != null) data['short_message'] = messageCourt;
if (expediteurId != null) data['sender_id'] = expediteurId;
if (expediteurNom != null) data['sender_name'] = expediteurNom;
if (organisationId != null) data['organization_id'] = organisationId;
if (donneesPersonnalisees != null) data['custom_data'] = donneesPersonnalisees;
if (imageUrl != null) data['image_url'] = imageUrl;
if (iconeUrl != null) data['icon_url'] = iconeUrl;
if (actionClic != null) data['click_action'] = actionClic;
if (parametresAction != null) data['action_params'] = parametresAction;
if (dateExpiration != null) data['expires_at'] = dateExpiration!.millisecondsSinceEpoch;
if (tags != null) data['tags'] = tags;
if (campagneId != null) data['campaign_id'] = campagneId;
if (plateforme != null) data['platform'] = plateforme;
if (tokenFCM != null) data['fcm_token'] = tokenFCM;
if (actionsRapides != null && actionsRapides!.isNotEmpty) {
data['actions'] = actionsRapides!
.map((action) => (action as ActionNotificationModel).toJson())
.toList();
}
return data;
}
/// Crée une copie avec des modifications
NotificationModel copyWithModel({
String? id,
TypeNotification? typeNotification,
StatutNotification? statut,
String? titre,
String? message,
String? messageCourt,
String? expediteurId,
String? expediteurNom,
List<String>? destinatairesIds,
String? organisationId,
Map<String, dynamic>? donneesPersonnalisees,
String? imageUrl,
String? iconeUrl,
String? actionClic,
Map<String, String>? parametresAction,
List<ActionNotification>? actionsRapides,
DateTime? dateCreation,
DateTime? dateEnvoiProgramme,
DateTime? dateEnvoi,
DateTime? dateExpiration,
DateTime? dateDerniereLecture,
int? priorite,
bool? estLue,
bool? estImportante,
bool? estArchivee,
int? nombreAffichages,
int? nombreClics,
List<String>? tags,
String? campagneId,
String? plateforme,
String? tokenFCM,
}) {
return NotificationModel(
id: id ?? this.id,
typeNotification: typeNotification ?? this.typeNotification,
statut: statut ?? this.statut,
titre: titre ?? this.titre,
message: message ?? this.message,
messageCourt: messageCourt ?? this.messageCourt,
expediteurId: expediteurId ?? this.expediteurId,
expediteurNom: expediteurNom ?? this.expediteurNom,
destinatairesIds: destinatairesIds ?? this.destinatairesIds,
organisationId: organisationId ?? this.organisationId,
donneesPersonnalisees: donneesPersonnalisees ?? this.donneesPersonnalisees,
imageUrl: imageUrl ?? this.imageUrl,
iconeUrl: iconeUrl ?? this.iconeUrl,
actionClic: actionClic ?? this.actionClic,
parametresAction: parametresAction ?? this.parametresAction,
actionsRapides: actionsRapides ?? this.actionsRapides,
dateCreation: dateCreation ?? this.dateCreation,
dateEnvoiProgramme: dateEnvoiProgramme ?? this.dateEnvoiProgramme,
dateEnvoi: dateEnvoi ?? this.dateEnvoi,
dateExpiration: dateExpiration ?? this.dateExpiration,
dateDerniereLecture: dateDerniereLecture ?? this.dateDerniereLecture,
priorite: priorite ?? this.priorite,
estLue: estLue ?? this.estLue,
estImportante: estImportante ?? this.estImportante,
estArchivee: estArchivee ?? this.estArchivee,
nombreAffichages: nombreAffichages ?? this.nombreAffichages,
nombreClics: nombreClics ?? this.nombreClics,
tags: tags ?? this.tags,
campagneId: campagneId ?? this.campagneId,
plateforme: plateforme ?? this.plateforme,
tokenFCM: tokenFCM ?? this.tokenFCM,
);
}
}

View File

@@ -1,414 +0,0 @@
import 'package:equatable/equatable.dart';
import 'package:json_annotation/json_annotation.dart';
part 'notification.g.dart';
/// Énumération des types de notification
enum TypeNotification {
// Événements
@JsonValue('NOUVEL_EVENEMENT')
nouvelEvenement('Nouvel événement', 'evenements', 'info', 'event', '#FF9800'),
@JsonValue('RAPPEL_EVENEMENT')
rappelEvenement('Rappel d\'événement', 'evenements', 'reminder', 'schedule', '#2196F3'),
@JsonValue('EVENEMENT_ANNULE')
evenementAnnule('Événement annulé', 'evenements', 'warning', 'event_busy', '#F44336'),
@JsonValue('INSCRIPTION_CONFIRMEE')
inscriptionConfirmee('Inscription confirmée', 'evenements', 'success', 'check_circle', '#4CAF50'),
// Cotisations
@JsonValue('COTISATION_DUE')
cotisationDue('Cotisation due', 'cotisations', 'reminder', 'payment', '#FF5722'),
@JsonValue('COTISATION_PAYEE')
cotisationPayee('Cotisation payée', 'cotisations', 'success', 'paid', '#4CAF50'),
@JsonValue('PAIEMENT_CONFIRME')
paiementConfirme('Paiement confirmé', 'cotisations', 'success', 'check_circle', '#4CAF50'),
@JsonValue('PAIEMENT_ECHOUE')
paiementEchoue('Paiement échoué', 'cotisations', 'error', 'error', '#F44336'),
// Solidarité
@JsonValue('NOUVELLE_DEMANDE_AIDE')
nouvelleDemandeAide('Nouvelle demande d\'aide', 'solidarite', 'info', 'help', '#E91E63'),
@JsonValue('DEMANDE_AIDE_APPROUVEE')
demandeAideApprouvee('Demande d\'aide approuvée', 'solidarite', 'success', 'thumb_up', '#4CAF50'),
@JsonValue('AIDE_DISPONIBLE')
aideDisponible('Aide disponible', 'solidarite', 'info', 'volunteer_activism', '#E91E63'),
// Membres
@JsonValue('NOUVEAU_MEMBRE')
nouveauMembre('Nouveau membre', 'membres', 'info', 'person_add', '#2196F3'),
@JsonValue('ANNIVERSAIRE_MEMBRE')
anniversaireMembre('Anniversaire de membre', 'membres', 'celebration', 'cake', '#FF9800'),
// Organisation
@JsonValue('ANNONCE_GENERALE')
annonceGenerale('Annonce générale', 'organisation', 'info', 'campaign', '#2196F3'),
@JsonValue('REUNION_PROGRAMMEE')
reunionProgrammee('Réunion programmée', 'organisation', 'info', 'groups', '#2196F3'),
// Messages
@JsonValue('MESSAGE_PRIVE')
messagePrive('Message privé', 'messages', 'info', 'mail', '#2196F3'),
@JsonValue('MENTION')
mention('Mention', 'messages', 'info', 'alternate_email', '#FF9800'),
// Système
@JsonValue('MISE_A_JOUR_APP')
miseAJourApp('Mise à jour disponible', 'systeme', 'info', 'system_update', '#2196F3'),
@JsonValue('MAINTENANCE_PROGRAMMEE')
maintenanceProgrammee('Maintenance programmée', 'systeme', 'warning', 'build', '#FF9800');
const TypeNotification(this.libelle, this.categorie, this.priorite, this.icone, this.couleur);
final String libelle;
final String categorie;
final String priorite;
final String icone;
final String couleur;
bool get isCritique => priorite == 'urgent' || priorite == 'error';
bool get isRappel => priorite == 'reminder';
bool get isPositive => priorite == 'success' || priorite == 'celebration';
int get niveauPriorite {
switch (priorite) {
case 'urgent': return 1;
case 'error': return 2;
case 'warning': return 3;
case 'important': return 4;
case 'reminder': return 5;
case 'info': return 6;
case 'success': return 7;
case 'celebration': return 8;
default: return 6;
}
}
}
/// Énumération des statuts de notification
enum StatutNotification {
@JsonValue('BROUILLON')
brouillon('Brouillon', 'draft', '#9E9E9E'),
@JsonValue('PROGRAMMEE')
programmee('Programmée', 'scheduled', '#FF9800'),
@JsonValue('ENVOYEE')
envoyee('Envoyée', 'sent', '#4CAF50'),
@JsonValue('RECUE')
recue('Reçue', 'received', '#4CAF50'),
@JsonValue('AFFICHEE')
affichee('Affichée', 'displayed', '#2196F3'),
@JsonValue('OUVERTE')
ouverte('Ouverte', 'opened', '#4CAF50'),
@JsonValue('LUE')
lue('Lue', 'read', '#4CAF50'),
@JsonValue('NON_LUE')
nonLue('Non lue', 'unread', '#FF9800'),
@JsonValue('MARQUEE_IMPORTANTE')
marqueeImportante('Marquée importante', 'starred', '#FF9800'),
@JsonValue('SUPPRIMEE')
supprimee('Supprimée', 'deleted', '#F44336'),
@JsonValue('ARCHIVEE')
archivee('Archivée', 'archived', '#9E9E9E'),
@JsonValue('ECHEC_ENVOI')
echecEnvoi('Échec d\'envoi', 'failed', '#F44336');
const StatutNotification(this.libelle, this.code, this.couleur);
final String libelle;
final String code;
final String couleur;
bool get isSucces => this == envoyee || this == recue || this == affichee || this == ouverte || this == lue;
bool get isErreur => this == echecEnvoi;
bool get isFinal => this == supprimee || this == archivee || isErreur;
}
/// Action rapide de notification
@JsonSerializable()
class ActionNotification extends Equatable {
const ActionNotification({
required this.id,
required this.libelle,
required this.typeAction,
this.description,
this.icone,
this.couleur,
this.url,
this.route,
this.parametres,
this.fermeNotification = true,
this.necessiteConfirmation = false,
this.estDestructive = false,
this.ordre = 0,
this.estActivee = true,
});
final String id;
final String libelle;
final String? description;
final String typeAction;
final String? icone;
final String? couleur;
final String? url;
final String? route;
final Map<String, String>? parametres;
final bool fermeNotification;
final bool necessiteConfirmation;
final bool estDestructive;
final int ordre;
final bool estActivee;
factory ActionNotification.fromJson(Map<String, dynamic> json) =>
_$ActionNotificationFromJson(json);
Map<String, dynamic> toJson() => _$ActionNotificationToJson(this);
@override
List<Object?> get props => [
id, libelle, description, typeAction, icone, couleur,
url, route, parametres, fermeNotification, necessiteConfirmation,
estDestructive, ordre, estActivee,
];
ActionNotification copyWith({
String? id,
String? libelle,
String? description,
String? typeAction,
String? icone,
String? couleur,
String? url,
String? route,
Map<String, String>? parametres,
bool? fermeNotification,
bool? necessiteConfirmation,
bool? estDestructive,
int? ordre,
bool? estActivee,
}) {
return ActionNotification(
id: id ?? this.id,
libelle: libelle ?? this.libelle,
description: description ?? this.description,
typeAction: typeAction ?? this.typeAction,
icone: icone ?? this.icone,
couleur: couleur ?? this.couleur,
url: url ?? this.url,
route: route ?? this.route,
parametres: parametres ?? this.parametres,
fermeNotification: fermeNotification ?? this.fermeNotification,
necessiteConfirmation: necessiteConfirmation ?? this.necessiteConfirmation,
estDestructive: estDestructive ?? this.estDestructive,
ordre: ordre ?? this.ordre,
estActivee: estActivee ?? this.estActivee,
);
}
}
/// Entité principale de notification
@JsonSerializable()
class NotificationEntity extends Equatable {
const NotificationEntity({
required this.id,
required this.typeNotification,
required this.statut,
required this.titre,
required this.message,
this.messageCourt,
this.expediteurId,
this.expediteurNom,
required this.destinatairesIds,
this.organisationId,
this.donneesPersonnalisees,
this.imageUrl,
this.iconeUrl,
this.actionClic,
this.parametresAction,
this.actionsRapides,
required this.dateCreation,
this.dateEnvoiProgramme,
this.dateEnvoi,
this.dateExpiration,
this.dateDerniereLecture,
this.priorite = 3,
this.estLue = false,
this.estImportante = false,
this.estArchivee = false,
this.nombreAffichages = 0,
this.nombreClics = 0,
this.tags,
this.campagneId,
this.plateforme,
this.tokenFCM,
});
final String id;
final TypeNotification typeNotification;
final StatutNotification statut;
final String titre;
final String message;
final String? messageCourt;
final String? expediteurId;
final String? expediteurNom;
final List<String> destinatairesIds;
final String? organisationId;
final Map<String, dynamic>? donneesPersonnalisees;
final String? imageUrl;
final String? iconeUrl;
final String? actionClic;
final Map<String, String>? parametresAction;
final List<ActionNotification>? actionsRapides;
final DateTime dateCreation;
final DateTime? dateEnvoiProgramme;
final DateTime? dateEnvoi;
final DateTime? dateExpiration;
final DateTime? dateDerniereLecture;
final int priorite;
final bool estLue;
final bool estImportante;
final bool estArchivee;
final int nombreAffichages;
final int nombreClics;
final List<String>? tags;
final String? campagneId;
final String? plateforme;
final String? tokenFCM;
factory NotificationEntity.fromJson(Map<String, dynamic> json) =>
_$NotificationEntityFromJson(json);
Map<String, dynamic> toJson() => _$NotificationEntityToJson(this);
@override
List<Object?> get props => [
id, typeNotification, statut, titre, message, messageCourt,
expediteurId, expediteurNom, destinatairesIds, organisationId,
donneesPersonnalisees, imageUrl, iconeUrl, actionClic, parametresAction,
actionsRapides, dateCreation, dateEnvoiProgramme, dateEnvoi,
dateExpiration, dateDerniereLecture, priorite, estLue, estImportante,
estArchivee, nombreAffichages, nombreClics, tags, campagneId,
plateforme, tokenFCM,
];
NotificationEntity copyWith({
String? id,
TypeNotification? typeNotification,
StatutNotification? statut,
String? titre,
String? message,
String? messageCourt,
String? expediteurId,
String? expediteurNom,
List<String>? destinatairesIds,
String? organisationId,
Map<String, dynamic>? donneesPersonnalisees,
String? imageUrl,
String? iconeUrl,
String? actionClic,
Map<String, String>? parametresAction,
List<ActionNotification>? actionsRapides,
DateTime? dateCreation,
DateTime? dateEnvoiProgramme,
DateTime? dateEnvoi,
DateTime? dateExpiration,
DateTime? dateDerniereLecture,
int? priorite,
bool? estLue,
bool? estImportante,
bool? estArchivee,
int? nombreAffichages,
int? nombreClics,
List<String>? tags,
String? campagneId,
String? plateforme,
String? tokenFCM,
}) {
return NotificationEntity(
id: id ?? this.id,
typeNotification: typeNotification ?? this.typeNotification,
statut: statut ?? this.statut,
titre: titre ?? this.titre,
message: message ?? this.message,
messageCourt: messageCourt ?? this.messageCourt,
expediteurId: expediteurId ?? this.expediteurId,
expediteurNom: expediteurNom ?? this.expediteurNom,
destinatairesIds: destinatairesIds ?? this.destinatairesIds,
organisationId: organisationId ?? this.organisationId,
donneesPersonnalisees: donneesPersonnalisees ?? this.donneesPersonnalisees,
imageUrl: imageUrl ?? this.imageUrl,
iconeUrl: iconeUrl ?? this.iconeUrl,
actionClic: actionClic ?? this.actionClic,
parametresAction: parametresAction ?? this.parametresAction,
actionsRapides: actionsRapides ?? this.actionsRapides,
dateCreation: dateCreation ?? this.dateCreation,
dateEnvoiProgramme: dateEnvoiProgramme ?? this.dateEnvoiProgramme,
dateEnvoi: dateEnvoi ?? this.dateEnvoi,
dateExpiration: dateExpiration ?? this.dateExpiration,
dateDerniereLecture: dateDerniereLecture ?? this.dateDerniereLecture,
priorite: priorite ?? this.priorite,
estLue: estLue ?? this.estLue,
estImportante: estImportante ?? this.estImportante,
estArchivee: estArchivee ?? this.estArchivee,
nombreAffichages: nombreAffichages ?? this.nombreAffichages,
nombreClics: nombreClics ?? this.nombreClics,
tags: tags ?? this.tags,
campagneId: campagneId ?? this.campagneId,
plateforme: plateforme ?? this.plateforme,
tokenFCM: tokenFCM ?? this.tokenFCM,
);
}
/// Vérifie si la notification est expirée
bool get isExpiree {
if (dateExpiration == null) return false;
return DateTime.now().isAfter(dateExpiration!);
}
/// Vérifie si la notification est récente (moins de 24h)
bool get isRecente {
final maintenant = DateTime.now();
final difference = maintenant.difference(dateCreation);
return difference.inHours < 24;
}
/// Retourne le temps écoulé depuis la création
String get tempsEcoule {
final maintenant = DateTime.now();
final difference = maintenant.difference(dateCreation);
if (difference.inMinutes < 1) {
return 'À l\'instant';
} else if (difference.inMinutes < 60) {
return 'Il y a ${difference.inMinutes}min';
} else if (difference.inHours < 24) {
return 'Il y a ${difference.inHours}h';
} else if (difference.inDays < 7) {
return 'Il y a ${difference.inDays}j';
} else {
return 'Il y a ${(difference.inDays / 7).floor()}sem';
}
}
/// Retourne le message à afficher (court ou complet)
String get messageAffichage => messageCourt ?? message;
/// Retourne la couleur du type de notification
String get couleurType => typeNotification.couleur;
/// Retourne l'icône du type de notification
String get iconeType => typeNotification.icone;
/// Vérifie si la notification a des actions rapides
bool get hasActionsRapides => actionsRapides != null && actionsRapides!.isNotEmpty;
/// Retourne les actions rapides actives
List<ActionNotification> get actionsRapidesActives {
if (actionsRapides == null) return [];
return actionsRapides!.where((action) => action.estActivee).toList();
}
/// Calcule le taux d'engagement
double get tauxEngagement {
if (nombreAffichages == 0) return 0.0;
return (nombreClics / nombreAffichages) * 100;
}
}

View File

@@ -1,451 +0,0 @@
import 'package:equatable/equatable.dart';
import 'package:json_annotation/json_annotation.dart';
import 'notification.dart';
part 'preferences_notification.g.dart';
/// Énumération des canaux de notification
enum CanalNotification {
@JsonValue('URGENT_CHANNEL')
urgent('urgent', 'Notifications urgentes', 5, true, true, '#F44336'),
@JsonValue('ERROR_CHANNEL')
error('error', 'Erreurs système', 4, true, true, '#F44336'),
@JsonValue('WARNING_CHANNEL')
warning('warning', 'Avertissements', 4, true, true, '#FF9800'),
@JsonValue('IMPORTANT_CHANNEL')
important('important', 'Notifications importantes', 4, true, true, '#FF5722'),
@JsonValue('REMINDER_CHANNEL')
reminder('reminder', 'Rappels', 3, true, true, '#2196F3'),
@JsonValue('SUCCESS_CHANNEL')
success('success', 'Confirmations', 2, false, false, '#4CAF50'),
@JsonValue('DEFAULT_CHANNEL')
defaultChannel('default', 'Notifications générales', 2, false, false, '#2196F3'),
@JsonValue('EVENTS_CHANNEL')
events('events', 'Événements', 3, true, false, '#2196F3'),
@JsonValue('PAYMENTS_CHANNEL')
payments('payments', 'Paiements', 4, true, true, '#4CAF50'),
@JsonValue('SOLIDARITY_CHANNEL')
solidarity('solidarity', 'Solidarité', 3, true, false, '#E91E63'),
@JsonValue('MEMBERS_CHANNEL')
members('members', 'Membres', 2, false, false, '#2196F3'),
@JsonValue('ORGANIZATION_CHANNEL')
organization('organization', 'Organisation', 3, true, false, '#2196F3'),
@JsonValue('SYSTEM_CHANNEL')
system('system', 'Système', 2, false, false, '#607D8B'),
@JsonValue('MESSAGES_CHANNEL')
messages('messages', 'Messages', 3, true, false, '#2196F3');
const CanalNotification(this.id, this.nom, this.importance, this.sonActive,
this.vibrationActive, this.couleur);
final String id;
final String nom;
final int importance;
final bool sonActive;
final bool vibrationActive;
final String couleur;
bool get isCritique => importance >= 4;
bool get isSilencieux => !sonActive && !vibrationActive;
}
/// Préférences spécifiques à un type de notification
@JsonSerializable()
class PreferenceTypeNotification extends Equatable {
const PreferenceTypeNotification({
this.active = true,
this.priorite,
this.sonPersonnalise,
this.patternVibration,
this.couleurLED,
this.dureeAffichageSecondes,
this.doitVibrer,
this.doitEmettreSon,
this.doitAllumerLED,
this.ignoreModesilencieux = false,
});
final bool active;
final int? priorite;
final String? sonPersonnalise;
final List<int>? patternVibration;
final String? couleurLED;
final int? dureeAffichageSecondes;
final bool? doitVibrer;
final bool? doitEmettreSon;
final bool? doitAllumerLED;
final bool ignoreModesilencieux;
factory PreferenceTypeNotification.fromJson(Map<String, dynamic> json) =>
_$PreferenceTypeNotificationFromJson(json);
Map<String, dynamic> toJson() => _$PreferenceTypeNotificationToJson(this);
@override
List<Object?> get props => [
active, priorite, sonPersonnalise, patternVibration, couleurLED,
dureeAffichageSecondes, doitVibrer, doitEmettreSon, doitAllumerLED,
ignoreModesilencieux,
];
PreferenceTypeNotification copyWith({
bool? active,
int? priorite,
String? sonPersonnalise,
List<int>? patternVibration,
String? couleurLED,
int? dureeAffichageSecondes,
bool? doitVibrer,
bool? doitEmettreSon,
bool? doitAllumerLED,
bool? ignoreModesilencieux,
}) {
return PreferenceTypeNotification(
active: active ?? this.active,
priorite: priorite ?? this.priorite,
sonPersonnalise: sonPersonnalise ?? this.sonPersonnalise,
patternVibration: patternVibration ?? this.patternVibration,
couleurLED: couleurLED ?? this.couleurLED,
dureeAffichageSecondes: dureeAffichageSecondes ?? this.dureeAffichageSecondes,
doitVibrer: doitVibrer ?? this.doitVibrer,
doitEmettreSon: doitEmettreSon ?? this.doitEmettreSon,
doitAllumerLED: doitAllumerLED ?? this.doitAllumerLED,
ignoreModesilencieux: ignoreModesilencieux ?? this.ignoreModesilencieux,
);
}
}
/// Préférences spécifiques à un canal de notification
@JsonSerializable()
class PreferenceCanalNotification extends Equatable {
const PreferenceCanalNotification({
this.active = true,
this.importance,
this.sonPersonnalise,
this.patternVibration,
this.couleurLED,
this.sonActive,
this.vibrationActive,
this.ledActive,
this.peutEtreDesactive = true,
});
final bool active;
final int? importance;
final String? sonPersonnalise;
final List<int>? patternVibration;
final String? couleurLED;
final bool? sonActive;
final bool? vibrationActive;
final bool? ledActive;
final bool peutEtreDesactive;
factory PreferenceCanalNotification.fromJson(Map<String, dynamic> json) =>
_$PreferenceCanalNotificationFromJson(json);
Map<String, dynamic> toJson() => _$PreferenceCanalNotificationToJson(this);
@override
List<Object?> get props => [
active, importance, sonPersonnalise, patternVibration, couleurLED,
sonActive, vibrationActive, ledActive, peutEtreDesactive,
];
PreferenceCanalNotification copyWith({
bool? active,
int? importance,
String? sonPersonnalise,
List<int>? patternVibration,
String? couleurLED,
bool? sonActive,
bool? vibrationActive,
bool? ledActive,
bool? peutEtreDesactive,
}) {
return PreferenceCanalNotification(
active: active ?? this.active,
importance: importance ?? this.importance,
sonPersonnalise: sonPersonnalise ?? this.sonPersonnalise,
patternVibration: patternVibration ?? this.patternVibration,
couleurLED: couleurLED ?? this.couleurLED,
sonActive: sonActive ?? this.sonActive,
vibrationActive: vibrationActive ?? this.vibrationActive,
ledActive: ledActive ?? this.ledActive,
peutEtreDesactive: peutEtreDesactive ?? this.peutEtreDesactive,
);
}
}
/// Entité principale des préférences de notification
@JsonSerializable()
class PreferencesNotificationEntity extends Equatable {
const PreferencesNotificationEntity({
required this.id,
required this.utilisateurId,
this.organisationId,
this.notificationsActivees = true,
this.pushActivees = true,
this.emailActivees = true,
this.smsActivees = false,
this.inAppActivees = true,
this.typesActives,
this.typesDesactivees,
this.canauxActifs,
this.canauxDesactives,
this.modeSilencieux = false,
this.heureDebutSilencieux,
this.heureFinSilencieux,
this.joursSilencieux,
this.urgentesIgnorentSilencieux = true,
this.frequenceRegroupementMinutes = 5,
this.maxNotificationsSimultanees = 10,
this.dureeAffichageSecondes = 10,
this.vibrationActivee = true,
this.sonActive = true,
this.ledActivee = true,
this.sonPersonnalise,
this.patternVibrationPersonnalise,
this.couleurLEDPersonnalisee,
this.apercuEcranVerrouillage = true,
this.affichageHistorique = true,
this.dureeConservationJours = 30,
this.marquageLectureAutomatique = false,
this.delaiMarquageLectureSecondes,
this.archivageAutomatique = true,
this.delaiArchivageHeures = 168,
this.preferencesParType,
this.preferencesParCanal,
this.motsClesFiltre,
this.expediteursBloques,
this.expediteursPrioritaires,
this.notificationsTestActivees = false,
this.niveauLog = 'INFO',
this.tokenFCM,
this.plateforme,
this.versionApp,
this.langue = 'fr',
this.fuseauHoraire,
this.metadonnees,
});
final String id;
final String utilisateurId;
final String? organisationId;
final bool notificationsActivees;
final bool pushActivees;
final bool emailActivees;
final bool smsActivees;
final bool inAppActivees;
final Set<TypeNotification>? typesActives;
final Set<TypeNotification>? typesDesactivees;
final Set<CanalNotification>? canauxActifs;
final Set<CanalNotification>? canauxDesactives;
final bool modeSilencieux;
final String? heureDebutSilencieux; // Format HH:mm
final String? heureFinSilencieux; // Format HH:mm
final Set<int>? joursSilencieux; // 1=Lundi, 7=Dimanche
final bool urgentesIgnorentSilencieux;
final int frequenceRegroupementMinutes;
final int maxNotificationsSimultanees;
final int dureeAffichageSecondes;
final bool vibrationActivee;
final bool sonActive;
final bool ledActivee;
final String? sonPersonnalise;
final List<int>? patternVibrationPersonnalise;
final String? couleurLEDPersonnalisee;
final bool apercuEcranVerrouillage;
final bool affichageHistorique;
final int dureeConservationJours;
final bool marquageLectureAutomatique;
final int? delaiMarquageLectureSecondes;
final bool archivageAutomatique;
final int delaiArchivageHeures;
final Map<TypeNotification, PreferenceTypeNotification>? preferencesParType;
final Map<CanalNotification, PreferenceCanalNotification>? preferencesParCanal;
final Set<String>? motsClesFiltre;
final Set<String>? expediteursBloques;
final Set<String>? expediteursPrioritaires;
final bool notificationsTestActivees;
final String niveauLog;
final String? tokenFCM;
final String? plateforme;
final String? versionApp;
final String langue;
final String? fuseauHoraire;
final Map<String, dynamic>? metadonnees;
factory PreferencesNotificationEntity.fromJson(Map<String, dynamic> json) =>
_$PreferencesNotificationEntityFromJson(json);
Map<String, dynamic> toJson() => _$PreferencesNotificationEntityToJson(this);
@override
List<Object?> get props => [
id, utilisateurId, organisationId, notificationsActivees, pushActivees,
emailActivees, smsActivees, inAppActivees, typesActives, typesDesactivees,
canauxActifs, canauxDesactives, modeSilencieux, heureDebutSilencieux,
heureFinSilencieux, joursSilencieux, urgentesIgnorentSilencieux,
frequenceRegroupementMinutes, maxNotificationsSimultanees,
dureeAffichageSecondes, vibrationActivee, sonActive, ledActivee,
sonPersonnalise, patternVibrationPersonnalise, couleurLEDPersonnalisee,
apercuEcranVerrouillage, affichageHistorique, dureeConservationJours,
marquageLectureAutomatique, delaiMarquageLectureSecondes,
archivageAutomatique, delaiArchivageHeures, preferencesParType,
preferencesParCanal, motsClesFiltre, expediteursBloques,
expediteursPrioritaires, notificationsTestActivees, niveauLog,
tokenFCM, plateforme, versionApp, langue, fuseauHoraire, metadonnees,
];
PreferencesNotificationEntity copyWith({
String? id,
String? utilisateurId,
String? organisationId,
bool? notificationsActivees,
bool? pushActivees,
bool? emailActivees,
bool? smsActivees,
bool? inAppActivees,
Set<TypeNotification>? typesActives,
Set<TypeNotification>? typesDesactivees,
Set<CanalNotification>? canauxActifs,
Set<CanalNotification>? canauxDesactives,
bool? modeSilencieux,
String? heureDebutSilencieux,
String? heureFinSilencieux,
Set<int>? joursSilencieux,
bool? urgentesIgnorentSilencieux,
int? frequenceRegroupementMinutes,
int? maxNotificationsSimultanees,
int? dureeAffichageSecondes,
bool? vibrationActivee,
bool? sonActive,
bool? ledActivee,
String? sonPersonnalise,
List<int>? patternVibrationPersonnalise,
String? couleurLEDPersonnalisee,
bool? apercuEcranVerrouillage,
bool? affichageHistorique,
int? dureeConservationJours,
bool? marquageLectureAutomatique,
int? delaiMarquageLectureSecondes,
bool? archivageAutomatique,
int? delaiArchivageHeures,
Map<TypeNotification, PreferenceTypeNotification>? preferencesParType,
Map<CanalNotification, PreferenceCanalNotification>? preferencesParCanal,
Set<String>? motsClesFiltre,
Set<String>? expediteursBloques,
Set<String>? expediteursPrioritaires,
bool? notificationsTestActivees,
String? niveauLog,
String? tokenFCM,
String? plateforme,
String? versionApp,
String? langue,
String? fuseauHoraire,
Map<String, dynamic>? metadonnees,
}) {
return PreferencesNotificationEntity(
id: id ?? this.id,
utilisateurId: utilisateurId ?? this.utilisateurId,
organisationId: organisationId ?? this.organisationId,
notificationsActivees: notificationsActivees ?? this.notificationsActivees,
pushActivees: pushActivees ?? this.pushActivees,
emailActivees: emailActivees ?? this.emailActivees,
smsActivees: smsActivees ?? this.smsActivees,
inAppActivees: inAppActivees ?? this.inAppActivees,
typesActives: typesActives ?? this.typesActives,
typesDesactivees: typesDesactivees ?? this.typesDesactivees,
canauxActifs: canauxActifs ?? this.canauxActifs,
canauxDesactives: canauxDesactives ?? this.canauxDesactives,
modeSilencieux: modeSilencieux ?? this.modeSilencieux,
heureDebutSilencieux: heureDebutSilencieux ?? this.heureDebutSilencieux,
heureFinSilencieux: heureFinSilencieux ?? this.heureFinSilencieux,
joursSilencieux: joursSilencieux ?? this.joursSilencieux,
urgentesIgnorentSilencieux: urgentesIgnorentSilencieux ?? this.urgentesIgnorentSilencieux,
frequenceRegroupementMinutes: frequenceRegroupementMinutes ?? this.frequenceRegroupementMinutes,
maxNotificationsSimultanees: maxNotificationsSimultanees ?? this.maxNotificationsSimultanees,
dureeAffichageSecondes: dureeAffichageSecondes ?? this.dureeAffichageSecondes,
vibrationActivee: vibrationActivee ?? this.vibrationActivee,
sonActive: sonActive ?? this.sonActive,
ledActivee: ledActivee ?? this.ledActivee,
sonPersonnalise: sonPersonnalise ?? this.sonPersonnalise,
patternVibrationPersonnalise: patternVibrationPersonnalise ?? this.patternVibrationPersonnalise,
couleurLEDPersonnalisee: couleurLEDPersonnalisee ?? this.couleurLEDPersonnalisee,
apercuEcranVerrouillage: apercuEcranVerrouillage ?? this.apercuEcranVerrouillage,
affichageHistorique: affichageHistorique ?? this.affichageHistorique,
dureeConservationJours: dureeConservationJours ?? this.dureeConservationJours,
marquageLectureAutomatique: marquageLectureAutomatique ?? this.marquageLectureAutomatique,
delaiMarquageLectureSecondes: delaiMarquageLectureSecondes ?? this.delaiMarquageLectureSecondes,
archivageAutomatique: archivageAutomatique ?? this.archivageAutomatique,
delaiArchivageHeures: delaiArchivageHeures ?? this.delaiArchivageHeures,
preferencesParType: preferencesParType ?? this.preferencesParType,
preferencesParCanal: preferencesParCanal ?? this.preferencesParCanal,
motsClesFiltre: motsClesFiltre ?? this.motsClesFiltre,
expediteursBloques: expediteursBloques ?? this.expediteursBloques,
expediteursPrioritaires: expediteursPrioritaires ?? this.expediteursPrioritaires,
notificationsTestActivees: notificationsTestActivees ?? this.notificationsTestActivees,
niveauLog: niveauLog ?? this.niveauLog,
tokenFCM: tokenFCM ?? this.tokenFCM,
plateforme: plateforme ?? this.plateforme,
versionApp: versionApp ?? this.versionApp,
langue: langue ?? this.langue,
fuseauHoraire: fuseauHoraire ?? this.fuseauHoraire,
metadonnees: metadonnees ?? this.metadonnees,
);
}
/// Vérifie si un type de notification est activé
bool isTypeActive(TypeNotification type) {
if (!notificationsActivees) return false;
if (typesDesactivees?.contains(type) == true) return false;
if (typesActives != null) return typesActives!.contains(type);
return true; // Activé par défaut
}
/// Vérifie si un canal de notification est activé
bool isCanalActif(CanalNotification canal) {
if (!notificationsActivees) return false;
if (canauxDesactives?.contains(canal) == true) return false;
if (canauxActifs != null) return canauxActifs!.contains(canal);
return true; // Activé par défaut
}
/// Vérifie si on est en mode silencieux actuellement
bool get isEnModeSilencieux {
if (!modeSilencieux) return false;
if (heureDebutSilencieux == null || heureFinSilencieux == null) return false;
final maintenant = DateTime.now();
final heureActuelle = '${maintenant.hour.toString().padLeft(2, '0')}:${maintenant.minute.toString().padLeft(2, '0')}';
// Gestion du cas où la période traverse minuit
if (heureDebutSilencieux!.compareTo(heureFinSilencieux!) > 0) {
return heureActuelle.compareTo(heureDebutSilencieux!) >= 0 ||
heureActuelle.compareTo(heureFinSilencieux!) <= 0;
} else {
return heureActuelle.compareTo(heureDebutSilencieux!) >= 0 &&
heureActuelle.compareTo(heureFinSilencieux!) <= 0;
}
}
/// Vérifie si un expéditeur est bloqué
bool isExpediteurBloque(String? expediteurId) {
if (expediteurId == null) return false;
return expediteursBloques?.contains(expediteurId) == true;
}
/// Vérifie si un expéditeur est prioritaire
bool isExpediteurPrioritaire(String? expediteurId) {
if (expediteurId == null) return false;
return expediteursPrioritaires?.contains(expediteurId) == true;
}
/// Crée des préférences par défaut pour un utilisateur
static PreferencesNotificationEntity creerDefaut(String utilisateurId) {
return PreferencesNotificationEntity(
id: 'pref_$utilisateurId',
utilisateurId: utilisateurId,
);
}
}

View File

@@ -1,310 +0,0 @@
import 'package:dartz/dartz.dart';
import '../../../../core/error/failures.dart';
import '../entities/notification.dart';
import '../entities/preferences_notification.dart';
/// Repository abstrait pour la gestion des notifications
abstract class NotificationsRepository {
// === GESTION DES NOTIFICATIONS ===
/// Récupère les notifications d'un utilisateur
///
/// [utilisateurId] ID de l'utilisateur
/// [includeArchivees] Inclure les notifications archivées
/// [limite] Nombre maximum de notifications à retourner
/// [offset] Décalage pour la pagination
Future<Either<Failure, List<NotificationEntity>>> obtenirNotifications({
required String utilisateurId,
bool includeArchivees = false,
int limite = 50,
int offset = 0,
});
/// Récupère une notification spécifique
///
/// [notificationId] ID de la notification
Future<Either<Failure, NotificationEntity>> obtenirNotification(String notificationId);
/// Marque une notification comme lue
///
/// [notificationId] ID de la notification
/// [utilisateurId] ID de l'utilisateur
Future<Either<Failure, void>> marquerCommeLue(String notificationId, String utilisateurId);
/// Marque toutes les notifications comme lues
///
/// [utilisateurId] ID de l'utilisateur
Future<Either<Failure, void>> marquerToutesCommeLues(String utilisateurId);
/// Marque une notification comme importante
///
/// [notificationId] ID de la notification
/// [utilisateurId] ID de l'utilisateur
/// [importante] true pour marquer comme importante, false pour enlever
Future<Either<Failure, void>> marquerCommeImportante(
String notificationId,
String utilisateurId,
bool importante,
);
/// Archive une notification
///
/// [notificationId] ID de la notification
/// [utilisateurId] ID de l'utilisateur
Future<Either<Failure, void>> archiverNotification(String notificationId, String utilisateurId);
/// Archive toutes les notifications lues
///
/// [utilisateurId] ID de l'utilisateur
Future<Either<Failure, void>> archiverToutesLues(String utilisateurId);
/// Supprime une notification
///
/// [notificationId] ID de la notification
/// [utilisateurId] ID de l'utilisateur
Future<Either<Failure, void>> supprimerNotification(String notificationId, String utilisateurId);
/// Supprime toutes les notifications archivées
///
/// [utilisateurId] ID de l'utilisateur
Future<Either<Failure, void>> supprimerToutesArchivees(String utilisateurId);
// === FILTRAGE ET RECHERCHE ===
/// Recherche des notifications par critères
///
/// [utilisateurId] ID de l'utilisateur
/// [query] Texte de recherche
/// [types] Types de notifications à inclure
/// [statuts] Statuts de notifications à inclure
/// [dateDebut] Date de début de la période
/// [dateFin] Date de fin de la période
/// [limite] Nombre maximum de résultats
Future<Either<Failure, List<NotificationEntity>>> rechercherNotifications({
required String utilisateurId,
String? query,
List<TypeNotification>? types,
List<StatutNotification>? statuts,
DateTime? dateDebut,
DateTime? dateFin,
int limite = 50,
});
/// Récupère les notifications par type
///
/// [utilisateurId] ID de l'utilisateur
/// [type] Type de notification
/// [limite] Nombre maximum de notifications
Future<Either<Failure, List<NotificationEntity>>> obtenirNotificationsParType(
String utilisateurId,
TypeNotification type, {
int limite = 50,
});
/// Récupère les notifications non lues
///
/// [utilisateurId] ID de l'utilisateur
/// [limite] Nombre maximum de notifications
Future<Either<Failure, List<NotificationEntity>>> obtenirNotificationsNonLues(
String utilisateurId, {
int limite = 50,
});
/// Récupère les notifications importantes
///
/// [utilisateurId] ID de l'utilisateur
/// [limite] Nombre maximum de notifications
Future<Either<Failure, List<NotificationEntity>>> obtenirNotificationsImportantes(
String utilisateurId, {
int limite = 50,
});
// === STATISTIQUES ===
/// Récupère le nombre de notifications non lues
///
/// [utilisateurId] ID de l'utilisateur
Future<Either<Failure, int>> obtenirNombreNonLues(String utilisateurId);
/// Récupère les statistiques des notifications
///
/// [utilisateurId] ID de l'utilisateur
/// [periode] Période d'analyse (en jours)
Future<Either<Failure, Map<String, dynamic>>> obtenirStatistiques(
String utilisateurId, {
int periode = 30,
});
// === ACTIONS SUR LES NOTIFICATIONS ===
/// Exécute une action rapide sur une notification
///
/// [notificationId] ID de la notification
/// [actionId] ID de l'action à exécuter
/// [utilisateurId] ID de l'utilisateur
/// [parametres] Paramètres additionnels pour l'action
Future<Either<Failure, Map<String, dynamic>>> executerActionRapide(
String notificationId,
String actionId,
String utilisateurId, {
Map<String, dynamic>? parametres,
});
/// Signale une notification comme spam
///
/// [notificationId] ID de la notification
/// [utilisateurId] ID de l'utilisateur
/// [raison] Raison du signalement
Future<Either<Failure, void>> signalerSpam(
String notificationId,
String utilisateurId,
String raison,
);
// === PRÉFÉRENCES DE NOTIFICATION ===
/// Récupère les préférences de notification d'un utilisateur
///
/// [utilisateurId] ID de l'utilisateur
Future<Either<Failure, PreferencesNotificationEntity>> obtenirPreferences(String utilisateurId);
/// Met à jour les préférences de notification
///
/// [preferences] Nouvelles préférences
Future<Either<Failure, void>> mettreAJourPreferences(PreferencesNotificationEntity preferences);
/// Réinitialise les préférences aux valeurs par défaut
///
/// [utilisateurId] ID de l'utilisateur
Future<Either<Failure, PreferencesNotificationEntity>> reinitialiserPreferences(String utilisateurId);
/// Active/désactive un type de notification
///
/// [utilisateurId] ID de l'utilisateur
/// [type] Type de notification
/// [active] true pour activer, false pour désactiver
Future<Either<Failure, void>> toggleTypeNotification(
String utilisateurId,
TypeNotification type,
bool active,
);
/// Active/désactive un canal de notification
///
/// [utilisateurId] ID de l'utilisateur
/// [canal] Canal de notification
/// [active] true pour activer, false pour désactiver
Future<Either<Failure, void>> toggleCanalNotification(
String utilisateurId,
CanalNotification canal,
bool active,
);
/// Configure le mode silencieux
///
/// [utilisateurId] ID de l'utilisateur
/// [active] true pour activer le mode silencieux
/// [heureDebut] Heure de début (format HH:mm)
/// [heureFin] Heure de fin (format HH:mm)
/// [jours] Jours de la semaine (1=Lundi, 7=Dimanche)
Future<Either<Failure, void>> configurerModeSilencieux(
String utilisateurId,
bool active, {
String? heureDebut,
String? heureFin,
Set<int>? jours,
});
// === GESTION DES TOKENS FCM ===
/// Enregistre ou met à jour le token FCM
///
/// [utilisateurId] ID de l'utilisateur
/// [token] Token FCM
/// [plateforme] Plateforme (android, ios)
Future<Either<Failure, void>> enregistrerTokenFCM(
String utilisateurId,
String token,
String plateforme,
);
/// Supprime le token FCM
///
/// [utilisateurId] ID de l'utilisateur
Future<Either<Failure, void>> supprimerTokenFCM(String utilisateurId);
// === NOTIFICATIONS DE TEST ===
/// Envoie une notification de test
///
/// [utilisateurId] ID de l'utilisateur
/// [type] Type de notification à tester
Future<Either<Failure, NotificationEntity>> envoyerNotificationTest(
String utilisateurId,
TypeNotification type,
);
// === CACHE ET SYNCHRONISATION ===
/// Synchronise les notifications avec le serveur
///
/// [utilisateurId] ID de l'utilisateur
/// [forceSync] Force la synchronisation même si le cache est récent
Future<Either<Failure, void>> synchroniser(String utilisateurId, {bool forceSync = false});
/// Vide le cache des notifications
///
/// [utilisateurId] ID de l'utilisateur (optionnel, vide tout si null)
Future<Either<Failure, void>> viderCache([String? utilisateurId]);
/// Vérifie si les données sont en cache et récentes
///
/// [utilisateurId] ID de l'utilisateur
/// [maxAgeMinutes] Âge maximum du cache en minutes
Future<bool> isCacheValide(String utilisateurId, {int maxAgeMinutes = 5});
// === ABONNEMENTS ET TOPICS ===
/// S'abonne à un topic de notifications
///
/// [utilisateurId] ID de l'utilisateur
/// [topic] Nom du topic
Future<Either<Failure, void>> abonnerAuTopic(String utilisateurId, String topic);
/// Se désabonne d'un topic de notifications
///
/// [utilisateurId] ID de l'utilisateur
/// [topic] Nom du topic
Future<Either<Failure, void>> desabonnerDuTopic(String utilisateurId, String topic);
/// Récupère la liste des topics auxquels l'utilisateur est abonné
///
/// [utilisateurId] ID de l'utilisateur
Future<Either<Failure, List<String>>> obtenirTopicsAbornes(String utilisateurId);
// === EXPORT ET SAUVEGARDE ===
/// Exporte les notifications vers un fichier
///
/// [utilisateurId] ID de l'utilisateur
/// [format] Format d'export (json, csv)
/// [dateDebut] Date de début de la période
/// [dateFin] Date de fin de la période
Future<Either<Failure, String>> exporterNotifications(
String utilisateurId,
String format, {
DateTime? dateDebut,
DateTime? dateFin,
});
/// Sauvegarde les notifications localement
///
/// [utilisateurId] ID de l'utilisateur
Future<Either<Failure, void>> sauvegarderLocalement(String utilisateurId);
/// Restaure les notifications depuis une sauvegarde locale
///
/// [utilisateurId] ID de l'utilisateur
Future<Either<Failure, void>> restaurerDepuisSauvegarde(String utilisateurId);
}

View File

@@ -1,388 +0,0 @@
import 'package:dartz/dartz.dart';
import '../../../../core/error/failures.dart';
import '../../../../core/usecases/usecase.dart';
import '../entities/notification.dart';
import '../repositories/notifications_repository.dart';
/// Use case pour marquer une notification comme lue
class MarquerCommeLueUseCase implements UseCase<void, MarquerCommeLueParams> {
final NotificationsRepository repository;
MarquerCommeLueUseCase(this.repository);
@override
Future<Either<Failure, void>> call(MarquerCommeLueParams params) async {
return await repository.marquerCommeLue(
params.notificationId,
params.utilisateurId,
);
}
}
/// Paramètres pour marquer comme lue
class MarquerCommeLueParams {
final String notificationId;
final String utilisateurId;
const MarquerCommeLueParams({
required this.notificationId,
required this.utilisateurId,
});
@override
String toString() {
return 'MarquerCommeLueParams{notificationId: $notificationId, utilisateurId: $utilisateurId}';
}
}
/// Use case pour marquer toutes les notifications comme lues
class MarquerToutesCommeLuesUseCase implements UseCase<void, String> {
final NotificationsRepository repository;
MarquerToutesCommeLuesUseCase(this.repository);
@override
Future<Either<Failure, void>> call(String utilisateurId) async {
return await repository.marquerToutesCommeLues(utilisateurId);
}
}
/// Use case pour marquer une notification comme importante
class MarquerCommeImportanteUseCase implements UseCase<void, MarquerCommeImportanteParams> {
final NotificationsRepository repository;
MarquerCommeImportanteUseCase(this.repository);
@override
Future<Either<Failure, void>> call(MarquerCommeImportanteParams params) async {
return await repository.marquerCommeImportante(
params.notificationId,
params.utilisateurId,
params.importante,
);
}
}
/// Paramètres pour marquer comme importante
class MarquerCommeImportanteParams {
final String notificationId;
final String utilisateurId;
final bool importante;
const MarquerCommeImportanteParams({
required this.notificationId,
required this.utilisateurId,
required this.importante,
});
@override
String toString() {
return 'MarquerCommeImportanteParams{notificationId: $notificationId, utilisateurId: $utilisateurId, importante: $importante}';
}
}
/// Use case pour archiver une notification
class ArchiverNotificationUseCase implements UseCase<void, ArchiverNotificationParams> {
final NotificationsRepository repository;
ArchiverNotificationUseCase(this.repository);
@override
Future<Either<Failure, void>> call(ArchiverNotificationParams params) async {
return await repository.archiverNotification(
params.notificationId,
params.utilisateurId,
);
}
}
/// Paramètres pour archiver une notification
class ArchiverNotificationParams {
final String notificationId;
final String utilisateurId;
const ArchiverNotificationParams({
required this.notificationId,
required this.utilisateurId,
});
@override
String toString() {
return 'ArchiverNotificationParams{notificationId: $notificationId, utilisateurId: $utilisateurId}';
}
}
/// Use case pour archiver toutes les notifications lues
class ArchiverToutesLuesUseCase implements UseCase<void, String> {
final NotificationsRepository repository;
ArchiverToutesLuesUseCase(this.repository);
@override
Future<Either<Failure, void>> call(String utilisateurId) async {
return await repository.archiverToutesLues(utilisateurId);
}
}
/// Use case pour supprimer une notification
class SupprimerNotificationUseCase implements UseCase<void, SupprimerNotificationParams> {
final NotificationsRepository repository;
SupprimerNotificationUseCase(this.repository);
@override
Future<Either<Failure, void>> call(SupprimerNotificationParams params) async {
return await repository.supprimerNotification(
params.notificationId,
params.utilisateurId,
);
}
}
/// Paramètres pour supprimer une notification
class SupprimerNotificationParams {
final String notificationId;
final String utilisateurId;
const SupprimerNotificationParams({
required this.notificationId,
required this.utilisateurId,
});
@override
String toString() {
return 'SupprimerNotificationParams{notificationId: $notificationId, utilisateurId: $utilisateurId}';
}
}
/// Use case pour supprimer toutes les notifications archivées
class SupprimerToutesArchiveesUseCase implements UseCase<void, String> {
final NotificationsRepository repository;
SupprimerToutesArchiveesUseCase(this.repository);
@override
Future<Either<Failure, void>> call(String utilisateurId) async {
return await repository.supprimerToutesArchivees(utilisateurId);
}
}
/// Use case pour exécuter une action rapide
class ExecuterActionRapideUseCase implements UseCase<Map<String, dynamic>, ExecuterActionRapideParams> {
final NotificationsRepository repository;
ExecuterActionRapideUseCase(this.repository);
@override
Future<Either<Failure, Map<String, dynamic>>> call(ExecuterActionRapideParams params) async {
return await repository.executerActionRapide(
params.notificationId,
params.actionId,
params.utilisateurId,
parametres: params.parametres,
);
}
}
/// Paramètres pour exécuter une action rapide
class ExecuterActionRapideParams {
final String notificationId;
final String actionId;
final String utilisateurId;
final Map<String, dynamic>? parametres;
const ExecuterActionRapideParams({
required this.notificationId,
required this.actionId,
required this.utilisateurId,
this.parametres,
});
ExecuterActionRapideParams copyWith({
String? notificationId,
String? actionId,
String? utilisateurId,
Map<String, dynamic>? parametres,
}) {
return ExecuterActionRapideParams(
notificationId: notificationId ?? this.notificationId,
actionId: actionId ?? this.actionId,
utilisateurId: utilisateurId ?? this.utilisateurId,
parametres: parametres ?? this.parametres,
);
}
@override
String toString() {
return 'ExecuterActionRapideParams{notificationId: $notificationId, actionId: $actionId, utilisateurId: $utilisateurId, parametres: $parametres}';
}
}
/// Use case pour signaler une notification comme spam
class SignalerSpamUseCase implements UseCase<void, SignalerSpamParams> {
final NotificationsRepository repository;
SignalerSpamUseCase(this.repository);
@override
Future<Either<Failure, void>> call(SignalerSpamParams params) async {
return await repository.signalerSpam(
params.notificationId,
params.utilisateurId,
params.raison,
);
}
}
/// Paramètres pour signaler comme spam
class SignalerSpamParams {
final String notificationId;
final String utilisateurId;
final String raison;
const SignalerSpamParams({
required this.notificationId,
required this.utilisateurId,
required this.raison,
});
@override
String toString() {
return 'SignalerSpamParams{notificationId: $notificationId, utilisateurId: $utilisateurId, raison: $raison}';
}
}
/// Use case pour synchroniser les notifications
class SynchroniserNotificationsUseCase implements UseCase<void, SynchroniserNotificationsParams> {
final NotificationsRepository repository;
SynchroniserNotificationsUseCase(this.repository);
@override
Future<Either<Failure, void>> call(SynchroniserNotificationsParams params) async {
return await repository.synchroniser(
params.utilisateurId,
forceSync: params.forceSync,
);
}
}
/// Paramètres pour synchroniser les notifications
class SynchroniserNotificationsParams {
final String utilisateurId;
final bool forceSync;
const SynchroniserNotificationsParams({
required this.utilisateurId,
this.forceSync = false,
});
SynchroniserNotificationsParams copyWith({
String? utilisateurId,
bool? forceSync,
}) {
return SynchroniserNotificationsParams(
utilisateurId: utilisateurId ?? this.utilisateurId,
forceSync: forceSync ?? this.forceSync,
);
}
@override
String toString() {
return 'SynchroniserNotificationsParams{utilisateurId: $utilisateurId, forceSync: $forceSync}';
}
}
/// Use case pour vider le cache des notifications
class ViderCacheNotificationsUseCase implements UseCase<void, String?> {
final NotificationsRepository repository;
ViderCacheNotificationsUseCase(this.repository);
@override
Future<Either<Failure, void>> call(String? utilisateurId) async {
return await repository.viderCache(utilisateurId);
}
}
/// Use case pour envoyer une notification de test
class EnvoyerNotificationTestUseCase implements UseCase<NotificationEntity, EnvoyerNotificationTestParams> {
final NotificationsRepository repository;
EnvoyerNotificationTestUseCase(this.repository);
@override
Future<Either<Failure, NotificationEntity>> call(EnvoyerNotificationTestParams params) async {
return await repository.envoyerNotificationTest(
params.utilisateurId,
params.type,
);
}
}
/// Paramètres pour envoyer une notification de test
class EnvoyerNotificationTestParams {
final String utilisateurId;
final TypeNotification type;
const EnvoyerNotificationTestParams({
required this.utilisateurId,
required this.type,
});
@override
String toString() {
return 'EnvoyerNotificationTestParams{utilisateurId: $utilisateurId, type: $type}';
}
}
/// Use case pour exporter les notifications
class ExporterNotificationsUseCase implements UseCase<String, ExporterNotificationsParams> {
final NotificationsRepository repository;
ExporterNotificationsUseCase(this.repository);
@override
Future<Either<Failure, String>> call(ExporterNotificationsParams params) async {
return await repository.exporterNotifications(
params.utilisateurId,
params.format,
dateDebut: params.dateDebut,
dateFin: params.dateFin,
);
}
}
/// Paramètres pour exporter les notifications
class ExporterNotificationsParams {
final String utilisateurId;
final String format;
final DateTime? dateDebut;
final DateTime? dateFin;
const ExporterNotificationsParams({
required this.utilisateurId,
required this.format,
this.dateDebut,
this.dateFin,
});
ExporterNotificationsParams copyWith({
String? utilisateurId,
String? format,
DateTime? dateDebut,
DateTime? dateFin,
}) {
return ExporterNotificationsParams(
utilisateurId: utilisateurId ?? this.utilisateurId,
format: format ?? this.format,
dateDebut: dateDebut ?? this.dateDebut,
dateFin: dateFin ?? this.dateFin,
);
}
@override
String toString() {
return 'ExporterNotificationsParams{utilisateurId: $utilisateurId, format: $format, dateDebut: $dateDebut, dateFin: $dateFin}';
}
}

View File

@@ -1,369 +0,0 @@
import 'package:dartz/dartz.dart';
import '../../../../core/error/failures.dart';
import '../../../../core/usecases/usecase.dart';
import '../entities/notification.dart';
import '../entities/preferences_notification.dart';
import '../repositories/notifications_repository.dart';
/// Use case pour obtenir les préférences de notification
class ObtenirPreferencesUseCase implements UseCase<PreferencesNotificationEntity, String> {
final NotificationsRepository repository;
ObtenirPreferencesUseCase(this.repository);
@override
Future<Either<Failure, PreferencesNotificationEntity>> call(String utilisateurId) async {
return await repository.obtenirPreferences(utilisateurId);
}
}
/// Use case pour mettre à jour les préférences de notification
class MettreAJourPreferencesUseCase implements UseCase<void, PreferencesNotificationEntity> {
final NotificationsRepository repository;
MettreAJourPreferencesUseCase(this.repository);
@override
Future<Either<Failure, void>> call(PreferencesNotificationEntity preferences) async {
return await repository.mettreAJourPreferences(preferences);
}
}
/// Use case pour réinitialiser les préférences
class ReinitialiserPreferencesUseCase implements UseCase<PreferencesNotificationEntity, String> {
final NotificationsRepository repository;
ReinitialiserPreferencesUseCase(this.repository);
@override
Future<Either<Failure, PreferencesNotificationEntity>> call(String utilisateurId) async {
return await repository.reinitialiserPreferences(utilisateurId);
}
}
/// Use case pour activer/désactiver un type de notification
class ToggleTypeNotificationUseCase implements UseCase<void, ToggleTypeNotificationParams> {
final NotificationsRepository repository;
ToggleTypeNotificationUseCase(this.repository);
@override
Future<Either<Failure, void>> call(ToggleTypeNotificationParams params) async {
return await repository.toggleTypeNotification(
params.utilisateurId,
params.type,
params.active,
);
}
}
/// Paramètres pour activer/désactiver un type de notification
class ToggleTypeNotificationParams {
final String utilisateurId;
final TypeNotification type;
final bool active;
const ToggleTypeNotificationParams({
required this.utilisateurId,
required this.type,
required this.active,
});
@override
String toString() {
return 'ToggleTypeNotificationParams{utilisateurId: $utilisateurId, type: $type, active: $active}';
}
}
/// Use case pour activer/désactiver un canal de notification
class ToggleCanalNotificationUseCase implements UseCase<void, ToggleCanalNotificationParams> {
final NotificationsRepository repository;
ToggleCanalNotificationUseCase(this.repository);
@override
Future<Either<Failure, void>> call(ToggleCanalNotificationParams params) async {
return await repository.toggleCanalNotification(
params.utilisateurId,
params.canal,
params.active,
);
}
}
/// Paramètres pour activer/désactiver un canal de notification
class ToggleCanalNotificationParams {
final String utilisateurId;
final CanalNotification canal;
final bool active;
const ToggleCanalNotificationParams({
required this.utilisateurId,
required this.canal,
required this.active,
});
@override
String toString() {
return 'ToggleCanalNotificationParams{utilisateurId: $utilisateurId, canal: $canal, active: $active}';
}
}
/// Use case pour configurer le mode silencieux
class ConfigurerModeSilencieuxUseCase implements UseCase<void, ConfigurerModeSilencieuxParams> {
final NotificationsRepository repository;
ConfigurerModeSilencieuxUseCase(this.repository);
@override
Future<Either<Failure, void>> call(ConfigurerModeSilencieuxParams params) async {
return await repository.configurerModeSilencieux(
params.utilisateurId,
params.active,
heureDebut: params.heureDebut,
heureFin: params.heureFin,
jours: params.jours,
);
}
}
/// Paramètres pour configurer le mode silencieux
class ConfigurerModeSilencieuxParams {
final String utilisateurId;
final bool active;
final String? heureDebut;
final String? heureFin;
final Set<int>? jours;
const ConfigurerModeSilencieuxParams({
required this.utilisateurId,
required this.active,
this.heureDebut,
this.heureFin,
this.jours,
});
ConfigurerModeSilencieuxParams copyWith({
String? utilisateurId,
bool? active,
String? heureDebut,
String? heureFin,
Set<int>? jours,
}) {
return ConfigurerModeSilencieuxParams(
utilisateurId: utilisateurId ?? this.utilisateurId,
active: active ?? this.active,
heureDebut: heureDebut ?? this.heureDebut,
heureFin: heureFin ?? this.heureFin,
jours: jours ?? this.jours,
);
}
@override
String toString() {
return 'ConfigurerModeSilencieuxParams{utilisateurId: $utilisateurId, active: $active, heureDebut: $heureDebut, heureFin: $heureFin, jours: $jours}';
}
}
/// Use case pour enregistrer le token FCM
class EnregistrerTokenFCMUseCase implements UseCase<void, EnregistrerTokenFCMParams> {
final NotificationsRepository repository;
EnregistrerTokenFCMUseCase(this.repository);
@override
Future<Either<Failure, void>> call(EnregistrerTokenFCMParams params) async {
return await repository.enregistrerTokenFCM(
params.utilisateurId,
params.token,
params.plateforme,
);
}
}
/// Paramètres pour enregistrer le token FCM
class EnregistrerTokenFCMParams {
final String utilisateurId;
final String token;
final String plateforme;
const EnregistrerTokenFCMParams({
required this.utilisateurId,
required this.token,
required this.plateforme,
});
@override
String toString() {
return 'EnregistrerTokenFCMParams{utilisateurId: $utilisateurId, token: $token, plateforme: $plateforme}';
}
}
/// Use case pour supprimer le token FCM
class SupprimerTokenFCMUseCase implements UseCase<void, String> {
final NotificationsRepository repository;
SupprimerTokenFCMUseCase(this.repository);
@override
Future<Either<Failure, void>> call(String utilisateurId) async {
return await repository.supprimerTokenFCM(utilisateurId);
}
}
/// Use case pour s'abonner à un topic
class AbonnerAuTopicUseCase implements UseCase<void, AbonnerAuTopicParams> {
final NotificationsRepository repository;
AbonnerAuTopicUseCase(this.repository);
@override
Future<Either<Failure, void>> call(AbonnerAuTopicParams params) async {
return await repository.abonnerAuTopic(
params.utilisateurId,
params.topic,
);
}
}
/// Paramètres pour s'abonner à un topic
class AbonnerAuTopicParams {
final String utilisateurId;
final String topic;
const AbonnerAuTopicParams({
required this.utilisateurId,
required this.topic,
});
@override
String toString() {
return 'AbonnerAuTopicParams{utilisateurId: $utilisateurId, topic: $topic}';
}
}
/// Use case pour se désabonner d'un topic
class DesabonnerDuTopicUseCase implements UseCase<void, DesabonnerDuTopicParams> {
final NotificationsRepository repository;
DesabonnerDuTopicUseCase(this.repository);
@override
Future<Either<Failure, void>> call(DesabonnerDuTopicParams params) async {
return await repository.desabonnerDuTopic(
params.utilisateurId,
params.topic,
);
}
}
/// Paramètres pour se désabonner d'un topic
class DesabonnerDuTopicParams {
final String utilisateurId;
final String topic;
const DesabonnerDuTopicParams({
required this.utilisateurId,
required this.topic,
});
@override
String toString() {
return 'DesabonnerDuTopicParams{utilisateurId: $utilisateurId, topic: $topic}';
}
}
/// Use case pour obtenir les topics auxquels l'utilisateur est abonné
class ObtenirTopicsAbornesUseCase implements UseCase<List<String>, String> {
final NotificationsRepository repository;
ObtenirTopicsAbornesUseCase(this.repository);
@override
Future<Either<Failure, List<String>>> call(String utilisateurId) async {
return await repository.obtenirTopicsAbornes(utilisateurId);
}
}
/// Use case pour configurer les préférences avancées
class ConfigurerPreferencesAvanceesUseCase implements UseCase<void, ConfigurerPreferencesAvanceesParams> {
final NotificationsRepository repository;
ConfigurerPreferencesAvanceesUseCase(this.repository);
@override
Future<Either<Failure, void>> call(ConfigurerPreferencesAvanceesParams params) async {
// Récupération des préférences actuelles
final preferencesResult = await repository.obtenirPreferences(params.utilisateurId);
return preferencesResult.fold(
(failure) => Left(failure),
(preferences) async {
// Mise à jour des préférences avec les nouveaux paramètres
final preferencesModifiees = preferences.copyWith(
vibrationActivee: params.vibrationActivee ?? preferences.vibrationActivee,
sonActive: params.sonActive ?? preferences.sonActive,
ledActivee: params.ledActivee ?? preferences.ledActivee,
sonPersonnalise: params.sonPersonnalise ?? preferences.sonPersonnalise,
patternVibrationPersonnalise: params.patternVibrationPersonnalise ?? preferences.patternVibrationPersonnalise,
couleurLEDPersonnalisee: params.couleurLEDPersonnalisee ?? preferences.couleurLEDPersonnalisee,
apercuEcranVerrouillage: params.apercuEcranVerrouillage ?? preferences.apercuEcranVerrouillage,
dureeAffichageSecondes: params.dureeAffichageSecondes ?? preferences.dureeAffichageSecondes,
frequenceRegroupementMinutes: params.frequenceRegroupementMinutes ?? preferences.frequenceRegroupementMinutes,
maxNotificationsSimultanees: params.maxNotificationsSimultanees ?? preferences.maxNotificationsSimultanees,
marquageLectureAutomatique: params.marquageLectureAutomatique ?? preferences.marquageLectureAutomatique,
delaiMarquageLectureSecondes: params.delaiMarquageLectureSecondes ?? preferences.delaiMarquageLectureSecondes,
archivageAutomatique: params.archivageAutomatique ?? preferences.archivageAutomatique,
delaiArchivageHeures: params.delaiArchivageHeures ?? preferences.delaiArchivageHeures,
dureeConservationJours: params.dureeConservationJours ?? preferences.dureeConservationJours,
);
return await repository.mettreAJourPreferences(preferencesModifiees);
},
);
}
}
/// Paramètres pour configurer les préférences avancées
class ConfigurerPreferencesAvanceesParams {
final String utilisateurId;
final bool? vibrationActivee;
final bool? sonActive;
final bool? ledActivee;
final String? sonPersonnalise;
final List<int>? patternVibrationPersonnalise;
final String? couleurLEDPersonnalisee;
final bool? apercuEcranVerrouillage;
final int? dureeAffichageSecondes;
final int? frequenceRegroupementMinutes;
final int? maxNotificationsSimultanees;
final bool? marquageLectureAutomatique;
final int? delaiMarquageLectureSecondes;
final bool? archivageAutomatique;
final int? delaiArchivageHeures;
final int? dureeConservationJours;
const ConfigurerPreferencesAvanceesParams({
required this.utilisateurId,
this.vibrationActivee,
this.sonActive,
this.ledActivee,
this.sonPersonnalise,
this.patternVibrationPersonnalise,
this.couleurLEDPersonnalisee,
this.apercuEcranVerrouillage,
this.dureeAffichageSecondes,
this.frequenceRegroupementMinutes,
this.maxNotificationsSimultanees,
this.marquageLectureAutomatique,
this.delaiMarquageLectureSecondes,
this.archivageAutomatique,
this.delaiArchivageHeures,
this.dureeConservationJours,
});
@override
String toString() {
return 'ConfigurerPreferencesAvanceesParams{utilisateurId: $utilisateurId, vibrationActivee: $vibrationActivee, sonActive: $sonActive, ledActivee: $ledActivee, ...}';
}
}

View File

@@ -1,274 +0,0 @@
import 'package:dartz/dartz.dart';
import '../../../../core/error/failures.dart';
import '../../../../core/usecases/usecase.dart';
import '../entities/notification.dart';
import '../repositories/notifications_repository.dart';
/// Use case pour obtenir les notifications d'un utilisateur
class ObtenirNotificationsUseCase implements UseCase<List<NotificationEntity>, ObtenirNotificationsParams> {
final NotificationsRepository repository;
ObtenirNotificationsUseCase(this.repository);
@override
Future<Either<Failure, List<NotificationEntity>>> call(ObtenirNotificationsParams params) async {
// Vérification du cache en premier
final cacheValide = await repository.isCacheValide(
params.utilisateurId,
maxAgeMinutes: params.maxAgeCacheMinutes,
);
if (!cacheValide || params.forceRefresh) {
// Synchronisation avec le serveur si nécessaire
final syncResult = await repository.synchroniser(
params.utilisateurId,
forceSync: params.forceRefresh,
);
// On continue même si la sync échoue (mode offline)
if (syncResult.isLeft()) {
// Log de l'erreur mais on continue avec les données en cache
print('Erreur de synchronisation: ${syncResult.fold((l) => l.toString(), (r) => '')}');
}
}
// Récupération des notifications
return await repository.obtenirNotifications(
utilisateurId: params.utilisateurId,
includeArchivees: params.includeArchivees,
limite: params.limite,
offset: params.offset,
);
}
}
/// Paramètres pour obtenir les notifications
class ObtenirNotificationsParams {
final String utilisateurId;
final bool includeArchivees;
final int limite;
final int offset;
final bool forceRefresh;
final int maxAgeCacheMinutes;
const ObtenirNotificationsParams({
required this.utilisateurId,
this.includeArchivees = false,
this.limite = 50,
this.offset = 0,
this.forceRefresh = false,
this.maxAgeCacheMinutes = 5,
});
ObtenirNotificationsParams copyWith({
String? utilisateurId,
bool? includeArchivees,
int? limite,
int? offset,
bool? forceRefresh,
int? maxAgeCacheMinutes,
}) {
return ObtenirNotificationsParams(
utilisateurId: utilisateurId ?? this.utilisateurId,
includeArchivees: includeArchivees ?? this.includeArchivees,
limite: limite ?? this.limite,
offset: offset ?? this.offset,
forceRefresh: forceRefresh ?? this.forceRefresh,
maxAgeCacheMinutes: maxAgeCacheMinutes ?? this.maxAgeCacheMinutes,
);
}
@override
String toString() {
return 'ObtenirNotificationsParams{utilisateurId: $utilisateurId, includeArchivees: $includeArchivees, limite: $limite, offset: $offset, forceRefresh: $forceRefresh}';
}
}
/// Use case pour obtenir les notifications non lues
class ObtenirNotificationsNonLuesUseCase implements UseCase<List<NotificationEntity>, String> {
final NotificationsRepository repository;
ObtenirNotificationsNonLuesUseCase(this.repository);
@override
Future<Either<Failure, List<NotificationEntity>>> call(String utilisateurId) async {
return await repository.obtenirNotificationsNonLues(utilisateurId);
}
}
/// Use case pour obtenir le nombre de notifications non lues
class ObtenirNombreNonLuesUseCase implements UseCase<int, String> {
final NotificationsRepository repository;
ObtenirNombreNonLuesUseCase(this.repository);
@override
Future<Either<Failure, int>> call(String utilisateurId) async {
return await repository.obtenirNombreNonLues(utilisateurId);
}
}
/// Use case pour rechercher des notifications
class RechercherNotificationsUseCase implements UseCase<List<NotificationEntity>, RechercherNotificationsParams> {
final NotificationsRepository repository;
RechercherNotificationsUseCase(this.repository);
@override
Future<Either<Failure, List<NotificationEntity>>> call(RechercherNotificationsParams params) async {
return await repository.rechercherNotifications(
utilisateurId: params.utilisateurId,
query: params.query,
types: params.types,
statuts: params.statuts,
dateDebut: params.dateDebut,
dateFin: params.dateFin,
limite: params.limite,
);
}
}
/// Paramètres pour la recherche de notifications
class RechercherNotificationsParams {
final String utilisateurId;
final String? query;
final List<TypeNotification>? types;
final List<StatutNotification>? statuts;
final DateTime? dateDebut;
final DateTime? dateFin;
final int limite;
const RechercherNotificationsParams({
required this.utilisateurId,
this.query,
this.types,
this.statuts,
this.dateDebut,
this.dateFin,
this.limite = 50,
});
RechercherNotificationsParams copyWith({
String? utilisateurId,
String? query,
List<TypeNotification>? types,
List<StatutNotification>? statuts,
DateTime? dateDebut,
DateTime? dateFin,
int? limite,
}) {
return RechercherNotificationsParams(
utilisateurId: utilisateurId ?? this.utilisateurId,
query: query ?? this.query,
types: types ?? this.types,
statuts: statuts ?? this.statuts,
dateDebut: dateDebut ?? this.dateDebut,
dateFin: dateFin ?? this.dateFin,
limite: limite ?? this.limite,
);
}
@override
String toString() {
return 'RechercherNotificationsParams{utilisateurId: $utilisateurId, query: $query, types: $types, statuts: $statuts, dateDebut: $dateDebut, dateFin: $dateFin, limite: $limite}';
}
}
/// Use case pour obtenir les notifications par type
class ObtenirNotificationsParTypeUseCase implements UseCase<List<NotificationEntity>, ObtenirNotificationsParTypeParams> {
final NotificationsRepository repository;
ObtenirNotificationsParTypeUseCase(this.repository);
@override
Future<Either<Failure, List<NotificationEntity>>> call(ObtenirNotificationsParTypeParams params) async {
return await repository.obtenirNotificationsParType(
params.utilisateurId,
params.type,
limite: params.limite,
);
}
}
/// Paramètres pour obtenir les notifications par type
class ObtenirNotificationsParTypeParams {
final String utilisateurId;
final TypeNotification type;
final int limite;
const ObtenirNotificationsParTypeParams({
required this.utilisateurId,
required this.type,
this.limite = 50,
});
ObtenirNotificationsParTypeParams copyWith({
String? utilisateurId,
TypeNotification? type,
int? limite,
}) {
return ObtenirNotificationsParTypeParams(
utilisateurId: utilisateurId ?? this.utilisateurId,
type: type ?? this.type,
limite: limite ?? this.limite,
);
}
@override
String toString() {
return 'ObtenirNotificationsParTypeParams{utilisateurId: $utilisateurId, type: $type, limite: $limite}';
}
}
/// Use case pour obtenir les notifications importantes
class ObtenirNotificationsImportantesUseCase implements UseCase<List<NotificationEntity>, String> {
final NotificationsRepository repository;
ObtenirNotificationsImportantesUseCase(this.repository);
@override
Future<Either<Failure, List<NotificationEntity>>> call(String utilisateurId) async {
return await repository.obtenirNotificationsImportantes(utilisateurId);
}
}
/// Use case pour obtenir les statistiques des notifications
class ObtenirStatistiquesNotificationsUseCase implements UseCase<Map<String, dynamic>, ObtenirStatistiquesParams> {
final NotificationsRepository repository;
ObtenirStatistiquesNotificationsUseCase(this.repository);
@override
Future<Either<Failure, Map<String, dynamic>>> call(ObtenirStatistiquesParams params) async {
return await repository.obtenirStatistiques(
params.utilisateurId,
periode: params.periode,
);
}
}
/// Paramètres pour obtenir les statistiques
class ObtenirStatistiquesParams {
final String utilisateurId;
final int periode;
const ObtenirStatistiquesParams({
required this.utilisateurId,
this.periode = 30,
});
ObtenirStatistiquesParams copyWith({
String? utilisateurId,
int? periode,
}) {
return ObtenirStatistiquesParams(
utilisateurId: utilisateurId ?? this.utilisateurId,
periode: periode ?? this.periode,
);
}
@override
String toString() {
return 'ObtenirStatistiquesParams{utilisateurId: $utilisateurId, periode: $periode}';
}
}

View File

@@ -1,779 +0,0 @@
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<NotificationPreferencesPage> createState() => _NotificationPreferencesPageState();
}
class _NotificationPreferencesPageState extends State<NotificationPreferencesPage>
with TickerProviderStateMixin {
late TabController _tabController;
@override
void initState() {
super.initState();
_tabController = TabController(length: 3, vsync: this);
// Chargement des préférences
context.read<NotificationPreferencesBloc>().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<String>(
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<NotificationPreferencesBloc, NotificationPreferencesState>(
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<NotificationPreferencesBloc>().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<Widget> _buildTypesByCategory(PreferencesNotificationEntity preferences) {
final typesByCategory = <String, List<TypeNotification>>{};
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<NotificationPreferencesBloc>().add(
UpdatePreferencesEvent(preferences: preferences),
);
}
void _toggleNotificationType(TypeNotification type, bool active) {
context.read<NotificationPreferencesBloc>().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<NotificationPreferencesBloc>().add(
const ResetPreferencesEvent(),
);
},
style: TextButton.styleFrom(
foregroundColor: AppColors.error,
),
child: const Text('Réinitialiser'),
),
],
),
);
}
void _sendTestNotification() {
context.read<NotificationPreferencesBloc>().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<NotificationPreferencesBloc>().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'),
),
],
),
);
}
}

View File

@@ -1,539 +0,0 @@
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 '../../../../core/utils/date_formatter.dart';
import '../../domain/entities/notification.dart';
import '../bloc/notifications_bloc.dart';
import '../widgets/notification_card_widget.dart';
import '../widgets/notification_filter_widget.dart';
import '../widgets/notification_search_widget.dart';
import '../widgets/notification_stats_widget.dart';
/// Page principale du centre de notifications
class NotificationsCenterPage extends StatefulWidget {
const NotificationsCenterPage({super.key});
@override
State<NotificationsCenterPage> createState() => _NotificationsCenterPageState();
}
class _NotificationsCenterPageState extends State<NotificationsCenterPage>
with TickerProviderStateMixin {
late TabController _tabController;
final ScrollController _scrollController = ScrollController();
bool _showSearch = false;
String _searchQuery = '';
Set<TypeNotification> _selectedTypes = {};
Set<StatutNotification> _selectedStatuts = {};
@override
void initState() {
super.initState();
_tabController = TabController(length: 4, vsync: this);
_scrollController.addListener(_onScroll);
// Chargement initial des notifications
context.read<NotificationsBloc>().add(
const LoadNotificationsEvent(forceRefresh: false),
);
}
@override
void dispose() {
_tabController.dispose();
_scrollController.dispose();
super.dispose();
}
void _onScroll() {
if (_scrollController.position.pixels >=
_scrollController.position.maxScrollExtent - 200) {
// Chargement de plus de notifications (pagination)
context.read<NotificationsBloc>().add(
const LoadMoreNotificationsEvent(),
);
}
}
void _onRefresh() {
context.read<NotificationsBloc>().add(
const LoadNotificationsEvent(forceRefresh: true),
);
}
void _toggleSearch() {
setState(() {
_showSearch = !_showSearch;
if (!_showSearch) {
_searchQuery = '';
_applyFilters();
}
});
}
void _onSearchChanged(String query) {
setState(() {
_searchQuery = query;
});
_applyFilters();
}
void _onFiltersChanged({
Set<TypeNotification>? types,
Set<StatutNotification>? statuts,
}) {
setState(() {
if (types != null) _selectedTypes = types;
if (statuts != null) _selectedStatuts = statuts;
});
_applyFilters();
}
void _applyFilters() {
context.read<NotificationsBloc>().add(
SearchNotificationsEvent(
query: _searchQuery.isEmpty ? null : _searchQuery,
types: _selectedTypes.isEmpty ? null : _selectedTypes.toList(),
statuts: _selectedStatuts.isEmpty ? null : _selectedStatuts.toList(),
),
);
}
void _markAllAsRead() {
context.read<NotificationsBloc>().add(
const MarkAllAsReadEvent(),
);
}
void _archiveAllRead() {
context.read<NotificationsBloc>().add(
const ArchiveAllReadEvent(),
);
}
@override
Widget build(BuildContext context) {
return UnifiedPageLayout(
title: 'Notifications',
showBackButton: true,
actions: [
IconButton(
icon: Icon(_showSearch ? Icons.search_off : Icons.search),
onPressed: _toggleSearch,
tooltip: _showSearch ? 'Fermer la recherche' : 'Rechercher',
),
PopupMenuButton<String>(
icon: const Icon(Icons.more_vert),
onSelected: (value) {
switch (value) {
case 'mark_all_read':
_markAllAsRead();
break;
case 'archive_all_read':
_archiveAllRead();
break;
case 'preferences':
Navigator.pushNamed(context, '/notifications/preferences');
break;
case 'export':
Navigator.pushNamed(context, '/notifications/export');
break;
}
},
itemBuilder: (context) => [
const PopupMenuItem(
value: 'mark_all_read',
child: ListTile(
leading: Icon(Icons.mark_email_read),
title: Text('Tout marquer comme lu'),
contentPadding: EdgeInsets.zero,
),
),
const PopupMenuItem(
value: 'archive_all_read',
child: ListTile(
leading: Icon(Icons.archive),
title: Text('Archiver tout lu'),
contentPadding: EdgeInsets.zero,
),
),
const PopupMenuDivider(),
const PopupMenuItem(
value: 'preferences',
child: ListTile(
leading: Icon(Icons.settings),
title: Text('Préférences'),
contentPadding: EdgeInsets.zero,
),
),
const PopupMenuItem(
value: 'export',
child: ListTile(
leading: Icon(Icons.download),
title: Text('Exporter'),
contentPadding: EdgeInsets.zero,
),
),
],
),
],
body: Column(
children: [
// Barre de recherche (conditionnelle)
if (_showSearch)
Padding(
padding: const EdgeInsets.all(16.0),
child: NotificationSearchWidget(
onSearchChanged: _onSearchChanged,
onFiltersChanged: _onFiltersChanged,
selectedTypes: _selectedTypes,
selectedStatuts: _selectedStatuts,
),
),
// Statistiques rapides
BlocBuilder<NotificationsBloc, NotificationsState>(
builder: (context, state) {
if (state is NotificationsLoaded) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: NotificationStatsWidget(
totalCount: state.notifications.length,
unreadCount: state.unreadCount,
importantCount: state.notifications
.where((n) => n.estImportante)
.length,
),
);
}
return const SizedBox.shrink();
},
),
const SizedBox(height: 16),
// Onglets de filtrage
Container(
margin: const EdgeInsets.symmetric(horizontal: 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: 'Toutes'),
Tab(text: 'Non lues'),
Tab(text: 'Importantes'),
Tab(text: 'Archivées'),
],
),
),
const SizedBox(height: 16),
// Liste des notifications
Expanded(
child: TabBarView(
controller: _tabController,
children: [
_buildNotificationsList(NotificationFilter.all),
_buildNotificationsList(NotificationFilter.unread),
_buildNotificationsList(NotificationFilter.important),
_buildNotificationsList(NotificationFilter.archived),
],
),
),
],
),
);
}
Widget _buildNotificationsList(NotificationFilter filter) {
return BlocBuilder<NotificationsBloc, NotificationsState>(
builder: (context, state) {
if (state is NotificationsLoading && state.notifications.isEmpty) {
return const Center(
child: CircularProgressIndicator(),
);
}
if (state is NotificationsError && state.notifications.isEmpty) {
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: _onRefresh,
icon: const Icon(Icons.refresh),
label: const Text('Réessayer'),
),
],
),
),
),
);
}
final notifications = _filterNotifications(
state.notifications,
filter,
);
if (notifications.isEmpty) {
return Center(
child: UnifiedCard(
variant: UnifiedCardVariant.outlined,
child: Padding(
padding: const EdgeInsets.all(24.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
_getEmptyIcon(filter),
size: 48,
color: AppColors.onSurface.withOpacity(0.5),
),
const SizedBox(height: 16),
Text(
_getEmptyTitle(filter),
style: AppTextStyles.titleMedium,
),
const SizedBox(height: 8),
Text(
_getEmptyMessage(filter),
style: AppTextStyles.bodyMedium.copyWith(
color: AppColors.onSurface.withOpacity(0.7),
),
textAlign: TextAlign.center,
),
],
),
),
),
);
}
return RefreshIndicator(
onRefresh: () async => _onRefresh(),
child: ListView.builder(
controller: _scrollController,
padding: const EdgeInsets.symmetric(horizontal: 16.0),
itemCount: notifications.length + (state.hasMore ? 1 : 0),
itemBuilder: (context, index) {
if (index >= notifications.length) {
// Indicateur de chargement pour la pagination
return const Padding(
padding: EdgeInsets.all(16.0),
child: Center(
child: CircularProgressIndicator(),
),
);
}
final notification = notifications[index];
return Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: NotificationCardWidget(
notification: notification,
onTap: () => _onNotificationTap(notification),
onMarkAsRead: () => _onMarkAsRead(notification),
onMarkAsImportant: () => _onMarkAsImportant(notification),
onArchive: () => _onArchive(notification),
onDelete: () => _onDelete(notification),
onActionTap: (action) => _onActionTap(notification, action),
),
);
},
),
);
},
);
}
List<NotificationEntity> _filterNotifications(
List<NotificationEntity> notifications,
NotificationFilter filter,
) {
switch (filter) {
case NotificationFilter.all:
return notifications.where((n) => !n.estArchivee).toList();
case NotificationFilter.unread:
return notifications.where((n) => !n.estLue && !n.estArchivee).toList();
case NotificationFilter.important:
return notifications.where((n) => n.estImportante && !n.estArchivee).toList();
case NotificationFilter.archived:
return notifications.where((n) => n.estArchivee).toList();
}
}
IconData _getEmptyIcon(NotificationFilter filter) {
switch (filter) {
case NotificationFilter.all:
return Icons.notifications_none;
case NotificationFilter.unread:
return Icons.mark_email_read;
case NotificationFilter.important:
return Icons.star_border;
case NotificationFilter.archived:
return Icons.archive;
}
}
String _getEmptyTitle(NotificationFilter filter) {
switch (filter) {
case NotificationFilter.all:
return 'Aucune notification';
case NotificationFilter.unread:
return 'Tout est lu !';
case NotificationFilter.important:
return 'Aucune notification importante';
case NotificationFilter.archived:
return 'Aucune notification archivée';
}
}
String _getEmptyMessage(NotificationFilter filter) {
switch (filter) {
case NotificationFilter.all:
return 'Vous n\'avez encore reçu aucune notification.';
case NotificationFilter.unread:
return 'Toutes vos notifications ont été lues.';
case NotificationFilter.important:
return 'Vous n\'avez aucune notification marquée comme importante.';
case NotificationFilter.archived:
return 'Vous n\'avez aucune notification archivée.';
}
}
void _onNotificationTap(NotificationEntity notification) {
// Marquer comme lue si pas encore lue
if (!notification.estLue) {
_onMarkAsRead(notification);
}
// Navigation vers le détail ou action par défaut
if (notification.actionClic != null) {
Navigator.pushNamed(
context,
notification.actionClic!,
arguments: notification.parametresAction,
);
} else {
Navigator.pushNamed(
context,
'/notifications/detail',
arguments: notification.id,
);
}
}
void _onMarkAsRead(NotificationEntity notification) {
context.read<NotificationsBloc>().add(
MarkAsReadEvent(notificationId: notification.id),
);
}
void _onMarkAsImportant(NotificationEntity notification) {
context.read<NotificationsBloc>().add(
MarkAsImportantEvent(
notificationId: notification.id,
important: !notification.estImportante,
),
);
}
void _onArchive(NotificationEntity notification) {
context.read<NotificationsBloc>().add(
ArchiveNotificationEvent(notificationId: notification.id),
);
}
void _onDelete(NotificationEntity 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.pop(context),
child: const Text('Annuler'),
),
TextButton(
onPressed: () {
Navigator.pop(context);
context.read<NotificationsBloc>().add(
DeleteNotificationEvent(notificationId: notification.id),
);
},
style: TextButton.styleFrom(
foregroundColor: AppColors.error,
),
child: const Text('Supprimer'),
),
],
),
);
}
void _onActionTap(NotificationEntity notification, ActionNotification action) {
context.read<NotificationsBloc>().add(
ExecuteQuickActionEvent(
notificationId: notification.id,
actionId: action.id,
parameters: action.parametres,
),
);
}
}
/// Énumération des filtres de notification
enum NotificationFilter {
all,
unread,
important,
archived,
}

View File

@@ -1,430 +0,0 @@
import 'package:flutter/material.dart';
import '../../../../core/widgets/unified_card.dart';
import '../../../../core/theme/app_colors.dart';
import '../../../../core/theme/app_text_styles.dart';
import '../../../../core/utils/date_formatter.dart';
import '../../domain/entities/notification.dart';
/// Widget de carte pour afficher une notification
class NotificationCardWidget extends StatelessWidget {
final NotificationEntity notification;
final VoidCallback? onTap;
final VoidCallback? onMarkAsRead;
final VoidCallback? onMarkAsImportant;
final VoidCallback? onArchive;
final VoidCallback? onDelete;
final Function(ActionNotification)? onActionTap;
const NotificationCardWidget({
super.key,
required this.notification,
this.onTap,
this.onMarkAsRead,
this.onMarkAsImportant,
this.onArchive,
this.onDelete,
this.onActionTap,
});
@override
Widget build(BuildContext context) {
final isUnread = !notification.estLue;
final isImportant = notification.estImportante;
final isExpired = notification.isExpiree;
return UnifiedCard(
variant: isUnread ? UnifiedCardVariant.elevated : UnifiedCardVariant.outlined,
onTap: onTap,
child: Container(
decoration: BoxDecoration(
border: isUnread
? Border.left(
color: _getTypeColor(),
width: 4,
)
: null,
),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// En-tête avec icône, titre et actions
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Icône du type de notification
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: _getTypeColor().withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: Icon(
_getTypeIcon(),
color: _getTypeColor(),
size: 20,
),
),
const SizedBox(width: 12),
// Titre et métadonnées
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Text(
notification.titre,
style: AppTextStyles.titleSmall.copyWith(
fontWeight: isUnread ? FontWeight.w600 : FontWeight.w500,
color: isExpired
? AppColors.onSurface.withOpacity(0.6)
: AppColors.onSurface,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
// Badges de statut
if (isImportant) ...[
const SizedBox(width: 8),
Icon(
Icons.star,
color: AppColors.warning,
size: 16,
),
],
if (isUnread) ...[
const SizedBox(width: 8),
Container(
width: 8,
height: 8,
decoration: BoxDecoration(
color: AppColors.primary,
shape: BoxShape.circle,
),
),
],
],
),
const SizedBox(height: 4),
// Métadonnées (expéditeur, date)
Row(
children: [
if (notification.expediteurNom != null) ...[
Text(
notification.expediteurNom!,
style: AppTextStyles.bodySmall.copyWith(
color: AppColors.onSurface.withOpacity(0.7),
fontWeight: FontWeight.w500,
),
),
Text(
'',
style: AppTextStyles.bodySmall.copyWith(
color: AppColors.onSurface.withOpacity(0.5),
),
),
],
Text(
notification.tempsEcoule,
style: AppTextStyles.bodySmall.copyWith(
color: AppColors.onSurface.withOpacity(0.7),
),
),
if (isExpired) ...[
Text(
' • Expirée',
style: AppTextStyles.bodySmall.copyWith(
color: AppColors.error,
fontWeight: FontWeight.w500,
),
),
],
],
),
],
),
),
// Menu d'actions
PopupMenuButton<String>(
icon: Icon(
Icons.more_vert,
color: AppColors.onSurface.withOpacity(0.6),
size: 20,
),
onSelected: (value) => _handleMenuAction(value),
itemBuilder: (context) => [
if (!notification.estLue)
const PopupMenuItem(
value: 'mark_read',
child: ListTile(
leading: Icon(Icons.mark_email_read, size: 20),
title: Text('Marquer comme lu'),
contentPadding: EdgeInsets.zero,
),
),
PopupMenuItem(
value: 'mark_important',
child: ListTile(
leading: Icon(
notification.estImportante ? Icons.star : Icons.star_border,
size: 20,
),
title: Text(
notification.estImportante
? 'Retirer des importantes'
: 'Marquer comme importante',
),
contentPadding: EdgeInsets.zero,
),
),
if (!notification.estArchivee)
const PopupMenuItem(
value: 'archive',
child: ListTile(
leading: Icon(Icons.archive, size: 20),
title: Text('Archiver'),
contentPadding: EdgeInsets.zero,
),
),
const PopupMenuDivider(),
const PopupMenuItem(
value: 'delete',
child: ListTile(
leading: Icon(Icons.delete, size: 20, color: Colors.red),
title: Text('Supprimer', style: TextStyle(color: Colors.red)),
contentPadding: EdgeInsets.zero,
),
),
],
),
],
),
const SizedBox(height: 12),
// Message de la notification
Text(
notification.messageAffichage,
style: AppTextStyles.bodyMedium.copyWith(
color: isExpired
? AppColors.onSurface.withOpacity(0.6)
: AppColors.onSurface.withOpacity(0.8),
),
maxLines: 3,
overflow: TextOverflow.ellipsis,
),
// Image de la notification (si présente)
if (notification.imageUrl != null) ...[
const SizedBox(height: 12),
ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Image.network(
notification.imageUrl!,
height: 120,
width: double.infinity,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) => Container(
height: 120,
decoration: BoxDecoration(
color: AppColors.surface,
borderRadius: BorderRadius.circular(8),
),
child: Icon(
Icons.image_not_supported,
color: AppColors.onSurface.withOpacity(0.5),
),
),
),
),
],
// Actions rapides
if (notification.hasActionsRapides) ...[
const SizedBox(height: 16),
Wrap(
spacing: 8,
runSpacing: 8,
children: notification.actionsRapidesActives
.take(3) // Limite à 3 actions pour éviter l'encombrement
.map((action) => _buildActionButton(action))
.toList(),
),
],
// Tags (si présents)
if (notification.tags != null && notification.tags!.isNotEmpty) ...[
const SizedBox(height: 12),
Wrap(
spacing: 6,
runSpacing: 6,
children: notification.tags!
.take(3) // Limite à 3 tags
.map((tag) => Container(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 4,
),
decoration: BoxDecoration(
color: AppColors.surfaceVariant,
borderRadius: BorderRadius.circular(12),
),
child: Text(
tag,
style: AppTextStyles.labelSmall.copyWith(
color: AppColors.onSurfaceVariant,
),
),
))
.toList(),
),
],
],
),
),
),
);
}
Widget _buildActionButton(ActionNotification action) {
return OutlinedButton.icon(
onPressed: () => onActionTap?.call(action),
icon: Icon(
_getActionIcon(action.icone),
size: 16,
),
label: Text(
action.libelle,
style: AppTextStyles.labelMedium,
),
style: OutlinedButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
minimumSize: Size.zero,
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
foregroundColor: action.couleur != null
? Color(int.parse(action.couleur!.replaceFirst('#', '0xFF')))
: AppColors.primary,
side: BorderSide(
color: action.couleur != null
? Color(int.parse(action.couleur!.replaceFirst('#', '0xFF')))
: AppColors.primary,
),
),
);
}
Color _getTypeColor() {
try {
return Color(int.parse(notification.couleurType.replaceFirst('#', '0xFF')));
} catch (e) {
return AppColors.primary;
}
}
IconData _getTypeIcon() {
switch (notification.typeNotification.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;
case 'system_update':
return Icons.system_update;
case 'build':
return Icons.build;
case 'schedule':
return Icons.schedule;
case 'event_busy':
return Icons.event_busy;
case 'check_circle':
return Icons.check_circle;
case 'paid':
return Icons.paid;
case 'error':
return Icons.error;
case 'thumb_up':
return Icons.thumb_up;
case 'volunteer_activism':
return Icons.volunteer_activism;
case 'groups':
return Icons.groups;
case 'alternate_email':
return Icons.alternate_email;
default:
return Icons.notifications;
}
}
IconData _getActionIcon(String? iconeName) {
if (iconeName == null) return Icons.touch_app;
switch (iconeName) {
case 'visibility':
return Icons.visibility;
case 'event_available':
return Icons.event_available;
case 'directions':
return Icons.directions;
case 'payment':
return Icons.payment;
case 'schedule':
return Icons.schedule;
case 'receipt':
return Icons.receipt;
case 'person':
return Icons.person;
case 'message':
return Icons.message;
case 'phone':
return Icons.phone;
case 'reply':
return Icons.reply;
default:
return Icons.touch_app;
}
}
void _handleMenuAction(String action) {
switch (action) {
case 'mark_read':
onMarkAsRead?.call();
break;
case 'mark_important':
onMarkAsImportant?.call();
break;
case 'archive':
onArchive?.call();
break;
case 'delete':
onDelete?.call();
break;
}
}
}

View File

@@ -1,389 +0,0 @@
import 'package:flutter/material.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';
/// Widget de recherche et filtrage des notifications
class NotificationSearchWidget extends StatefulWidget {
final Function(String) onSearchChanged;
final Function({
Set<TypeNotification>? types,
Set<StatutNotification>? statuts,
}) onFiltersChanged;
final Set<TypeNotification> selectedTypes;
final Set<StatutNotification> selectedStatuts;
const NotificationSearchWidget({
super.key,
required this.onSearchChanged,
required this.onFiltersChanged,
required this.selectedTypes,
required this.selectedStatuts,
});
@override
State<NotificationSearchWidget> createState() => _NotificationSearchWidgetState();
}
class _NotificationSearchWidgetState extends State<NotificationSearchWidget> {
final TextEditingController _searchController = TextEditingController();
bool _showFilters = false;
@override
void dispose() {
_searchController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return UnifiedCard(
variant: UnifiedCardVariant.outlined,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Barre de recherche
Row(
children: [
Expanded(
child: TextField(
controller: _searchController,
decoration: InputDecoration(
hintText: 'Rechercher dans les notifications...',
hintStyle: AppTextStyles.bodyMedium.copyWith(
color: AppColors.onSurface.withOpacity(0.6),
),
prefixIcon: Icon(
Icons.search,
color: AppColors.onSurface.withOpacity(0.6),
),
suffixIcon: _searchController.text.isNotEmpty
? IconButton(
icon: const Icon(Icons.clear),
onPressed: () {
_searchController.clear();
widget.onSearchChanged('');
},
)
: null,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(
color: AppColors.outline.withOpacity(0.3),
),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(
color: AppColors.outline.withOpacity(0.3),
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(
color: AppColors.primary,
width: 2,
),
),
contentPadding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 12,
),
),
onChanged: widget.onSearchChanged,
),
),
const SizedBox(width: 12),
// Bouton de filtres
IconButton(
onPressed: () {
setState(() {
_showFilters = !_showFilters;
});
},
icon: Icon(
Icons.filter_list,
color: _hasActiveFilters()
? AppColors.primary
: AppColors.onSurface.withOpacity(0.6),
),
style: IconButton.styleFrom(
backgroundColor: _hasActiveFilters()
? AppColors.primary.withOpacity(0.1)
: AppColors.surface,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
side: BorderSide(
color: _hasActiveFilters()
? AppColors.primary
: AppColors.outline.withOpacity(0.3),
),
),
),
),
],
),
// Panneau de filtres (conditionnel)
if (_showFilters) ...[
const SizedBox(height: 16),
_buildFiltersPanel(),
],
],
),
),
);
}
Widget _buildFiltersPanel() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// En-tête des filtres
Row(
children: [
Text(
'Filtres',
style: AppTextStyles.titleSmall.copyWith(
fontWeight: FontWeight.w600,
),
),
const Spacer(),
if (_hasActiveFilters())
TextButton(
onPressed: _clearAllFilters,
child: Text(
'Tout effacer',
style: AppTextStyles.labelMedium.copyWith(
color: AppColors.primary,
),
),
),
],
),
const SizedBox(height: 12),
// Filtres par type
Text(
'Types de notification',
style: AppTextStyles.labelLarge.copyWith(
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 8),
Wrap(
spacing: 8,
runSpacing: 8,
children: _getPopularTypes()
.map((type) => _buildTypeChip(type))
.toList(),
),
const SizedBox(height: 16),
// Filtres par statut
Text(
'Statuts',
style: AppTextStyles.labelLarge.copyWith(
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 8),
Wrap(
spacing: 8,
runSpacing: 8,
children: _getPopularStatuts()
.map((statut) => _buildStatutChip(statut))
.toList(),
),
const SizedBox(height: 16),
// Boutons d'action
Row(
children: [
Expanded(
child: OutlinedButton(
onPressed: () {
setState(() {
_showFilters = false;
});
},
child: const Text('Fermer'),
),
),
const SizedBox(width: 12),
Expanded(
child: ElevatedButton(
onPressed: () {
setState(() {
_showFilters = false;
});
// Les filtres sont déjà appliqués en temps réel
},
child: const Text('Appliquer'),
),
),
],
),
],
);
}
Widget _buildTypeChip(TypeNotification type) {
final isSelected = widget.selectedTypes.contains(type);
return FilterChip(
label: Text(
type.libelle,
style: AppTextStyles.labelMedium.copyWith(
color: isSelected ? AppColors.onPrimary : AppColors.onSurface,
),
),
selected: isSelected,
onSelected: (selected) {
final newTypes = Set<TypeNotification>.from(widget.selectedTypes);
if (selected) {
newTypes.add(type);
} else {
newTypes.remove(type);
}
widget.onFiltersChanged(types: newTypes);
},
selectedColor: AppColors.primary,
backgroundColor: AppColors.surface,
side: BorderSide(
color: isSelected
? AppColors.primary
: AppColors.outline.withOpacity(0.3),
),
avatar: isSelected
? null
: Icon(
_getTypeIcon(type),
size: 16,
color: _getTypeColor(type),
),
);
}
Widget _buildStatutChip(StatutNotification statut) {
final isSelected = widget.selectedStatuts.contains(statut);
return FilterChip(
label: Text(
statut.libelle,
style: AppTextStyles.labelMedium.copyWith(
color: isSelected ? AppColors.onPrimary : AppColors.onSurface,
),
),
selected: isSelected,
onSelected: (selected) {
final newStatuts = Set<StatutNotification>.from(widget.selectedStatuts);
if (selected) {
newStatuts.add(statut);
} else {
newStatuts.remove(statut);
}
widget.onFiltersChanged(statuts: newStatuts);
},
selectedColor: AppColors.primary,
backgroundColor: AppColors.surface,
side: BorderSide(
color: isSelected
? AppColors.primary
: AppColors.outline.withOpacity(0.3),
),
avatar: isSelected
? null
: Container(
width: 8,
height: 8,
decoration: BoxDecoration(
color: _getStatutColor(statut),
shape: BoxShape.circle,
),
),
);
}
List<TypeNotification> _getPopularTypes() {
return [
TypeNotification.nouvelEvenement,
TypeNotification.cotisationDue,
TypeNotification.nouvelleDemandeAide,
TypeNotification.nouveauMembre,
TypeNotification.annonceGenerale,
TypeNotification.messagePrive,
];
}
List<StatutNotification> _getPopularStatuts() {
return [
StatutNotification.nonLue,
StatutNotification.lue,
StatutNotification.marqueeImportante,
StatutNotification.archivee,
];
}
IconData _getTypeIcon(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 '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;
}
}
Color _getStatutColor(StatutNotification statut) {
try {
return Color(int.parse(statut.couleur.replaceFirst('#', '0xFF')));
} catch (e) {
return AppColors.primary;
}
}
bool _hasActiveFilters() {
return widget.selectedTypes.isNotEmpty || widget.selectedStatuts.isNotEmpty;
}
void _clearAllFilters() {
widget.onFiltersChanged(
types: <TypeNotification>{},
statuts: <StatutNotification>{},
);
}
}

View File

@@ -1,400 +0,0 @@
import 'package:flutter/material.dart';
import '../../../../core/widgets/unified_card.dart';
import '../../../../core/theme/app_colors.dart';
import '../../../../core/theme/app_text_styles.dart';
/// Widget d'affichage des statistiques de notifications
class NotificationStatsWidget extends StatelessWidget {
final int totalCount;
final int unreadCount;
final int importantCount;
const NotificationStatsWidget({
super.key,
required this.totalCount,
required this.unreadCount,
required this.importantCount,
});
@override
Widget build(BuildContext context) {
return UnifiedCard(
variant: UnifiedCardVariant.filled,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
children: [
// Statistique principale - Non lues
Expanded(
child: _buildStatItem(
icon: Icons.mark_email_unread,
label: 'Non lues',
value: unreadCount.toString(),
color: unreadCount > 0 ? AppColors.primary : AppColors.onSurface.withOpacity(0.6),
isHighlighted: unreadCount > 0,
),
),
// Séparateur
Container(
width: 1,
height: 40,
color: AppColors.outline.withOpacity(0.2),
),
// Statistique secondaire - Importantes
Expanded(
child: _buildStatItem(
icon: Icons.star,
label: 'Importantes',
value: importantCount.toString(),
color: importantCount > 0 ? AppColors.warning : AppColors.onSurface.withOpacity(0.6),
isHighlighted: importantCount > 0,
),
),
// Séparateur
Container(
width: 1,
height: 40,
color: AppColors.outline.withOpacity(0.2),
),
// Statistique tertiaire - Total
Expanded(
child: _buildStatItem(
icon: Icons.notifications,
label: 'Total',
value: totalCount.toString(),
color: AppColors.onSurface.withOpacity(0.8),
isHighlighted: false,
),
),
],
),
),
);
}
Widget _buildStatItem({
required IconData icon,
required String label,
required String value,
required Color color,
required bool isHighlighted,
}) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
// Icône avec badge si mis en évidence
Stack(
clipBehavior: Clip.none,
children: [
Container(
width: 32,
height: 32,
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: Icon(
icon,
color: color,
size: 18,
),
),
if (isHighlighted && value != '0')
Positioned(
right: -4,
top: -4,
child: Container(
width: 12,
height: 12,
decoration: BoxDecoration(
color: color,
shape: BoxShape.circle,
border: Border.all(
color: AppColors.surface,
width: 2,
),
),
),
),
],
),
const SizedBox(height: 8),
// Valeur
Text(
value,
style: AppTextStyles.titleMedium.copyWith(
color: color,
fontWeight: isHighlighted ? FontWeight.w700 : FontWeight.w600,
),
),
const SizedBox(height: 2),
// Label
Text(
label,
style: AppTextStyles.labelSmall.copyWith(
color: AppColors.onSurface.withOpacity(0.7),
),
textAlign: TextAlign.center,
),
],
);
}
}
/// Widget d'affichage des statistiques détaillées
class DetailedNotificationStatsWidget extends StatelessWidget {
final Map<String, dynamic> stats;
const DetailedNotificationStatsWidget({
super.key,
required this.stats,
});
@override
Widget build(BuildContext context) {
final totalNotifications = stats['total'] ?? 0;
final unreadNotifications = stats['unread'] ?? 0;
final importantNotifications = stats['important'] ?? 0;
final archivedNotifications = stats['archived'] ?? 0;
final todayNotifications = stats['today'] ?? 0;
final weekNotifications = stats['week'] ?? 0;
final engagementRate = stats['engagement_rate'] ?? 0.0;
return UnifiedCard(
variant: UnifiedCardVariant.outlined,
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// En-tête
Row(
children: [
Icon(
Icons.analytics,
color: AppColors.primary,
size: 24,
),
const SizedBox(width: 12),
Text(
'Statistiques détaillées',
style: AppTextStyles.titleMedium.copyWith(
fontWeight: FontWeight.w600,
),
),
],
),
const SizedBox(height: 20),
// Grille de statistiques
GridView.count(
crossAxisCount: 2,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
childAspectRatio: 2.5,
mainAxisSpacing: 16,
crossAxisSpacing: 16,
children: [
_buildDetailedStatCard(
'Total',
totalNotifications.toString(),
Icons.notifications,
AppColors.primary,
),
_buildDetailedStatCard(
'Non lues',
unreadNotifications.toString(),
Icons.mark_email_unread,
AppColors.warning,
),
_buildDetailedStatCard(
'Importantes',
importantNotifications.toString(),
Icons.star,
AppColors.error,
),
_buildDetailedStatCard(
'Archivées',
archivedNotifications.toString(),
Icons.archive,
AppColors.onSurface.withOpacity(0.6),
),
],
),
const SizedBox(height: 20),
// Statistiques temporelles
Row(
children: [
Expanded(
child: _buildTimeStatCard(
'Aujourd\'hui',
todayNotifications.toString(),
Icons.today,
),
),
const SizedBox(width: 16),
Expanded(
child: _buildTimeStatCard(
'Cette semaine',
weekNotifications.toString(),
Icons.date_range,
),
),
],
),
const SizedBox(height: 20),
// Taux d'engagement
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppColors.primaryContainer.withOpacity(0.3),
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: AppColors.primary.withOpacity(0.2),
),
),
child: Row(
children: [
Icon(
Icons.trending_up,
color: AppColors.primary,
size: 20,
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Taux d\'engagement',
style: AppTextStyles.labelMedium.copyWith(
color: AppColors.primary,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 2),
Text(
'Pourcentage de notifications ouvertes',
style: AppTextStyles.bodySmall.copyWith(
color: AppColors.onSurface.withOpacity(0.7),
),
),
],
),
),
Text(
'${engagementRate.toStringAsFixed(1)}%',
style: AppTextStyles.titleMedium.copyWith(
color: AppColors.primary,
fontWeight: FontWeight.w700,
),
),
],
),
),
],
),
),
);
}
Widget _buildDetailedStatCard(
String label,
String value,
IconData icon,
Color color,
) {
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: color.withOpacity(0.05),
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: color.withOpacity(0.2),
),
),
child: Row(
children: [
Icon(
icon,
color: color,
size: 20,
),
const SizedBox(width: 8),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
value,
style: AppTextStyles.titleSmall.copyWith(
color: color,
fontWeight: FontWeight.w700,
),
),
Text(
label,
style: AppTextStyles.labelSmall.copyWith(
color: AppColors.onSurface.withOpacity(0.7),
),
),
],
),
),
],
),
);
}
Widget _buildTimeStatCard(String label, String value, IconData icon) {
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: AppColors.surfaceVariant.withOpacity(0.5),
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: AppColors.outline.withOpacity(0.2),
),
),
child: Column(
children: [
Icon(
icon,
color: AppColors.onSurfaceVariant,
size: 20,
),
const SizedBox(height: 8),
Text(
value,
style: AppTextStyles.titleSmall.copyWith(
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 2),
Text(
label,
style: AppTextStyles.labelSmall.copyWith(
color: AppColors.onSurface.withOpacity(0.7),
),
textAlign: TextAlign.center,
),
],
),
);
}
}