fix(chat): Correction race condition + Implémentation TODOs

## Corrections Critiques

### Race Condition - Statuts de Messages
- Fix : Les icônes de statut (✓, ✓✓, ✓✓ bleu) ne s'affichaient pas
- Cause : WebSocket delivery confirmations arrivaient avant messages locaux
- Solution : Pattern Optimistic UI dans chat_bloc.dart
  - Création message temporaire immédiate
  - Ajout à la liste AVANT requête HTTP
  - Remplacement par message serveur à la réponse
- Fichier : lib/presentation/state_management/chat_bloc.dart

## Implémentation TODOs (13/21)

### Social (social_header_widget.dart)
-  Copier lien du post dans presse-papiers
-  Partage natif via Share.share()
-  Dialogue de signalement avec 5 raisons

### Partage (share_post_dialog.dart)
-  Interface sélection d'amis avec checkboxes
-  Partage externe via Share API

### Média (media_upload_service.dart)
-  Parsing JSON réponse backend
-  Méthode deleteMedia() pour suppression
-  Génération miniature vidéo

### Posts (create_post_dialog.dart, edit_post_dialog.dart)
-  Extraction URL depuis uploads
-  Documentation chargement médias

### Chat (conversations_screen.dart)
-  Navigation vers notifications
-  ConversationSearchDelegate pour recherche

## Nouveaux Fichiers

### Configuration
- build-prod.ps1 : Script build production avec dart-define
- lib/core/constants/env_config.dart : Gestion environnements

### Documentation
- TODOS_IMPLEMENTED.md : Documentation complète TODOs

## Améliorations

### Architecture
- Refactoring injection de dépendances
- Amélioration routing et navigation
- Optimisation providers (UserProvider, FriendsProvider)

### UI/UX
- Amélioration thème et couleurs
- Optimisation animations
- Meilleure gestion erreurs

### Services
- Configuration API avec env_config
- Amélioration datasources (events, users)
- Optimisation modèles de données
This commit is contained in:
dahoud
2026-01-10 10:43:17 +00:00
parent 06031b01f2
commit 92612abbd7
321 changed files with 43137 additions and 4285 deletions

View File

@@ -1,68 +1,168 @@
class EventModel {
final String eventId;
import 'package:equatable/equatable.dart';
/// Entité de domaine représentant un événement.
///
/// Cette classe est indépendante de toute infrastructure (API, BDD, etc.)
/// et représente la logique métier pure selon Clean Architecture.
class Event extends Equatable {
const Event({
required this.id,
required this.title,
required this.description,
required this.startDate,
required this.location,
required this.category,
required this.creatorEmail, required this.creatorFirstName, required this.creatorLastName, required this.creatorProfileImageUrl, this.link,
this.imageUrl,
this.participantIds = const [],
this.status = EventStatus.open,
this.reactionsCount = 0,
this.commentsCount = 0,
this.sharesCount = 0,
});
final String id;
final String title;
final String description;
final String eventDate;
final DateTime startDate;
final String location;
final String category;
final String? link;
final String? imageUrl;
final String creatorId;
final String status;
// Informations sur le créateur
final String creatorEmail;
final String creatorFirstName;
final String creatorLastName;
final String creatorProfileImageUrl;
// Participants et interactions
final List<String> participantIds;
final EventStatus status;
final int reactionsCount;
final int commentsCount;
final int sharesCount;
EventModel({
required this.eventId,
required this.title,
required this.description,
required this.eventDate,
required this.location,
required this.category,
this.link,
this.imageUrl,
required this.creatorId,
required this.status,
});
// Méthode pour créer un EventModel à partir d'un JSON
factory EventModel.fromJson(Map<String, dynamic> json) {
return EventModel(
eventId: json['id'],
title: json['title'],
description: json['description'],
eventDate: json['event_date'],
location: json['location'],
category: json['category'],
link: json['link'],
imageUrl: json['imageUrl'],
creatorId: json['creator']['id'], // Assurez-vous que le JSON a ce format
status: json['status'],
/// Crée une copie de l'événement avec les champs modifiés
Event copyWith({
String? id,
String? title,
String? description,
DateTime? startDate,
String? location,
String? category,
String? link,
String? imageUrl,
String? creatorEmail,
String? creatorFirstName,
String? creatorLastName,
String? creatorProfileImageUrl,
List<String>? participantIds,
EventStatus? status,
int? reactionsCount,
int? commentsCount,
int? sharesCount,
}) {
return Event(
id: id ?? this.id,
title: title ?? this.title,
description: description ?? this.description,
startDate: startDate ?? this.startDate,
location: location ?? this.location,
category: category ?? this.category,
link: link ?? this.link,
imageUrl: imageUrl ?? this.imageUrl,
creatorEmail: creatorEmail ?? this.creatorEmail,
creatorFirstName: creatorFirstName ?? this.creatorFirstName,
creatorLastName: creatorLastName ?? this.creatorLastName,
creatorProfileImageUrl: creatorProfileImageUrl ?? this.creatorProfileImageUrl,
participantIds: participantIds ?? this.participantIds,
status: status ?? this.status,
reactionsCount: reactionsCount ?? this.reactionsCount,
commentsCount: commentsCount ?? this.commentsCount,
sharesCount: sharesCount ?? this.sharesCount,
);
}
// Méthode pour convertir un EventModel en JSON
Map<String, dynamic> toJson() {
return {
'id': eventId,
'title': title,
'description': description,
'event_date': eventDate,
'location': location,
'category': category,
'link': link,
'imageUrl': imageUrl,
'creator': {'id': creatorId}, // Structure du JSON pour l'API
'status': status,
};
/// Retourne le nom complet du créateur
String get creatorFullName => '$creatorFirstName $creatorLastName';
/// Retourne le nombre total de participants
int get participantsCount => participantIds.length;
/// Vérifie si l'événement est ouvert aux participations
bool get isOpen => status == EventStatus.open;
/// Vérifie si l'événement est fermé
bool get isClosed => status == EventStatus.closed;
/// Vérifie si l'événement est annulé
bool get isCancelled => status == EventStatus.cancelled;
@override
List<Object?> get props => [
id,
title,
description,
startDate,
location,
category,
link,
imageUrl,
creatorEmail,
creatorFirstName,
creatorLastName,
creatorProfileImageUrl,
participantIds,
status,
reactionsCount,
commentsCount,
sharesCount,
];
}
/// Énumération des statuts possibles d'un événement
enum EventStatus {
open,
closed,
cancelled,
completed;
/// Convertit une chaîne en EventStatus
static EventStatus fromString(String status) {
switch (status.toLowerCase()) {
case 'ouvert':
case 'open':
return EventStatus.open;
case 'fermé':
case 'ferme':
case 'closed':
return EventStatus.closed;
case 'annulé':
case 'annule':
case 'cancelled':
return EventStatus.cancelled;
case 'terminé':
case 'termine':
case 'completed':
return EventStatus.completed;
default:
return EventStatus.open;
}
}
// Convertir une liste d'EventModel à partir d'une liste JSON
static List<EventModel> fromJsonList(List<dynamic> jsonList) {
return jsonList.map((json) => EventModel.fromJson(json)).toList();
}
// Convertir une liste d'EventModel en JSON
static List<Map<String, dynamic>> toJsonList(List<EventModel> events) {
return events.map((event) => event.toJson()).toList();
/// Convertit EventStatus en chaîne pour l'API
String toApiString() {
switch (this) {
case EventStatus.open:
return 'ouvert';
case EventStatus.closed:
return 'fermé';
case EventStatus.cancelled:
return 'annulé';
case EventStatus.completed:
return 'terminé';
}
}
}