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

@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:provider/provider.dart';
import '../../core/constants/design_system.dart';
import '../../core/constants/env_config.dart';
import '../../core/constants/urls.dart';
import '../../data/datasources/user_remote_data_source.dart';
@@ -130,6 +131,12 @@ class _AddFriendDialogState extends State<AddFriendDialog> {
return;
}
// VALIDATION: Empêcher l'utilisateur de s'ajouter lui-même comme ami
if (friendId == _currentUserId) {
_showError('Vous ne pouvez pas vous ajouter vous-même comme ami');
return;
}
String? friendEmail;
try {
@@ -214,24 +221,24 @@ class _AddFriendDialogState extends State<AddFriendDialog> {
return Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
borderRadius: BorderRadius.circular(DesignSystem.radiusMd),
),
child: Container(
constraints: const BoxConstraints(maxWidth: 400, maxHeight: 600),
padding: const EdgeInsets.all(24),
padding: const EdgeInsets.all(DesignSystem.spacingLg),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_buildHeader(theme),
const SizedBox(height: 16),
const SizedBox(height: DesignSystem.spacingMd),
_buildSearchField(theme),
const SizedBox(height: 16),
const SizedBox(height: DesignSystem.spacingMd),
if (_errorMessage != null) _buildErrorMessage(theme),
if (_isSearching) _buildLoadingIndicator(theme),
if (!_isSearching && _searchResults.isNotEmpty)
_buildSearchResults(theme),
const SizedBox(height: 16),
const SizedBox(height: DesignSystem.spacingMd),
_buildActions(theme),
],
),
@@ -244,24 +251,25 @@ class _AddFriendDialogState extends State<AddFriendDialog> {
return Row(
children: [
Icon(
Icons.person_add,
Icons.person_add_rounded,
color: theme.colorScheme.primary,
size: 28,
size: 24,
),
const SizedBox(width: 12),
const SizedBox(width: DesignSystem.spacingMd),
Expanded(
child: Text(
'Ajouter un ami',
style: theme.textTheme.titleLarge?.copyWith(
style: theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
fontSize: 17,
),
),
),
IconButton(
icon: const Icon(Icons.close),
icon: const Icon(Icons.close_rounded, size: 20),
onPressed: () => Navigator.of(context).pop(),
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
constraints: const BoxConstraints(minWidth: 32, minHeight: 32),
),
],
);
@@ -273,10 +281,18 @@ class _AddFriendDialogState extends State<AddFriendDialog> {
controller: _searchController,
decoration: InputDecoration(
hintText: 'Entrez l\'email de l\'ami',
prefixIcon: const Icon(Icons.person_search),
hintStyle: theme.textTheme.bodyMedium?.copyWith(
color: theme.colorScheme.onSurface.withOpacity(0.4),
fontSize: 14,
),
prefixIcon: Icon(
Icons.search_rounded,
size: 20,
color: theme.colorScheme.onSurface.withOpacity(0.5),
),
suffixIcon: _searchController.text.isNotEmpty
? IconButton(
icon: const Icon(Icons.clear),
icon: const Icon(Icons.clear_rounded, size: 18),
onPressed: () {
_searchController.clear();
setState(() {
@@ -287,10 +303,19 @@ class _AddFriendDialogState extends State<AddFriendDialog> {
)
: null,
helperText: 'Recherchez un utilisateur par son adresse email',
helperStyle: theme.textTheme.bodySmall?.copyWith(
fontSize: 12,
color: theme.colorScheme.onSurface.withOpacity(0.5),
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderRadius: BorderRadius.circular(DesignSystem.radiusMd),
),
contentPadding: const EdgeInsets.symmetric(
horizontal: DesignSystem.spacingMd,
vertical: DesignSystem.spacingSm,
),
),
style: theme.textTheme.bodyMedium?.copyWith(fontSize: 14),
onChanged: (value) {
setState(() {
_errorMessage = null;
@@ -321,24 +346,25 @@ class _AddFriendDialogState extends State<AddFriendDialog> {
/// Construit le message d'erreur
Widget _buildErrorMessage(ThemeData theme) {
return Container(
padding: const EdgeInsets.all(12),
padding: const EdgeInsets.all(DesignSystem.spacingMd),
decoration: BoxDecoration(
color: theme.colorScheme.errorContainer,
borderRadius: BorderRadius.circular(8),
borderRadius: BorderRadius.circular(DesignSystem.radiusSm),
),
child: Row(
children: [
Icon(
Icons.error_outline,
Icons.error_outline_rounded,
color: theme.colorScheme.error,
size: 20,
size: 18,
),
const SizedBox(width: 8),
const SizedBox(width: DesignSystem.spacingSm),
Expanded(
child: Text(
_errorMessage!,
style: theme.textTheme.bodySmall?.copyWith(
color: theme.colorScheme.onErrorContainer,
fontSize: 13,
),
),
),
@@ -349,10 +375,10 @@ class _AddFriendDialogState extends State<AddFriendDialog> {
/// Construit l'indicateur de chargement
Widget _buildLoadingIndicator(ThemeData theme) {
return const Padding(
padding: EdgeInsets.all(24),
return Padding(
padding: const EdgeInsets.all(DesignSystem.spacingLg),
child: Center(
child: CircularProgressIndicator(),
child: CircularProgressIndicator(strokeWidth: 2.5),
),
);
}
@@ -360,10 +386,10 @@ class _AddFriendDialogState extends State<AddFriendDialog> {
/// Construit les résultats de recherche
Widget _buildSearchResults(ThemeData theme) {
if (_isSearching) {
return const Padding(
padding: EdgeInsets.all(24),
return Padding(
padding: const EdgeInsets.all(DesignSystem.spacingLg),
child: Center(
child: CircularProgressIndicator(),
child: CircularProgressIndicator(strokeWidth: 2.5),
),
);
}
@@ -372,26 +398,36 @@ class _AddFriendDialogState extends State<AddFriendDialog> {
return const SizedBox.shrink();
}
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: _searchResults.length,
itemBuilder: (context, index) {
final user = _searchResults[index];
return _buildUserTile(theme, user);
},
),
return ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: _searchResults.length,
itemBuilder: (context, index) {
final user = _searchResults[index];
return _buildUserTile(theme, user);
},
);
}
/// Construit une tuile d'utilisateur
Widget _buildUserTile(ThemeData theme, UserModel user) {
return Card(
margin: const EdgeInsets.only(bottom: 8),
margin: const EdgeInsets.only(bottom: DesignSystem.spacingSm),
elevation: 0.5,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(DesignSystem.radiusMd),
side: BorderSide(
color: theme.dividerColor.withOpacity(0.3),
width: 1,
),
),
child: ListTile(
contentPadding: const EdgeInsets.symmetric(
horizontal: DesignSystem.spacingMd,
vertical: DesignSystem.spacingSm,
),
leading: CircleAvatar(
radius: 20,
backgroundColor: theme.colorScheme.primaryContainer,
backgroundImage: user.profileImageUrl.isNotEmpty &&
user.profileImageUrl.startsWith('http')
@@ -411,22 +447,32 @@ class _AddFriendDialogState extends State<AddFriendDialog> {
: '?',
style: TextStyle(
color: theme.colorScheme.onPrimaryContainer,
fontSize: 16,
fontWeight: FontWeight.w600,
),
)
: null,
),
title: Text(
'${user.userFirstName} ${user.userLastName}'.trim(),
style: theme.textTheme.titleMedium,
style: theme.textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w600,
fontSize: 14,
),
),
subtitle: Text(
user.email,
style: theme.textTheme.bodySmall,
style: theme.textTheme.bodySmall?.copyWith(
fontSize: 12,
color: theme.colorScheme.onSurface.withOpacity(0.6),
),
),
trailing: IconButton(
icon: const Icon(Icons.person_add),
icon: const Icon(Icons.person_add_rounded, size: 20),
onPressed: _isSearching ? null : () => _addFriend(user.userId),
tooltip: 'Ajouter comme ami',
padding: EdgeInsets.zero,
constraints: const BoxConstraints(minWidth: 36, minHeight: 36),
),
),
);