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:
238
lib/presentation/widgets/modern_empty_state.dart
Normal file
238
lib/presentation/widgets/modern_empty_state.dart
Normal file
@@ -0,0 +1,238 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../core/constants/design_system.dart';
|
||||
import 'animated_widgets.dart';
|
||||
|
||||
/// Widget moderne pour afficher un état vide avec illustration et animation.
|
||||
///
|
||||
/// Ce widget affiche un état vide élégant avec :
|
||||
/// - Une illustration personnalisée (icône ou widget custom)
|
||||
/// - Un titre et une description
|
||||
/// - Un bouton d'action optionnel
|
||||
/// - Des animations fluides
|
||||
///
|
||||
/// **Usage:**
|
||||
/// ```dart
|
||||
/// ModernEmptyState(
|
||||
/// illustration: EmptyStateIllustration.friends,
|
||||
/// title: 'Aucun ami trouvé',
|
||||
/// description: 'Commencez à ajouter des amis',
|
||||
/// actionLabel: 'Ajouter un ami',
|
||||
/// onAction: () => _addFriend(),
|
||||
/// )
|
||||
/// ```
|
||||
class ModernEmptyState extends StatelessWidget {
|
||||
const ModernEmptyState({
|
||||
required this.illustration,
|
||||
required this.title,
|
||||
this.description,
|
||||
this.actionLabel,
|
||||
this.onAction,
|
||||
super.key,
|
||||
});
|
||||
|
||||
/// Type d'illustration à afficher
|
||||
final EmptyStateIllustration illustration;
|
||||
|
||||
/// Titre principal
|
||||
final String title;
|
||||
|
||||
/// Description optionnelle
|
||||
final String? description;
|
||||
|
||||
/// Label du bouton d'action
|
||||
final String? actionLabel;
|
||||
|
||||
/// Callback du bouton d'action
|
||||
final VoidCallback? onAction;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
|
||||
return Center(
|
||||
child: SingleChildScrollView(
|
||||
padding: DesignSystem.paddingAll(DesignSystem.spacingXl),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
// Illustration animée
|
||||
FadeInWidget(
|
||||
duration: DesignSystem.durationMedium,
|
||||
child: PulseAnimation(
|
||||
duration: const Duration(seconds: 2),
|
||||
child: _buildIllustration(theme),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
|
||||
// Titre
|
||||
FadeInWidget(
|
||||
delay: const Duration(milliseconds: 200),
|
||||
duration: DesignSystem.durationMedium,
|
||||
child: Text(
|
||||
title,
|
||||
style: theme.textTheme.headlineSmall?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: theme.colorScheme.onSurface,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
|
||||
// Description
|
||||
if (description != null) ...[
|
||||
const SizedBox(height: 12),
|
||||
FadeInWidget(
|
||||
delay: const Duration(milliseconds: 300),
|
||||
duration: DesignSystem.durationMedium,
|
||||
child: Text(
|
||||
description!,
|
||||
style: theme.textTheme.bodyLarge?.copyWith(
|
||||
color: theme.colorScheme.onSurface.withOpacity(0.7),
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
// Bouton d'action
|
||||
if (actionLabel != null && onAction != null) ...[
|
||||
const SizedBox(height: 32),
|
||||
FadeInWidget(
|
||||
delay: const Duration(milliseconds: 400),
|
||||
duration: DesignSystem.durationMedium,
|
||||
child: AnimatedScaleButton(
|
||||
onTap: onAction!,
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: onAction,
|
||||
icon: const Icon(Icons.add),
|
||||
label: Text(actionLabel!),
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 24,
|
||||
vertical: 16,
|
||||
),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: DesignSystem.borderRadiusMd,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Construit l'illustration selon le type
|
||||
Widget _buildIllustration(ThemeData theme) {
|
||||
final config = _getIllustrationConfig(illustration);
|
||||
|
||||
return Container(
|
||||
width: 200,
|
||||
height: 200,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
gradient: RadialGradient(
|
||||
colors: [
|
||||
config.color.withOpacity(0.1),
|
||||
config.color.withOpacity(0.02),
|
||||
],
|
||||
),
|
||||
),
|
||||
child: Center(
|
||||
child: Container(
|
||||
width: 120,
|
||||
height: 120,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: config.color.withOpacity(0.15),
|
||||
),
|
||||
child: Icon(
|
||||
config.icon,
|
||||
size: 64,
|
||||
color: config.color,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Retourne la configuration de l'illustration
|
||||
_IllustrationConfig _getIllustrationConfig(EmptyStateIllustration type) {
|
||||
switch (type) {
|
||||
case EmptyStateIllustration.friends:
|
||||
return _IllustrationConfig(
|
||||
icon: Icons.people_outline,
|
||||
color: Colors.blue,
|
||||
);
|
||||
case EmptyStateIllustration.requests:
|
||||
return _IllustrationConfig(
|
||||
icon: Icons.person_add_outlined,
|
||||
color: Colors.orange,
|
||||
);
|
||||
case EmptyStateIllustration.events:
|
||||
return _IllustrationConfig(
|
||||
icon: Icons.event_note_outlined,
|
||||
color: Colors.purple,
|
||||
);
|
||||
case EmptyStateIllustration.notifications:
|
||||
return _IllustrationConfig(
|
||||
icon: Icons.notifications_none_outlined,
|
||||
color: Colors.teal,
|
||||
);
|
||||
case EmptyStateIllustration.messages:
|
||||
return _IllustrationConfig(
|
||||
icon: Icons.chat_bubble_outline,
|
||||
color: Colors.green,
|
||||
);
|
||||
case EmptyStateIllustration.search:
|
||||
return _IllustrationConfig(
|
||||
icon: Icons.search_off_outlined,
|
||||
color: Colors.grey,
|
||||
);
|
||||
case EmptyStateIllustration.social:
|
||||
return _IllustrationConfig(
|
||||
icon: Icons.forum_outlined,
|
||||
color: Colors.pink,
|
||||
);
|
||||
case EmptyStateIllustration.reservations:
|
||||
return _IllustrationConfig(
|
||||
icon: Icons.bookmark_border_outlined,
|
||||
color: Colors.amber,
|
||||
);
|
||||
case EmptyStateIllustration.establishments:
|
||||
return _IllustrationConfig(
|
||||
icon: Icons.store_outlined,
|
||||
color: Colors.deepOrange,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Types d'illustrations disponibles pour les états vides
|
||||
enum EmptyStateIllustration {
|
||||
friends,
|
||||
requests,
|
||||
events,
|
||||
notifications,
|
||||
messages,
|
||||
search,
|
||||
social,
|
||||
reservations,
|
||||
establishments,
|
||||
}
|
||||
|
||||
/// Configuration d'une illustration
|
||||
class _IllustrationConfig {
|
||||
const _IllustrationConfig({
|
||||
required this.icon,
|
||||
required this.color,
|
||||
});
|
||||
|
||||
final IconData icon;
|
||||
final Color color;
|
||||
}
|
||||
Reference in New Issue
Block a user