import 'package:flutter/material.dart'; import 'package:flutter_animate/flutter_animate.dart'; /// Custom notification widget that matches app design without underlines class CustomNotification extends StatefulWidget { final String message; final bool isSuccess; final VoidCallback? onDismiss; final Duration duration; const CustomNotification({ super.key, required this.message, required this.isSuccess, this.onDismiss, this.duration = const Duration(seconds: 3), }); @override State createState() => _CustomNotificationState(); } class _CustomNotificationState extends State with SingleTickerProviderStateMixin { late AnimationController _controller; late Animation _slideAnimation; late Animation _fadeAnimation; @override void initState() { super.initState(); _controller = AnimationController( duration: const Duration(milliseconds: 300), vsync: this, ); _slideAnimation = Tween( begin: -1.0, end: 0.0, ).animate(CurvedAnimation( parent: _controller, curve: Curves.elasticOut, )); _fadeAnimation = Tween( begin: 0.0, end: 1.0, ).animate(CurvedAnimation( parent: _controller, curve: Curves.easeInOut, )); _controller.forward(); _autoDismiss(); } void _autoDismiss() { Future.delayed(widget.duration, () { if (mounted) { _dismiss(); } }); } void _dismiss() { _controller.reverse().then((_) { if (mounted) { widget.onDismiss?.call(); } }); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Positioned( top: 50, left: 16, right: 16, child: AnimatedBuilder( animation: _controller, builder: (context, child) { return Transform.translate( offset: Offset(0, _slideAnimation.value * -100), child: Opacity( opacity: _fadeAnimation.value, child: Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: widget.isSuccess ? [ const Color(0xFF4CAF50), const Color(0xFF45A049), ] : [ const Color(0xFFE53E3E), const Color(0xFFC53030), ], ), borderRadius: BorderRadius.circular(12), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.2), blurRadius: 10, offset: const Offset(0, 4), ), ], ), child: Row( children: [ Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: Colors.white.withOpacity(0.2), borderRadius: BorderRadius.circular(8), ), child: Icon( widget.isSuccess ? Icons.check_circle : Icons.error, color: Colors.white, size: 24, ), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Text( widget.isSuccess ? 'Sucesso!' : 'Erro', style: const TextStyle( color: Colors.white, fontSize: 16, fontWeight: FontWeight.bold, decoration: TextDecoration.none, // Remove underline ), ), const SizedBox(height: 4), Text( widget.message, style: const TextStyle( color: Colors.white, fontSize: 14, decoration: TextDecoration.none, // Remove underline ), ), ], ), ), GestureDetector( onTap: _dismiss, child: Container( padding: const EdgeInsets.all(4), child: const Icon( Icons.close, color: Colors.white, size: 20, ), ), ), ], ), ), ), ); }, ), ).animate().scale( duration: const Duration(milliseconds: 300), curve: Curves.elasticOut, ); } } /// Helper class to show custom notifications without underlines class NotificationHelper { static OverlayEntry? _overlayEntry; static void showSuccess( BuildContext context, { required String message, Duration duration = const Duration(seconds: 3), }) { _showNotification( context, message: message, isSuccess: true, duration: duration, ); } static void showError( BuildContext context, { required String message, Duration duration = const Duration(seconds: 3), }) { _showNotification( context, message: message, isSuccess: false, duration: duration, ); } static void _showNotification( BuildContext context, { required String message, required bool isSuccess, required Duration duration, }) { // Dismiss previous notification if exists dismiss(); _overlayEntry = OverlayEntry( builder: (context) => CustomNotification( message: message, isSuccess: isSuccess, duration: duration, onDismiss: dismiss, ), ); Overlay.of(context).insert(_overlayEntry!); } static void dismiss() { if (_overlayEntry != null) { _overlayEntry?.remove(); _overlayEntry = null; } } }