Versione OK Pour l'onglet événements.
This commit is contained in:
@@ -0,0 +1,352 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../shared/theme/app_theme.dart';
|
||||
|
||||
/// Service de notifications animées
|
||||
class AnimatedNotifications {
|
||||
static OverlayEntry? _currentOverlay;
|
||||
|
||||
/// Affiche une notification de succès
|
||||
static void showSuccess(
|
||||
BuildContext context,
|
||||
String message, {
|
||||
Duration duration = const Duration(seconds: 3),
|
||||
}) {
|
||||
_showNotification(
|
||||
context,
|
||||
message,
|
||||
NotificationType.success,
|
||||
duration,
|
||||
);
|
||||
}
|
||||
|
||||
/// Affiche une notification d'erreur
|
||||
static void showError(
|
||||
BuildContext context,
|
||||
String message, {
|
||||
Duration duration = const Duration(seconds: 4),
|
||||
}) {
|
||||
_showNotification(
|
||||
context,
|
||||
message,
|
||||
NotificationType.error,
|
||||
duration,
|
||||
);
|
||||
}
|
||||
|
||||
/// Affiche une notification d'information
|
||||
static void showInfo(
|
||||
BuildContext context,
|
||||
String message, {
|
||||
Duration duration = const Duration(seconds: 3),
|
||||
}) {
|
||||
_showNotification(
|
||||
context,
|
||||
message,
|
||||
NotificationType.info,
|
||||
duration,
|
||||
);
|
||||
}
|
||||
|
||||
/// Affiche une notification d'avertissement
|
||||
static void showWarning(
|
||||
BuildContext context,
|
||||
String message, {
|
||||
Duration duration = const Duration(seconds: 3),
|
||||
}) {
|
||||
_showNotification(
|
||||
context,
|
||||
message,
|
||||
NotificationType.warning,
|
||||
duration,
|
||||
);
|
||||
}
|
||||
|
||||
static void _showNotification(
|
||||
BuildContext context,
|
||||
String message,
|
||||
NotificationType type,
|
||||
Duration duration,
|
||||
) {
|
||||
// Supprimer la notification précédente si elle existe
|
||||
_currentOverlay?.remove();
|
||||
|
||||
final overlay = Overlay.of(context);
|
||||
late OverlayEntry overlayEntry;
|
||||
|
||||
overlayEntry = OverlayEntry(
|
||||
builder: (context) => AnimatedNotificationWidget(
|
||||
message: message,
|
||||
type: type,
|
||||
onDismiss: () {
|
||||
overlayEntry.remove();
|
||||
_currentOverlay = null;
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
_currentOverlay = overlayEntry;
|
||||
overlay.insert(overlayEntry);
|
||||
|
||||
// Auto-dismiss après la durée spécifiée
|
||||
Future.delayed(duration, () {
|
||||
if (_currentOverlay == overlayEntry) {
|
||||
overlayEntry.remove();
|
||||
_currentOverlay = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Masque la notification actuelle
|
||||
static void dismiss() {
|
||||
_currentOverlay?.remove();
|
||||
_currentOverlay = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// Widget de notification animée
|
||||
class AnimatedNotificationWidget extends StatefulWidget {
|
||||
final String message;
|
||||
final NotificationType type;
|
||||
final VoidCallback onDismiss;
|
||||
|
||||
const AnimatedNotificationWidget({
|
||||
super.key,
|
||||
required this.message,
|
||||
required this.type,
|
||||
required this.onDismiss,
|
||||
});
|
||||
|
||||
@override
|
||||
State<AnimatedNotificationWidget> createState() => _AnimatedNotificationWidgetState();
|
||||
}
|
||||
|
||||
class _AnimatedNotificationWidgetState extends State<AnimatedNotificationWidget>
|
||||
with TickerProviderStateMixin {
|
||||
late AnimationController _slideController;
|
||||
late AnimationController _fadeController;
|
||||
late AnimationController _scaleController;
|
||||
|
||||
late Animation<Offset> _slideAnimation;
|
||||
late Animation<double> _fadeAnimation;
|
||||
late Animation<double> _scaleAnimation;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_slideController = AnimationController(
|
||||
duration: const Duration(milliseconds: 500),
|
||||
vsync: this,
|
||||
);
|
||||
|
||||
_fadeController = AnimationController(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
vsync: this,
|
||||
);
|
||||
|
||||
_scaleController = AnimationController(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
vsync: this,
|
||||
);
|
||||
|
||||
_slideAnimation = Tween<Offset>(
|
||||
begin: const Offset(0, -1),
|
||||
end: Offset.zero,
|
||||
).animate(CurvedAnimation(
|
||||
parent: _slideController,
|
||||
curve: Curves.elasticOut,
|
||||
));
|
||||
|
||||
_fadeAnimation = Tween<double>(
|
||||
begin: 0.0,
|
||||
end: 1.0,
|
||||
).animate(CurvedAnimation(
|
||||
parent: _fadeController,
|
||||
curve: Curves.easeOut,
|
||||
));
|
||||
|
||||
_scaleAnimation = Tween<double>(
|
||||
begin: 1.0,
|
||||
end: 1.05,
|
||||
).animate(CurvedAnimation(
|
||||
parent: _scaleController,
|
||||
curve: Curves.easeInOut,
|
||||
));
|
||||
|
||||
// Démarrer les animations d'entrée
|
||||
_fadeController.forward();
|
||||
_slideController.forward();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_slideController.dispose();
|
||||
_fadeController.dispose();
|
||||
_scaleController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _dismiss() async {
|
||||
await _fadeController.reverse();
|
||||
widget.onDismiss();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colors = _getColors();
|
||||
|
||||
return Positioned(
|
||||
top: MediaQuery.of(context).padding.top + 16,
|
||||
left: 16,
|
||||
right: 16,
|
||||
child: AnimatedBuilder(
|
||||
animation: Listenable.merge([
|
||||
_slideAnimation,
|
||||
_fadeAnimation,
|
||||
_scaleAnimation,
|
||||
]),
|
||||
builder: (context, child) {
|
||||
return SlideTransition(
|
||||
position: _slideAnimation,
|
||||
child: FadeTransition(
|
||||
opacity: _fadeAnimation,
|
||||
child: Transform.scale(
|
||||
scale: _scaleAnimation.value,
|
||||
child: GestureDetector(
|
||||
onTap: () => _scaleController.forward().then((_) {
|
||||
_scaleController.reverse();
|
||||
}),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
colors.backgroundColor,
|
||||
colors.backgroundColor.withOpacity(0.9),
|
||||
],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: colors.backgroundColor.withOpacity(0.3),
|
||||
blurRadius: 12,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
// Icône
|
||||
Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: colors.iconBackgroundColor,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Icon(
|
||||
colors.icon,
|
||||
color: colors.iconColor,
|
||||
size: 24,
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(width: 12),
|
||||
|
||||
// Message
|
||||
Expanded(
|
||||
child: Text(
|
||||
widget.message,
|
||||
style: TextStyle(
|
||||
color: colors.textColor,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Bouton de fermeture
|
||||
GestureDetector(
|
||||
onTap: _dismiss,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: Icon(
|
||||
Icons.close,
|
||||
color: colors.textColor.withOpacity(0.7),
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
_NotificationColors _getColors() {
|
||||
switch (widget.type) {
|
||||
case NotificationType.success:
|
||||
return _NotificationColors(
|
||||
backgroundColor: AppTheme.successColor,
|
||||
textColor: Colors.white,
|
||||
icon: Icons.check_circle,
|
||||
iconColor: Colors.white,
|
||||
iconBackgroundColor: Colors.white.withOpacity(0.2),
|
||||
);
|
||||
case NotificationType.error:
|
||||
return _NotificationColors(
|
||||
backgroundColor: AppTheme.errorColor,
|
||||
textColor: Colors.white,
|
||||
icon: Icons.error,
|
||||
iconColor: Colors.white,
|
||||
iconBackgroundColor: Colors.white.withOpacity(0.2),
|
||||
);
|
||||
case NotificationType.warning:
|
||||
return _NotificationColors(
|
||||
backgroundColor: AppTheme.warningColor,
|
||||
textColor: Colors.white,
|
||||
icon: Icons.warning,
|
||||
iconColor: Colors.white,
|
||||
iconBackgroundColor: Colors.white.withOpacity(0.2),
|
||||
);
|
||||
case NotificationType.info:
|
||||
return _NotificationColors(
|
||||
backgroundColor: AppTheme.primaryColor,
|
||||
textColor: Colors.white,
|
||||
icon: Icons.info,
|
||||
iconColor: Colors.white,
|
||||
iconBackgroundColor: Colors.white.withOpacity(0.2),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _NotificationColors {
|
||||
final Color backgroundColor;
|
||||
final Color textColor;
|
||||
final IconData icon;
|
||||
final Color iconColor;
|
||||
final Color iconBackgroundColor;
|
||||
|
||||
_NotificationColors({
|
||||
required this.backgroundColor,
|
||||
required this.textColor,
|
||||
required this.icon,
|
||||
required this.iconColor,
|
||||
required this.iconBackgroundColor,
|
||||
});
|
||||
}
|
||||
|
||||
enum NotificationType {
|
||||
success,
|
||||
error,
|
||||
warning,
|
||||
info,
|
||||
}
|
||||
Reference in New Issue
Block a user