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:
@@ -1,52 +1,300 @@
|
||||
/// Exception de base pour toutes les exceptions serveur.
|
||||
///
|
||||
/// Cette exception est levée lorsque le serveur retourne une erreur
|
||||
/// ou lorsqu'une communication avec le serveur échoue.
|
||||
///
|
||||
/// **Usage:**
|
||||
/// ```dart
|
||||
/// if (response.statusCode >= 400) {
|
||||
/// throw ServerException(
|
||||
/// 'Erreur serveur: ${response.statusCode}',
|
||||
/// statusCode: response.statusCode,
|
||||
/// );
|
||||
/// }
|
||||
/// ```
|
||||
class ServerException implements Exception {
|
||||
/// Crée une nouvelle [ServerException].
|
||||
///
|
||||
/// [message] Message décrivant l'erreur
|
||||
/// [statusCode] Code de statut HTTP optionnel
|
||||
/// [originalError] L'erreur originale si disponible
|
||||
const ServerException(
|
||||
this.message, {
|
||||
this.statusCode,
|
||||
this.originalError,
|
||||
});
|
||||
|
||||
/// Message décrivant l'erreur
|
||||
final String message;
|
||||
|
||||
ServerException([this.message = 'Une erreur serveur est survenue']);
|
||||
/// Code de statut HTTP (404, 500, etc.)
|
||||
final int? statusCode;
|
||||
|
||||
/// L'erreur originale qui a causé cette exception
|
||||
final Object? originalError;
|
||||
|
||||
@override
|
||||
String toString() => 'ServerException: $message';
|
||||
String toString() {
|
||||
final buffer = StringBuffer('ServerException: $message');
|
||||
if (statusCode != null) {
|
||||
buffer.write(' (Status: $statusCode)');
|
||||
}
|
||||
if (originalError != null) {
|
||||
buffer.write(' (Original: $originalError)');
|
||||
}
|
||||
return buffer.toString();
|
||||
}
|
||||
}
|
||||
|
||||
class CacheException implements Exception {}
|
||||
/// Exception liée au cache local.
|
||||
///
|
||||
/// Cette exception est levée lorsque :
|
||||
/// - Les données ne peuvent pas être lues depuis le cache
|
||||
/// - Les données ne peuvent pas être écrites dans le cache
|
||||
/// - Le cache est corrompu ou inaccessible
|
||||
///
|
||||
/// **Usage:**
|
||||
/// ```dart
|
||||
/// try {
|
||||
/// await cache.write(key, value);
|
||||
/// } catch (e) {
|
||||
/// throw CacheException('Impossible d\'écrire dans le cache', e);
|
||||
/// }
|
||||
/// ```
|
||||
class CacheException implements Exception {
|
||||
/// Crée une nouvelle [CacheException].
|
||||
///
|
||||
/// [message] Message décrivant l'erreur
|
||||
/// [originalError] L'erreur originale si disponible
|
||||
const CacheException([
|
||||
this.message = 'Erreur de cache',
|
||||
this.originalError,
|
||||
]);
|
||||
|
||||
/// Message décrivant l'erreur
|
||||
final String message;
|
||||
|
||||
/// L'erreur originale qui a causé cette exception
|
||||
final Object? originalError;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
if (originalError != null) {
|
||||
return 'CacheException: $message (Original: $originalError)';
|
||||
}
|
||||
return 'CacheException: $message';
|
||||
}
|
||||
}
|
||||
|
||||
/// Exception liée à l'authentification.
|
||||
///
|
||||
/// Cette exception est levée lorsque :
|
||||
/// - Les identifiants sont incorrects
|
||||
/// - Le token d'authentification est expiré
|
||||
/// - L'utilisateur n'est pas autorisé
|
||||
///
|
||||
/// **Usage:**
|
||||
/// ```dart
|
||||
/// if (!isValidCredentials(email, password)) {
|
||||
/// throw AuthenticationException('Identifiants incorrects');
|
||||
/// }
|
||||
/// ```
|
||||
class AuthenticationException implements Exception {
|
||||
/// Crée une nouvelle [AuthenticationException].
|
||||
///
|
||||
/// [message] Message décrivant l'erreur d'authentification
|
||||
/// [code] Code d'erreur optionnel
|
||||
const AuthenticationException(
|
||||
this.message, {
|
||||
this.code,
|
||||
});
|
||||
|
||||
/// Message décrivant l'erreur
|
||||
final String message;
|
||||
|
||||
AuthenticationException(this.message);
|
||||
/// Code d'erreur optionnel
|
||||
final String? code;
|
||||
|
||||
@override
|
||||
String toString() => 'AuthenticationException: $message';
|
||||
String toString() {
|
||||
if (code != null) {
|
||||
return 'AuthenticationException: $message (Code: $code)';
|
||||
}
|
||||
return 'AuthenticationException: $message';
|
||||
}
|
||||
}
|
||||
|
||||
/// Exception serveur avec message personnalisé.
|
||||
///
|
||||
/// **Note:** Cette classe est dépréciée. Utilisez [ServerException] à la place.
|
||||
///
|
||||
/// **Usage déprécié:**
|
||||
/// ```dart
|
||||
/// throw ServerExceptionWithMessage('Erreur personnalisée');
|
||||
/// ```
|
||||
@Deprecated('Utilisez ServerException à la place')
|
||||
class ServerExceptionWithMessage implements Exception {
|
||||
final String message;
|
||||
/// Crée une nouvelle [ServerExceptionWithMessage].
|
||||
///
|
||||
/// [message] Message décrivant l'erreur
|
||||
const ServerExceptionWithMessage(this.message);
|
||||
|
||||
ServerExceptionWithMessage(this.message);
|
||||
/// Message décrivant l'erreur
|
||||
final String message;
|
||||
|
||||
@override
|
||||
String toString() => 'ServerException: $message';
|
||||
}
|
||||
|
||||
/// Exception levée lorsque l'utilisateur n'est pas trouvé.
|
||||
///
|
||||
/// Cette exception est levée lorsque :
|
||||
/// - L'utilisateur avec l'ID donné n'existe pas
|
||||
/// - L'utilisateur a été supprimé
|
||||
/// - L'ID utilisateur est invalide
|
||||
///
|
||||
/// **Usage:**
|
||||
/// ```dart
|
||||
/// final user = await repository.getUserById(userId);
|
||||
/// if (user == null) {
|
||||
/// throw UserNotFoundException('Utilisateur avec ID $userId non trouvé');
|
||||
/// }
|
||||
/// ```
|
||||
class UserNotFoundException implements Exception {
|
||||
/// Crée une nouvelle [UserNotFoundException].
|
||||
///
|
||||
/// [message] Message décrivant l'erreur
|
||||
/// [userId] L'ID de l'utilisateur non trouvé
|
||||
const UserNotFoundException([
|
||||
this.message = 'Utilisateur non trouvé',
|
||||
this.userId,
|
||||
]);
|
||||
|
||||
/// Message décrivant l'erreur
|
||||
final String message;
|
||||
UserNotFoundException([this.message = "User not found"]);
|
||||
|
||||
/// L'ID de l'utilisateur non trouvé
|
||||
final String? userId;
|
||||
|
||||
@override
|
||||
String toString() => "UserNotFoundException: $message";
|
||||
String toString() {
|
||||
if (userId != null) {
|
||||
return 'UserNotFoundException: $message (UserId: $userId)';
|
||||
}
|
||||
return 'UserNotFoundException: $message';
|
||||
}
|
||||
}
|
||||
|
||||
/// Exception levée en cas de conflit de données.
|
||||
///
|
||||
/// Cette exception est levée lorsque :
|
||||
/// - Une ressource existe déjà (ex: email déjà utilisé)
|
||||
/// - Une opération entre en conflit avec l'état actuel
|
||||
/// - Une contrainte d'unicité est violée
|
||||
///
|
||||
/// **Usage:**
|
||||
/// ```dart
|
||||
/// if (await userExists(email)) {
|
||||
/// throw ConflictException('Un utilisateur avec cet email existe déjà');
|
||||
/// }
|
||||
/// ```
|
||||
class ConflictException implements Exception {
|
||||
/// Crée une nouvelle [ConflictException].
|
||||
///
|
||||
/// [message] Message décrivant le conflit
|
||||
/// [resource] La ressource en conflit
|
||||
const ConflictException([
|
||||
this.message = 'Conflit détecté',
|
||||
this.resource,
|
||||
]);
|
||||
|
||||
/// Message décrivant le conflit
|
||||
final String message;
|
||||
ConflictException([this.message = "Conflict"]);
|
||||
|
||||
/// La ressource en conflit
|
||||
final String? resource;
|
||||
|
||||
@override
|
||||
String toString() => "ConflictException: $message";
|
||||
String toString() {
|
||||
if (resource != null) {
|
||||
return 'ConflictException: $message (Resource: $resource)';
|
||||
}
|
||||
return 'ConflictException: $message';
|
||||
}
|
||||
}
|
||||
|
||||
/// Exception levée lorsque l'utilisateur n'est pas autorisé.
|
||||
///
|
||||
/// Cette exception est levée lorsque :
|
||||
/// - Le token d'authentification est invalide ou expiré
|
||||
/// - L'utilisateur n'a pas les permissions nécessaires
|
||||
/// - La session a expiré
|
||||
///
|
||||
/// **Usage:**
|
||||
/// ```dart
|
||||
/// if (!hasPermission(user, Permission.admin)) {
|
||||
/// throw UnauthorizedException('Accès non autorisé');
|
||||
/// }
|
||||
/// ```
|
||||
class UnauthorizedException implements Exception {
|
||||
/// Crée une nouvelle [UnauthorizedException].
|
||||
///
|
||||
/// [message] Message décrivant l'erreur
|
||||
/// [reason] Raison de la non-autorisation
|
||||
const UnauthorizedException([
|
||||
this.message = 'Non autorisé',
|
||||
this.reason,
|
||||
]);
|
||||
|
||||
/// Message décrivant l'erreur
|
||||
final String message;
|
||||
UnauthorizedException([this.message = "Unauthorized"]);
|
||||
|
||||
/// Raison de la non-autorisation
|
||||
final String? reason;
|
||||
|
||||
@override
|
||||
String toString() => "UnauthorizedException: $message";
|
||||
String toString() {
|
||||
if (reason != null) {
|
||||
return 'UnauthorizedException: $message (Reason: $reason)';
|
||||
}
|
||||
return 'UnauthorizedException: $message';
|
||||
}
|
||||
}
|
||||
|
||||
/// Exception levée lorsque la validation échoue.
|
||||
///
|
||||
/// Cette exception est levée lorsque :
|
||||
/// - Les données ne respectent pas les contraintes
|
||||
/// - Les données sont manquantes ou invalides
|
||||
/// - Les données ne passent pas la validation métier
|
||||
///
|
||||
/// **Usage:**
|
||||
/// ```dart
|
||||
/// if (email.isEmpty || !isValidEmail(email)) {
|
||||
/// throw ValidationException('Email invalide', field: 'email');
|
||||
/// }
|
||||
/// ```
|
||||
class ValidationException implements Exception {
|
||||
/// Crée une nouvelle [ValidationException].
|
||||
///
|
||||
/// [message] Message décrivant l'erreur de validation
|
||||
/// [field] Le champ qui a échoué la validation
|
||||
const ValidationException(
|
||||
this.message, {
|
||||
this.field,
|
||||
});
|
||||
|
||||
/// Message décrivant l'erreur
|
||||
final String message;
|
||||
|
||||
/// Le champ qui a échoué la validation
|
||||
final String? field;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
if (field != null) {
|
||||
return 'ValidationException: $message (Field: $field)';
|
||||
}
|
||||
return 'ValidationException: $message';
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,218 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
/// Classe de base abstraite pour toutes les erreurs de l'application.
|
||||
///
|
||||
/// Les [Failure] représentent des erreurs métier qui peuvent être gérées
|
||||
/// de manière élégante par l'application, contrairement aux [Exception]
|
||||
/// qui sont des erreurs techniques.
|
||||
///
|
||||
/// **Usage:**
|
||||
/// ```dart
|
||||
/// try {
|
||||
/// final result = await repository.getData();
|
||||
/// return Right(result);
|
||||
/// } catch (e) {
|
||||
/// return Left(ServerFailure(message: e.toString()));
|
||||
/// }
|
||||
/// ```
|
||||
abstract class Failure extends Equatable {
|
||||
/// Crée une nouvelle [Failure].
|
||||
///
|
||||
/// [message] Un message optionnel décrivant l'erreur
|
||||
/// [code] Un code d'erreur optionnel
|
||||
const Failure({
|
||||
this.message = 'Une erreur est survenue',
|
||||
this.code,
|
||||
});
|
||||
|
||||
/// Message décrivant l'erreur
|
||||
final String message;
|
||||
|
||||
/// Code d'erreur optionnel
|
||||
final String? code;
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
List<Object?> get props => [message, code];
|
||||
|
||||
@override
|
||||
String toString() => 'Failure(message: $message, code: $code)';
|
||||
}
|
||||
|
||||
class ServerFailure extends Failure {}
|
||||
class CacheFailure extends Failure {}
|
||||
/// Erreur liée au serveur ou à la communication réseau.
|
||||
///
|
||||
/// Cette erreur est levée lorsque :
|
||||
/// - Une requête HTTP échoue
|
||||
/// - Le serveur retourne une erreur
|
||||
/// - La connexion réseau est perdue
|
||||
/// - Un timeout se produit
|
||||
///
|
||||
/// **Exemple:**
|
||||
/// ```dart
|
||||
/// try {
|
||||
/// final response = await http.get(url);
|
||||
/// if (response.statusCode != 200) {
|
||||
/// throw ServerFailure(
|
||||
/// message: 'Erreur serveur: ${response.statusCode}',
|
||||
/// code: response.statusCode.toString(),
|
||||
/// );
|
||||
/// }
|
||||
/// } catch (e) {
|
||||
/// throw ServerFailure(message: e.toString());
|
||||
/// }
|
||||
/// ```
|
||||
class ServerFailure extends Failure {
|
||||
/// Crée une nouvelle [ServerFailure].
|
||||
///
|
||||
/// [message] Message décrivant l'erreur serveur
|
||||
/// [code] Code d'erreur HTTP optionnel
|
||||
/// [statusCode] Code de statut HTTP optionnel
|
||||
const ServerFailure({
|
||||
super.message = 'Erreur serveur',
|
||||
super.code,
|
||||
this.statusCode,
|
||||
});
|
||||
|
||||
/// Code de statut HTTP (404, 500, etc.)
|
||||
final int? statusCode;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [...super.props, statusCode];
|
||||
|
||||
@override
|
||||
String toString() =>
|
||||
'ServerFailure(message: $message, code: $code, statusCode: $statusCode)';
|
||||
}
|
||||
|
||||
/// Erreur liée au cache local.
|
||||
///
|
||||
/// Cette erreur est levée lorsque :
|
||||
/// - Les données ne peuvent pas être lues depuis le cache
|
||||
/// - Les données ne peuvent pas être écrites dans le cache
|
||||
/// - Le cache est corrompu ou inaccessible
|
||||
///
|
||||
/// **Exemple:**
|
||||
/// ```dart
|
||||
/// try {
|
||||
/// final cachedData = await cache.get(key);
|
||||
/// if (cachedData == null) {
|
||||
/// throw CacheFailure(message: 'Données non trouvées dans le cache');
|
||||
/// }
|
||||
/// } catch (e) {
|
||||
/// throw CacheFailure(message: e.toString());
|
||||
/// }
|
||||
/// ```
|
||||
class CacheFailure extends Failure {
|
||||
/// Crée une nouvelle [CacheFailure].
|
||||
///
|
||||
/// [message] Message décrivant l'erreur de cache
|
||||
/// [code] Code d'erreur optionnel
|
||||
const CacheFailure({
|
||||
super.message = 'Erreur de cache',
|
||||
super.code,
|
||||
});
|
||||
|
||||
@override
|
||||
String toString() => 'CacheFailure(message: $message, code: $code)';
|
||||
}
|
||||
|
||||
/// Erreur liée à l'authentification.
|
||||
///
|
||||
/// Cette erreur est levée lorsque :
|
||||
/// - Les identifiants sont incorrects
|
||||
/// - Le token d'authentification est expiré
|
||||
/// - L'utilisateur n'est pas autorisé
|
||||
///
|
||||
/// **Exemple:**
|
||||
/// ```dart
|
||||
/// if (!isAuthenticated) {
|
||||
/// throw AuthenticationFailure(
|
||||
/// message: 'Authentification requise',
|
||||
/// );
|
||||
/// }
|
||||
/// ```
|
||||
class AuthenticationFailure extends Failure {
|
||||
/// Crée une nouvelle [AuthenticationFailure].
|
||||
///
|
||||
/// [message] Message décrivant l'erreur d'authentification
|
||||
/// [code] Code d'erreur optionnel
|
||||
const AuthenticationFailure({
|
||||
super.message = 'Erreur d\'authentification',
|
||||
super.code,
|
||||
});
|
||||
|
||||
@override
|
||||
String toString() =>
|
||||
'AuthenticationFailure(message: $message, code: $code)';
|
||||
}
|
||||
|
||||
/// Erreur liée à la validation des données.
|
||||
///
|
||||
/// Cette erreur est levée lorsque :
|
||||
/// - Les données saisies sont invalides
|
||||
/// - Les données ne respectent pas les contraintes
|
||||
/// - Les données sont manquantes
|
||||
///
|
||||
/// **Exemple:**
|
||||
/// ```dart
|
||||
/// if (email.isEmpty || !isValidEmail(email)) {
|
||||
/// throw ValidationFailure(
|
||||
/// message: 'Email invalide',
|
||||
/// field: 'email',
|
||||
/// );
|
||||
/// }
|
||||
/// ```
|
||||
class ValidationFailure extends Failure {
|
||||
/// Crée une nouvelle [ValidationFailure].
|
||||
///
|
||||
/// [message] Message décrivant l'erreur de validation
|
||||
/// [field] Le champ qui a échoué la validation
|
||||
/// [code] Code d'erreur optionnel
|
||||
const ValidationFailure({
|
||||
super.message = 'Erreur de validation',
|
||||
this.field,
|
||||
super.code,
|
||||
});
|
||||
|
||||
/// Le champ qui a échoué la validation
|
||||
final String? field;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [super.props, field];
|
||||
|
||||
@override
|
||||
String toString() =>
|
||||
'ValidationFailure(message: $message, field: $field, code: $code)';
|
||||
}
|
||||
|
||||
/// Erreur liée à une opération réseau.
|
||||
///
|
||||
/// Cette erreur est levée lorsque :
|
||||
/// - La connexion Internet est perdue
|
||||
/// - Le timeout est dépassé
|
||||
/// - La connexion est refusée
|
||||
///
|
||||
/// **Exemple:**
|
||||
/// ```dart
|
||||
/// try {
|
||||
/// final response = await http.get(url).timeout(
|
||||
/// const Duration(seconds: 5),
|
||||
/// );
|
||||
/// } on TimeoutException {
|
||||
/// throw NetworkFailure(message: 'Timeout de connexion');
|
||||
/// } on SocketException {
|
||||
/// throw NetworkFailure(message: 'Pas de connexion Internet');
|
||||
/// }
|
||||
/// ```
|
||||
class NetworkFailure extends Failure {
|
||||
/// Crée une nouvelle [NetworkFailure].
|
||||
///
|
||||
/// [message] Message décrivant l'erreur réseau
|
||||
/// [code] Code d'erreur optionnel
|
||||
const NetworkFailure({
|
||||
super.message = 'Erreur réseau',
|
||||
super.code,
|
||||
});
|
||||
|
||||
@override
|
||||
String toString() => 'NetworkFailure(message: $message, code: $code)';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user