## 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
174 lines
5.2 KiB
Dart
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');
|
|
}
|
|
}
|
|
}
|