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:
@@ -1,57 +1,190 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Bouton de soumission avec un gradient visuel et des ombres
|
||||
/// Utilisé pour l'envoi d'un formulaire d'événement
|
||||
class SubmitButton extends StatelessWidget {
|
||||
/// Fonction à exécuter lors de l'appui sur le bouton
|
||||
/// Bouton de soumission avec dégradé visuel et animations.
|
||||
///
|
||||
/// Ce widget fournit un bouton de soumission moderne avec:
|
||||
/// - Dégradé de couleurs adaptatif au thème
|
||||
/// - Ombres et élévations
|
||||
/// - Support des états de chargement
|
||||
/// - Animations fluides
|
||||
///
|
||||
/// **Usage:**
|
||||
/// ```dart
|
||||
/// SubmitButton(
|
||||
/// text: 'Créer l\'événement',
|
||||
/// onPressed: () {
|
||||
/// // Action de soumission
|
||||
/// },
|
||||
/// isLoading: false,
|
||||
/// )
|
||||
/// ```
|
||||
class SubmitButton extends StatefulWidget {
|
||||
/// Crée un nouveau [SubmitButton].
|
||||
///
|
||||
/// [text] Le texte à afficher sur le bouton
|
||||
/// [onPressed] La fonction à exécuter lors du clic
|
||||
/// [isLoading] Si true, affiche un indicateur de chargement
|
||||
/// [isEnabled] Si false, désactive le bouton
|
||||
/// [icon] Une icône optionnelle à afficher avant le texte
|
||||
const SubmitButton({
|
||||
required this.text,
|
||||
required this.onPressed,
|
||||
super.key,
|
||||
this.isLoading = false,
|
||||
this.isEnabled = true,
|
||||
this.icon,
|
||||
});
|
||||
|
||||
/// Le texte à afficher sur le bouton
|
||||
final String text;
|
||||
|
||||
/// La fonction à exécuter lors du clic
|
||||
final VoidCallback onPressed;
|
||||
|
||||
const SubmitButton({Key? key, required this.onPressed}) : super(key: key);
|
||||
/// Si true, affiche un indicateur de chargement
|
||||
final bool isLoading;
|
||||
|
||||
/// Si false, désactive le bouton
|
||||
final bool isEnabled;
|
||||
|
||||
/// Une icône optionnelle à afficher avant le texte
|
||||
final IconData? icon;
|
||||
|
||||
@override
|
||||
State<SubmitButton> createState() => _SubmitButtonState();
|
||||
}
|
||||
|
||||
class _SubmitButtonState extends State<SubmitButton>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late AnimationController _animationController;
|
||||
late Animation<double> _scaleAnimation;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_animationController = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(milliseconds: 150),
|
||||
);
|
||||
_scaleAnimation = Tween<double>(begin: 1.0, end: 0.95).animate(
|
||||
CurvedAnimation(
|
||||
parent: _animationController,
|
||||
curve: Curves.easeInOut,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_animationController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _handleTapDown(TapDownDetails details) {
|
||||
_animationController.forward();
|
||||
}
|
||||
|
||||
void _handleTapUp(TapUpDetails details) {
|
||||
_animationController.reverse();
|
||||
}
|
||||
|
||||
void _handleTapCancel() {
|
||||
_animationController.reverse();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
// Décoration du bouton avec un dégradé de couleurs et une ombre
|
||||
decoration: BoxDecoration(
|
||||
gradient: const LinearGradient(
|
||||
colors: [
|
||||
Color(0xFF1DBF73), // Dégradé vert clair
|
||||
Color(0xFF11998E), // Dégradé vert foncé
|
||||
],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.2),
|
||||
spreadRadius: 2,
|
||||
blurRadius: 8,
|
||||
offset: const Offset(2, 4), // Position de l'ombre
|
||||
final theme = Theme.of(context);
|
||||
final isDisabled = !widget.isEnabled || widget.isLoading;
|
||||
|
||||
return ScaleTransition(
|
||||
scale: _scaleAnimation,
|
||||
child: GestureDetector(
|
||||
onTapDown: isDisabled ? null : _handleTapDown,
|
||||
onTapUp: isDisabled ? null : _handleTapUp,
|
||||
onTapCancel: isDisabled ? null : _handleTapCancel,
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
height: 56,
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: isDisabled
|
||||
? [
|
||||
theme.colorScheme.surface,
|
||||
theme.colorScheme.surface,
|
||||
]
|
||||
: [
|
||||
theme.colorScheme.tertiary,
|
||||
theme.colorScheme.secondary,
|
||||
],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
boxShadow: isDisabled
|
||||
? null
|
||||
: [
|
||||
BoxShadow(
|
||||
color: theme.colorScheme.tertiary.withOpacity(0.3),
|
||||
spreadRadius: 2,
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
],
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
],
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
),
|
||||
child: ElevatedButton(
|
||||
onPressed: onPressed, // Appel de la fonction passée en paramètre
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.transparent, // Fond transparent pour voir le dégradé
|
||||
shadowColor: Colors.transparent, // Suppression de l'ombre par défaut
|
||||
padding: const EdgeInsets.symmetric(vertical: 14.0),
|
||||
minimumSize: const Size(double.infinity, 50), // Taille du bouton
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
),
|
||||
),
|
||||
child: const Text(
|
||||
'Créer l\'événement',
|
||||
style: TextStyle(
|
||||
color: Colors.white, // Couleur du texte
|
||||
fontSize: 16, // Taille du texte
|
||||
fontWeight: FontWeight.bold, // Texte en gras
|
||||
letterSpacing: 1.2, // Espacement entre les lettres
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: isDisabled ? null : widget.onPressed,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
|
||||
child: widget.isLoading
|
||||
? Center(
|
||||
child: SizedBox(
|
||||
height: 24,
|
||||
width: 24,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2.5,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
theme.colorScheme.onPrimary,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (widget.icon != null) ...[
|
||||
Icon(
|
||||
widget.icon,
|
||||
color: isDisabled
|
||||
? theme.colorScheme.onSurface.withOpacity(0.38)
|
||||
: theme.colorScheme.onPrimary,
|
||||
size: 20,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
],
|
||||
Text(
|
||||
widget.text,
|
||||
style: theme.textTheme.titleMedium?.copyWith(
|
||||
color: isDisabled
|
||||
? theme.colorScheme.onSurface.withOpacity(0.38)
|
||||
: theme.colorScheme.onPrimary,
|
||||
fontWeight: FontWeight.bold,
|
||||
letterSpacing: 0.5,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user