Files
afterwork/lib/data/services/image_compression_service.dart
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

174 lines
5.2 KiB
Dart

import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter_image_compress/flutter_image_compress.dart';
import 'package:image_picker/image_picker.dart';
import 'package:path/path.dart' as path;
import 'package:path_provider/path_provider.dart';
import '../../core/constants/env_config.dart';
/// Configuration de compression d'image.
class CompressionConfig {
const CompressionConfig({
this.quality = 85,
this.maxWidth = 1920,
this.maxHeight = 1920,
this.format = CompressFormat.jpeg,
});
final int quality; // 0-100
final int maxWidth;
final int maxHeight;
final CompressFormat format;
/// Configuration pour les posts (équilibre qualité/taille)
static const CompressionConfig post = CompressionConfig(
quality: 85,
maxWidth: 1920,
maxHeight: 1920,
);
/// Configuration pour les thumbnails (petite taille)
static const CompressionConfig thumbnail = CompressionConfig(
quality: 70,
maxWidth: 400,
maxHeight: 400,
);
/// Configuration pour les stories (vertical, haute qualité)
static const CompressionConfig story = CompressionConfig(
quality: 90,
maxWidth: 1080,
maxHeight: 1920,
);
/// Configuration pour les avatars (petit, carré)
static const CompressionConfig avatar = CompressionConfig(
quality: 80,
maxWidth: 500,
maxHeight: 500,
);
}
/// Service de compression d'images.
///
/// Compresse les images avant l'upload pour réduire la bande passante
/// et améliorer les performances.
class ImageCompressionService {
/// Compresse une image selon la configuration donnée.
Future<XFile?> compressImage(
XFile file, {
CompressionConfig config = CompressionConfig.post,
}) async {
try {
final filePath = file.path;
final lastIndex = filePath.lastIndexOf('.');
final splitted = filePath.substring(0, lastIndex);
final outPath = '${splitted}_compressed${path.extension(filePath)}';
if (EnvConfig.enableDetailedLogs) {
final originalSize = await File(filePath).length();
debugPrint('[ImageCompression] Compression de: ${path.basename(filePath)}');
debugPrint('[ImageCompression] Taille originale: ${_formatBytes(originalSize)}');
}
final result = await FlutterImageCompress.compressAndGetFile(
filePath,
outPath,
quality: config.quality,
minWidth: config.maxWidth,
minHeight: config.maxHeight,
format: config.format,
);
if (result != null) {
if (EnvConfig.enableDetailedLogs) {
final compressedSize = await File(result.path).length();
final originalSize = await File(filePath).length();
final reduction = ((1 - compressedSize / originalSize) * 100).toStringAsFixed(1);
debugPrint('[ImageCompression] Taille compressée: ${_formatBytes(compressedSize)}');
debugPrint('[ImageCompression] Réduction: $reduction%');
}
return result;
}
return null;
} catch (e) {
debugPrint('[ImageCompression] Erreur: $e');
// En cas d'erreur, on retourne le fichier original
return file;
}
}
/// Compresse plusieurs images en parallèle.
Future<List<XFile>> compressMultipleImages(
List<XFile> files, {
CompressionConfig config = CompressionConfig.post,
void Function(int processed, int total)? onProgress,
}) async {
final results = <XFile>[];
int processed = 0;
for (final file in files) {
// Ne compresser que les images, pas les vidéos
if (_isImageFile(file.path)) {
final compressed = await compressImage(file, config: config);
results.add(compressed ?? file);
} else {
results.add(file);
}
processed++;
if (onProgress != null) {
onProgress(processed, files.length);
}
}
return results;
}
/// Crée un thumbnail à partir d'une image.
Future<XFile?> createThumbnail(XFile file) async {
return compressImage(file, config: CompressionConfig.thumbnail);
}
/// Vérifie si le fichier est une image.
bool _isImageFile(String filePath) {
const imageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.bmp'];
final extension = path.extension(filePath).toLowerCase();
return imageExtensions.contains(extension);
}
/// Formate la taille en bytes de manière lisible.
String _formatBytes(int bytes) {
if (bytes < 1024) return '$bytes B';
if (bytes < 1024 * 1024) return '${(bytes / 1024).toStringAsFixed(1)} KB';
if (bytes < 1024 * 1024 * 1024) {
return '${(bytes / (1024 * 1024)).toStringAsFixed(1)} MB';
}
return '${(bytes / (1024 * 1024 * 1024)).toStringAsFixed(1)} GB';
}
/// Nettoie les fichiers temporaires compressés.
Future<void> cleanupTempFiles() async {
try {
final tempDir = await getTemporaryDirectory();
final files = tempDir.listSync();
for (final file in files) {
if (file.path.contains('_compressed')) {
await file.delete();
}
}
if (EnvConfig.enableDetailedLogs) {
debugPrint('[ImageCompression] Fichiers temporaires nettoyés');
}
} catch (e) {
debugPrint('[ImageCompression] Erreur nettoyage: $e');
}
}
}