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

@@ -0,0 +1,148 @@
import '../../core/constants/env_config.dart';
import '../../core/utils/app_logger.dart';
import '../../domain/entities/story.dart';
/// Modèle de données pour les stories (Data Transfer Object).
///
/// Cette classe est responsable de la sérialisation/désérialisation
/// avec l'API backend et convertit vers/depuis l'entité de domaine [Story].
class StoryModel extends Story {
/// Crée une nouvelle instance de [StoryModel].
const StoryModel({
required super.id,
required super.userId,
required super.userFirstName,
required super.userLastName,
required super.userProfileImageUrl,
required super.userIsVerified,
required super.mediaType,
required super.mediaUrl,
required super.createdAt,
required super.expiresAt,
super.thumbnailUrl,
super.durationSeconds,
super.isActive,
super.viewsCount,
super.hasViewed,
});
/// Crée un [StoryModel] à partir d'un JSON reçu depuis l'API.
factory StoryModel.fromJson(Map<String, dynamic> json) {
try {
return StoryModel(
id: json['id']?.toString() ?? '',
userId: json['userId']?.toString() ?? '',
userFirstName: json['userFirstName']?.toString() ?? '',
userLastName: json['userLastName']?.toString() ?? '',
userProfileImageUrl: json['userProfileImageUrl']?.toString() ?? '',
userIsVerified: json['userIsVerified'] as bool? ?? false,
mediaType: _parseMediaType(json['mediaType']),
mediaUrl: json['mediaUrl']?.toString() ?? '',
thumbnailUrl: json['thumbnailUrl']?.toString(),
durationSeconds: json['durationSeconds'] as int?,
createdAt: _parseDateTime(json['createdAt']),
expiresAt: _parseDateTime(json['expiresAt']),
isActive: json['isActive'] as bool? ?? true,
viewsCount: json['viewsCount'] as int? ?? 0,
hasViewed: json['hasViewed'] as bool? ?? false,
);
} catch (e, stackTrace) {
AppLogger.e('Erreur lors du parsing JSON', error: e, stackTrace: stackTrace, tag: 'StoryModel');
AppLogger.d('JSON reçu: $json', tag: 'StoryModel');
rethrow;
}
}
/// Convertit le modèle en JSON pour l'envoi à l'API.
Map<String, dynamic> toJson() {
return {
'id': id,
'userId': userId,
'userFirstName': userFirstName,
'userLastName': userLastName,
'userProfileImageUrl': userProfileImageUrl,
'userIsVerified': userIsVerified,
'mediaType': _mediaTypeToString(mediaType),
'mediaUrl': mediaUrl,
'thumbnailUrl': thumbnailUrl,
'durationSeconds': durationSeconds,
'createdAt': createdAt.toIso8601String(),
'expiresAt': expiresAt.toIso8601String(),
'isActive': isActive,
'viewsCount': viewsCount,
'hasViewed': hasViewed,
};
}
/// Convertit le modèle en entité de domaine.
Story toEntity() {
return Story(
id: id,
userId: userId,
userFirstName: userFirstName,
userLastName: userLastName,
userProfileImageUrl: userProfileImageUrl,
userIsVerified: userIsVerified,
mediaType: mediaType,
mediaUrl: mediaUrl,
thumbnailUrl: thumbnailUrl,
durationSeconds: durationSeconds,
createdAt: createdAt,
expiresAt: expiresAt,
isActive: isActive,
viewsCount: viewsCount,
hasViewed: hasViewed,
);
}
/// Parse le type de média depuis une string.
static StoryMediaType _parseMediaType(dynamic value) {
if (value == null) return StoryMediaType.image;
final stringValue = value.toString().toUpperCase();
switch (stringValue) {
case 'IMAGE':
return StoryMediaType.image;
case 'VIDEO':
return StoryMediaType.video;
default:
AppLogger.w('Type de média inconnu: $value, utilisation de IMAGE par défaut', tag: 'StoryModel');
return StoryMediaType.image;
}
}
/// Convertit le type de média en string pour l'API.
static String _mediaTypeToString(StoryMediaType type) {
switch (type) {
case StoryMediaType.image:
return 'IMAGE';
case StoryMediaType.video:
return 'VIDEO';
}
}
/// Parse une DateTime depuis différents formats possibles.
static DateTime _parseDateTime(dynamic value) {
if (value == null) return DateTime.now();
try {
// Si c'est déjà une DateTime
if (value is DateTime) return value;
// Si c'est une string ISO 8601
if (value is String) {
return DateTime.parse(value);
}
// Si c'est un timestamp en millisecondes
if (value is int) {
return DateTime.fromMillisecondsSinceEpoch(value);
}
AppLogger.w('Format de date non reconnu: $value', tag: 'StoryModel');
return DateTime.now();
} catch (e, stackTrace) {
AppLogger.e('Erreur parsing DateTime', error: e, stackTrace: stackTrace, tag: 'StoryModel');
return DateTime.now();
}
}
}