Files
afterwork/lib/main.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

242 lines
8.2 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:http/http.dart' as http;
import 'package:intl/date_symbol_data_local.dart';
import 'package:provider/provider.dart';
import 'config/injection/injection.dart';
import 'config/router.dart';
import 'core/constants/env_config.dart';
import 'core/theme/theme_provider.dart';
import 'core/utils/app_logger.dart';
import 'data/datasources/event_remote_data_source.dart';
import 'data/datasources/notification_remote_data_source.dart';
import 'data/providers/friends_provider.dart';
import 'data/providers/presence_provider.dart';
import 'data/providers/user_provider.dart';
import 'data/repositories/friends_repository_impl.dart';
import 'data/services/notification_service.dart';
import 'data/services/preferences_helper.dart';
import 'data/services/realtime_notification_service.dart';
import 'data/services/secure_storage.dart';
import 'domain/entities/user.dart';
import 'presentation/state_management/event_bloc.dart';
import 'presentation/widgets/realtime_notification_handler.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Validation de la configuration au démarrage
try {
EnvConfig.validate(throwOnError: true);
AppLogger.i('Configuration validée avec succès', tag: 'Main');
AppLogger.d(EnvConfig.getConfigSummary(), tag: 'Main');
} on ConfigurationException catch (e, stackTrace) {
AppLogger.e(
'Erreur de configuration au démarrage',
error: e,
stackTrace: stackTrace,
tag: 'Main',
);
// En production, on ne peut pas continuer avec une configuration invalide
if (EnvConfig.isProduction) {
throw e;
}
// En développement, on continue mais on log l'erreur
AppLogger.w('Continuité en mode développement malgré les erreurs de configuration', tag: 'Main');
}
// Initialisation de l'injection de dépendances
init();
// Initialisation du format de date en français
await initializeDateFormatting('fr_FR');
// Récupération des services depuis GetIt
final secureStorage = sl<SecureStorage>();
final preferencesHelper = sl<PreferencesHelper>();
// Récupération des informations stockées avec logs détaillés
String? userId = await secureStorage.getUserId();
String? userFirstName = await preferencesHelper.getUseFirstrName();
String? userLastName = await preferencesHelper.getUserLastName();
// Log de la récupération des informations
AppLogger.i(
'Récupération des informations utilisateur : userId = $userId, userFirstName = $userFirstName, userLastName = $userLastName',
tag: 'Main',
);
// Gestion des valeurs par défaut si les informations ne sont pas trouvées
userId ??= 'default_user_id';
userFirstName ??= 'Default';
userLastName ??= 'User';
// Log des valeurs par défaut appliquées
AppLogger.i(
'Valeurs par défaut appliquées : userId = $userId, userFirstName = $userFirstName, userLastName = $userLastName',
tag: 'Main',
);
// Création de l'objet User
final User user = User(
userId: userId,
userLastName: userLastName,
userFirstName: userFirstName,
email: 'user@example.com',
motDePasse: 'motDePasseHashé',
profileImageUrl: 'lib/assets/images/profile_picture.png',
);
runApp(MyApp(user: user));
}
class MyApp extends StatefulWidget {
const MyApp({
required this.user,
super.key,
});
final User user;
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
RealtimeNotificationService? _realtimeService;
bool _isInitialized = false;
@override
void initState() {
super.initState();
_initializeRealtime();
}
/// Initialise le service de notifications temps réel.
Future<void> _initializeRealtime() async {
try {
// Vérifier si l'utilisateur est connecté (pas de valeur par défaut)
if (widget.user.userId == 'default_user_id') {
AppLogger.w('Utilisateur non connecté, service temps réel non initialisé', tag: 'MyApp');
setState(() => _isInitialized = true);
return;
}
AppLogger.i('Initialisation du service temps réel pour userId: ${widget.user.userId}', tag: 'MyApp');
// Créer le service temps réel
_realtimeService = RealtimeNotificationService(widget.user.userId);
await _realtimeService!.connect();
AppLogger.i('Service temps réel connecté avec succès', tag: 'MyApp');
setState(() => _isInitialized = true);
} catch (e, stackTrace) {
AppLogger.e('Erreur lors de l\'initialisation du service temps réel', error: e, stackTrace: stackTrace, tag: 'MyApp');
setState(() => _isInitialized = true);
}
}
/// Connecte le service temps réel aux providers après leur création.
void _connectProvidersToRealtime(BuildContext context) {
if (_realtimeService == null) return;
try {
// Connecter FriendsProvider
final friendsProvider = Provider.of<FriendsProvider>(context, listen: false);
friendsProvider.connectRealtime(_realtimeService!);
AppLogger.i('FriendsProvider connecté au service temps réel', tag: 'MyApp');
// Connecter PresenceProvider
final presenceProvider = Provider.of<PresenceProvider>(context, listen: false);
presenceProvider.connectRealtime(_realtimeService!);
AppLogger.i('PresenceProvider connecté au service temps réel', tag: 'MyApp');
// Connecter NotificationService
final notificationService = Provider.of<NotificationService>(context, listen: false);
notificationService.connectRealtime(_realtimeService!);
AppLogger.i('NotificationService connecté au service temps réel', tag: 'MyApp');
} catch (e, stackTrace) {
AppLogger.e('Erreur lors de la connexion des providers au temps réel', error: e, stackTrace: stackTrace, tag: 'MyApp');
}
}
@override
void dispose() {
_realtimeService?.disconnect();
super.dispose();
}
@override
Widget build(BuildContext context) {
// Récupération des dépendances depuis GetIt
final friendsRepository = sl<FriendsRepositoryImpl>();
final notificationDataSource = sl<NotificationRemoteDataSource>();
final secureStorage = sl<SecureStorage>();
final eventRemoteDataSource = sl<EventRemoteDataSource>();
// Log lors de la construction de l'application
AppLogger.i(
"Construction de l'application avec user : ${widget.user.toString()}",
tag: 'MyApp',
);
return MultiProvider(
providers: [
ChangeNotifierProvider(
create: (_) => UserProvider()..setUser(widget.user),
),
ChangeNotifierProvider(
create: (_) => FriendsProvider(friendsRepository: friendsRepository),
),
ChangeNotifierProvider(
create: (_) => PresenceProvider(),
),
ChangeNotifierProvider(
create: (_) => ThemeProvider(),
),
ChangeNotifierProvider(
create: (_) => NotificationService(notificationDataSource, secureStorage)
..initialize(),
),
BlocProvider(
create: (context) => sl<EventBloc>(),
),
],
child: Consumer<ThemeProvider>(
builder: (context, themeProvider, _) {
// Connecter les providers au service temps réel après leur création
if (_isInitialized && _realtimeService != null) {
WidgetsBinding.instance.addPostFrameCallback((_) {
_connectProvidersToRealtime(context);
});
}
final materialApp = MaterialApp(
title: 'AfterWork',
theme: themeProvider.currentTheme,
onGenerateRoute: AppRouter(
eventRemoteDataSource: eventRemoteDataSource,
userId: widget.user.userId,
userFirstName: widget.user.userFirstName,
userLastName: widget.user.userLastName,
).generateRoute,
initialRoute: '/',
);
// Wrapper avec RealtimeNotificationHandler si le service est initialisé
if (_isInitialized && _realtimeService != null) {
return RealtimeNotificationHandler(
realtimeService: _realtimeService!,
child: materialApp,
);
}
return materialApp;
},
),
);
}
}