Files
unionflow-server-api/unionflow-mobile-apps/lib/core/animations/animated_notifications.dart
2025-09-15 20:15:34 +00:00

353 lines
9.5 KiB
Dart

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,
}