Files
afterwork/lib/presentation/widgets/social/README.md
dahoud 92612abbd7 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
2026-01-10 10:43:17 +00:00

16 KiB

Widgets Sociaux - Architecture Modulaire

Architecture de composants réutilisables pour les posts sociaux avec support d'images et vidéos.

📐 Architecture

🔹 Widgets Atomiques (Niveau 1)

Les plus petits composants réutilisables dans toute l'application.

SocialActionButton

Bouton d'action tout petit et uniforme

  • Taille par défaut: 22px
  • Supporte tooltip
  • Animation scale: 0.85
  • Couleur personnalisable
SocialActionButton(
  icon: Icons.favorite_rounded,
  onTap: () => handleLike(),
  color: Colors.red,
  tooltip: 'J\'aime',
)

SocialActionButtonWithCount

Bouton d'action avec compteur

  • Affichage du nombre (1K, 1M formaté)
  • Support état actif/inactif
  • Couleur différente si actif
SocialActionButtonWithCount(
  icon: Icons.favorite_rounded,
  count: 245,
  onTap: () => handleLike(),
  isActive: true,
  activeColor: Colors.red,
)

SocialBadge

Badge réutilisable générique

  • Icône optionnelle
  • Couleurs personnalisables
  • Taille de police ajustable
  • Padding personnalisable
SocialBadge(
  label: 'Nouveau',
  icon: Icons.fiber_new,
  fontSize: 11,
)

VerifiedBadge

Badge de compte vérifié

  • Icône verified avec tooltip
  • Taille personnalisable
VerifiedBadge(size: 16)

CategoryBadge

Badge de catégorie

  • Couleur secondaire
  • Icône optionnelle
CategoryBadge(
  category: 'Sport',
  icon: Icons.sports_soccer,
)

StatusBadge

Badge de statut dynamique

  • Couleurs automatiques selon le statut
    • "nouveau" → primaryContainer
    • "tendance" → errorContainer
    • "populaire" → tertiaryContainer
StatusBadge(
  status: 'tendance',
  icon: Icons.trending_up,
)

MediaCountBadge

Badge de nombre de médias

  • Pour images/vidéos
  • Affichage sur fond noir semi-transparent
  • Icône différente selon le type
MediaCountBadge(
  count: 5,
  isVideo: false,
)

🔹 Widgets de Médias (Niveau 2)

Composants spécialisés pour la gestion des médias.

PostMedia (Model)

Modèle de données pour un média

class PostMedia {
  final String url;
  final MediaType type; // image ou video
  final String? thumbnailUrl;
  final Duration? duration;
}

PostMediaViewer

Affichage de médias avec dispositions multiples

  • 1 média: Aspect ratio 1:1
  • 2 médias: Côte à côte
  • 3 médias: 1 grand + 2 petits
  • 4+ médias: Grille 2x2 avec compteur "+N"

Fonctionnalités:

  • Hero animation
  • Loading progressif
  • Gestion d'erreurs
  • Tap pour plein écran
  • Badge de durée pour vidéos
PostMediaViewer(
  medias: [
    PostMedia(url: 'image1.jpg', type: MediaType.image),
    PostMedia(url: 'video1.mp4', type: MediaType.video, duration: Duration(minutes: 2, seconds: 30)),
  ],
  postId: post.id,
  onTap: () => handleMediaTap(),
)

MediaPicker

Sélecteur de médias pour création de post

  • Sélection depuis galerie (multi)
  • Capture photo depuis caméra
  • Sélection vidéo
  • Limite: 10 médias max (personnalisable)
  • Prévisualisation avec bouton supprimer
  • Indicateur vidéo sur thumbnails

Fonctionnalités:

  • Compteur médias sélectionnés
  • Boutons désactivés si limite atteinte
  • Grille horizontale scrollable
  • Suppression individuelle
MediaPicker(
  onMediasChanged: (medias) {
    setState(() => selectedMedias = medias);
  },
  maxMedias: 10,
  initialMedias: [],
)

🔹 Dialogues et Composants Complexes (Niveau 3)

CreatePostDialog

Dialogue de création de post avec médias

Fonctionnalités:

  • Avatar et nom utilisateur
  • Champ texte (3-8 lignes, max 500 caractères)
  • MediaPicker intégré
  • Validation formulaire
  • État de chargement pendant création
  • Compteur caractères
  • Visibilité (Public par défaut)
  • Compression automatique des images avant upload
  • Upload progressif avec indicateur de progression
  • Nettoyage automatique des fichiers temporaires

Processus d'upload en 3 étapes:

  1. Compression des images (0-50%)
  2. Upload des médias (50-100%)
  3. Création du post

Présentation:

  • Modal bottom sheet
  • Auto-focus sur le champ texte
  • Padding adapté au clavier
  • Actions: Annuler / Publier
  • Barre de progression avec statut textuel
await CreatePostDialog.show(
  context: context,
  onPostCreated: (content, medias) async {
    await createPost(content, medias);
  },
  userName: 'Jean Dupont',
  userAvatarUrl: 'avatar.jpg',
);

EditPostDialog

Dialogue d'édition de post existant

Fonctionnalités:

  • Pré-remplissage avec contenu existant
  • Détection automatique des changements
  • MediaPicker pour modifier les médias
  • Validation formulaire
  • Bouton "Enregistrer" désactivé si aucun changement
  • État de chargement pendant mise à jour

Présentation:

  • Modal bottom sheet
  • Auto-focus sur le champ texte
  • Icône d'édition dans l'en-tête
  • Actions: Annuler / Enregistrer
await EditPostDialog.show(
  context: context,
  post: existingPost,
  onPostUpdated: (content, medias) async {
    await updatePost(existingPost.id, content, medias);
  },
);

FullscreenVideoPlayer

Lecteur vidéo plein écran avec contrôles

Fonctionnalités:

  • Lecture automatique au démarrage
  • Orientation paysage forcée
  • Mode immersif (barre système cachée)
  • Contrôles tactiles :
    • Tap pour afficher/masquer contrôles
    • Play/Pause
    • Reculer de 10s
    • Avancer de 10s
  • Barre de progression interactive (scrubbing)
  • Affichage durée actuelle / totale
  • Hero animation pour transition fluide

Présentation:

  • Fond noir
  • Header avec bouton fermer et titre
  • Contrôles centraux (reculer/play/avancer)
  • Footer avec barre de progression
  • Gradient overlay sur header/footer
await FullscreenVideoPlayer.show(
  context: context,
  videoUrl: 'https://example.com/video.mp4',
  heroTag: 'post_media_123_0',
  title: 'Ma vidéo',
);

SocialCardRefactored

Card modulaire de post social

Composants internes:

  • _PostHeader: Avatar gradient, nom, badge vérifié, timestamp, menu
  • _UserAvatar: Avatar avec bordure gradient
  • _PostTimestamp: Formatage relatif (Il y a X min/h/j)
  • _PostMenu: PopupMenu (Modifier, Supprimer)
  • _PostActions: Like, Comment, Share, Bookmark
  • _PostStats: Nombre de likes
  • _PostContent: Texte enrichi (hashtags #, mentions @)
  • _CommentsLink: Lien vers commentaires

Fonctionnalités:

  • Support médias via PostMediaViewer
  • Hashtags et mentions cliquables (couleur primaire)
  • "Voir plus/Voir moins" pour contenu long (>150 caractères)
  • Badge de catégorie optionnel
  • Badge vérifié optionnel
  • AnimatedCard avec elevation
SocialCardRefactored(
  post: post,
  onLike: () => handleLike(),
  onComment: () => handleComment(),
  onShare: () => handleShare(),
  onDeletePost: () => handleDelete(),
  onEditPost: () => handleEdit(),
  showVerifiedBadge: true,
  showCategory: true,
  category: 'Sport',
)

🔧 Services (lib/data/services/)

ImageCompressionService

Service de compression d'images avant upload

Configurations prédéfinies:

  • CompressionConfig.post : 85% qualité, 1920x1920px
  • CompressionConfig.thumbnail : 70% qualité, 400x400px
  • CompressionConfig.story : 90% qualité, 1080x1920px
  • CompressionConfig.avatar : 80% qualité, 500x500px

Fonctionnalités:

  • Compression avec qualité ajustable (0-100)
  • Redimensionnement automatique
  • Support formats: JPEG, PNG, WebP
  • Compression parallèle pour plusieurs images
  • Callback de progression
  • Statistiques de réduction de taille
  • Nettoyage automatique des fichiers temporaires

Utilisation:

final compressionService = ImageCompressionService();

// Compression d'une image
final compressed = await compressionService.compressImage(
  imageFile,
  config: CompressionConfig.post,
);

// Compression multiple avec progression
final compressedList = await compressionService.compressMultipleImages(
  imageFiles,
  config: CompressionConfig.thumbnail,
  onProgress: (processed, total) {
    print('Compression: $processed/$total');
  },
);

// Créer un thumbnail
final thumbnail = await compressionService.createThumbnail(imageFile);

// Nettoyer les fichiers temporaires
await compressionService.cleanupTempFiles();

Logs (si EnvConfig.enableDetailedLogs = true):

[ImageCompression] Compression de: photo.jpg
[ImageCompression] Taille originale: 4.2 MB
[ImageCompression] Taille compressée: 1.8 MB
[ImageCompression] Réduction: 57.1%

MediaUploadService

Service d'upload de médias vers le backend

Fonctionnalités:

  • Upload d'images et vidéos
  • Upload parallèle de plusieurs médias
  • Callback de progression
  • Génération automatique de thumbnails pour vidéos
  • Support vidéos locales et réseau
  • Suppression de médias

Modèle de résultat:

class MediaUploadResult {
  final String url;           // URL du média uploadé
  final String? thumbnailUrl; // URL du thumbnail (vidéo)
  final String type;          // 'image' ou 'video'
  final Duration? duration;   // Durée (vidéo seulement)
}

Utilisation:

final uploadService = MediaUploadService(http.Client());

// Upload d'un média
final result = await uploadService.uploadMedia(imageFile);
print('URL: ${result.url}');

// Upload multiple avec progression
final results = await uploadService.uploadMultipleMedias(
  mediaFiles,
  onProgress: (uploaded, total) {
    print('Upload: $uploaded/$total');
  },
);

// Générer thumbnail pour vidéo
final thumbnailUrl = await uploadService.generateVideoThumbnail(videoUrl);

// Supprimer un média
await uploadService.deleteMedia(mediaUrl);

Configuration: Le endpoint d'upload est configuré dans EnvConfig.mediaUploadEndpoint.

📦 Utilisation

Import Simple

import 'package:afterwork/presentation/widgets/social/social_widgets.dart';

Ceci importe tous les widgets nécessaires :

  • Boutons d'action
  • Badges
  • Media picker
  • Post media viewer
  • Create post dialog
  • Social card

Exemple Complet

// 1. Créer un post avec compression et upload automatiques
await CreatePostDialog.show(
  context: context,
  onPostCreated: (content, medias) async {
    // Les médias sont déjà compressés et uploadés par le dialogue
    await apiService.createPost(
      content: content,
      mediaFiles: medias,
    );
  },
  userName: currentUser.name,
  userAvatarUrl: currentUser.avatar,
);

// 2. Créer un post manuellement avec services
final compressionService = ImageCompressionService();
final uploadService = MediaUploadService(http.Client());

// Compresser les images
final compressedMedias = await compressionService.compressMultipleImages(
  selectedMedias,
  config: CompressionConfig.post,
  onProgress: (processed, total) {
    print('Compression: $processed/$total');
  },
);

// Uploader les médias
final uploadResults = await uploadService.uploadMultipleMedias(
  compressedMedias,
  onProgress: (uploaded, total) {
    print('Upload: $uploaded/$total');
  },
);

// Créer le post avec les URLs
await apiService.createPost(
  content: contentController.text,
  mediaUrls: uploadResults.map((r) => r.url).toList(),
);

// Nettoyer les fichiers temporaires
await compressionService.cleanupTempFiles();

// 3. Afficher un post avec médias
SocialCardRefactored(
  post: SocialPost(
    id: '123',
    content: 'Super soirée avec les amis ! #afterwork @john',
    imageUrl: '', // Géré par medias maintenant
    likesCount: 42,
    commentsCount: 5,
    sharesCount: 2,
    isLikedByCurrentUser: false,
    // ...
  ),
  medias: [
    PostMedia(
      url: 'https://example.com/photo1.jpg',
      type: MediaType.image,
    ),
    PostMedia(
      url: 'https://example.com/video1.mp4',
      type: MediaType.video,
      thumbnailUrl: 'https://example.com/video1_thumb.jpg',
      duration: Duration(minutes: 2, seconds: 30),
    ),
  ],
  onLike: () {
    setState(() {
      post = post.copyWith(
        isLikedByCurrentUser: !post.isLikedByCurrentUser,
        likesCount: post.isLikedByCurrentUser
          ? post.likesCount - 1
          : post.likesCount + 1,
      );
    });
  },
  onComment: () {
    Navigator.push(/* CommentsScreen */);
  },
  onEditPost: () async {
    await EditPostDialog.show(
      context: context,
      post: post,
      onPostUpdated: (content, medias) async {
        await apiService.updatePost(post.id, content, medias);
      },
    );
  },
  onDeletePost: () async {
    await apiService.deletePost(post.id);
  },
  showVerifiedBadge: user.isVerified,
  showCategory: true,
  category: post.category,
);

// 4. Afficher une vidéo en plein écran
GestureDetector(
  onTap: () {
    FullscreenVideoPlayer.show(
      context: context,
      videoUrl: 'https://example.com/video.mp4',
      heroTag: 'post_media_${post.id}_0',
      title: 'Ma vidéo',
    );
  },
  child: Stack(
    children: [
      Image.network(videoThumbnail),
      Center(
        child: Icon(Icons.play_circle_outline, size: 64),
      ),
    ],
  ),
);

🎨 Principes de Design

Uniformité

  • Tous les boutons icônes: 20-22px
  • Tous les badges: fontSize 10-11px
  • Spacing: DesignSystem constants (4, 8, 12, 16px)
  • Border radius: DesignSystem (4, 10, 16px)

Réutilisabilité

  • Chaque composant fait UNE chose
  • Props claires et typées
  • Valeurs par défaut sensées
  • Documentation inline

Performances

  • Widgets const quand possible
  • Keys pour listes
  • Lazy loading des images
  • AnimatedCard pour hover effects

Accessibilité

  • Tooltips sur tous les boutons
  • Couleurs avec contraste suffisant
  • Tailles tactiles minimales respectées (44x44)

🔧 Personnalisation

Thème

Tous les widgets utilisent le ThemeData du contexte :

  • theme.colorScheme.primary
  • theme.colorScheme.onSurface
  • theme.textTheme.bodyMedium

DesignSystem

Les constantes sont centralisées :

  • DesignSystem.spacingSm (8px)
  • DesignSystem.spacingMd (12px)
  • DesignSystem.spacingLg (16px)
  • DesignSystem.radiusSm (4px)
  • DesignSystem.radiusMd (10px)
  • DesignSystem.radiusLg (16px)

📝 Statut des Fonctionnalités

Complété

  • Support de plusieurs médias par post - PostMediaViewer supporte 1-10+ médias avec dispositions adaptatives
  • Upload des médias vers le backend - MediaUploadService avec progression et support image/vidéo
  • Lecteur vidéo en plein écran - FullscreenVideoPlayer avec contrôles complets
  • Édition de post avec médias - EditPostDialog avec détection de changements
  • Compression d'images avant upload - ImageCompressionService avec 4 configs prédéfinies
  • Architecture modulaire - Widgets atomiques, réutilisables et bien documentés
  • Animations et interactions - Like, bookmark, hero transitions, scale effects
  • Hashtags et mentions cliquables - Parsing et styling automatiques
  • Progress tracking - Indicateurs de progression pour compression et upload

🚧 À Venir

  • Stories avec médias - Système de stories éphémères (24h) avec images/vidéos
  • Filtres sur les photos - Filtres Instagram-style pour édition d'images
  • GIF support - Support des GIFs animés dans les posts
  • Commentaires imbriqués - Système de réponses aux commentaires
  • Réactions étendues - Plus de réactions au-delà du simple like (❤️ 😂 😮 😢 😡)
  • Partage vers stories - Partager un post dans sa story
  • Brouillons - Sauvegarder des posts en brouillon
  • Planification - Programmer la publication d'un post

📊 Métriques

  • 13 widgets créés (atomiques + composites)
  • 2 services (compression + upload)
  • 5 types de badges réutilisables
  • 4 layouts pour affichage médias (1, 2, 3, 4+)
  • 3 étapes de processus d'upload (compression, upload, création)
  • 10 médias max par post (configurable)