import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; /// Notification badge widget that can be placed on icons class NotificationBadge extends StatelessWidget { final Widget child; final int count; final Color badgeColor; final Color textColor; final double? size; final bool showBadge; const NotificationBadge({ super.key, required this.child, this.count = 0, this.badgeColor = const Color(0xFFD32F2F), this.textColor = Colors.white, this.size, this.showBadge = true, }); @override Widget build(BuildContext context) { return Stack( clipBehavior: Clip.none, children: [ child, if (showBadge && count > 0) Positioned( right: -4.w, top: -4.h, child: _buildBadge(), ), ], ); } Widget _buildBadge() { final badgeSize = size ?? 18.w; final fontSize = (size ?? 18) * 0.55; return TweenAnimationBuilder( tween: Tween(begin: 0.0, end: 1.0), duration: const Duration(milliseconds: 300), curve: Curves.elasticOut, builder: (context, value, child) { return Transform.scale( scale: value, child: child, ); }, child: Container( padding: EdgeInsets.all(count > 99 ? 2.w : 3.w), decoration: BoxDecoration( color: badgeColor, shape: BoxShape.circle, boxShadow: [ BoxShadow( color: badgeColor.withOpacity(0.4), blurRadius: 4, offset: const Offset(0, 2), ), ], ), constraints: BoxConstraints( minWidth: badgeSize, minHeight: badgeSize, ), child: Center( child: Text( count > 99 ? '99+' : count.toString(), style: TextStyle( color: textColor, fontSize: fontSize.sp, fontWeight: FontWeight.bold, height: 1.0, ), textAlign: TextAlign.center, ), ), ), ); } } /// Dot badge for simple notifications class DotBadge extends StatelessWidget { final Widget child; final bool showDot; final Color dotColor; final double dotSize; const DotBadge({ super.key, required this.child, this.showDot = true, this.dotColor = const Color(0xFFD32F2F), this.dotSize = 8.0, }); @override Widget build(BuildContext context) { return Stack( clipBehavior: Clip.none, children: [ child, if (showDot) Positioned( right: 0, top: 0, child: TweenAnimationBuilder( tween: Tween(begin: 0.0, end: 1.0), duration: const Duration(milliseconds: 300), curve: Curves.elasticOut, builder: (context, value, child) { return Transform.scale( scale: value, child: child, ); }, child: Container( width: dotSize.w, height: dotSize.h, decoration: BoxDecoration( color: dotColor, shape: BoxShape.circle, border: Border.all(color: Colors.white, width: 2), boxShadow: [ BoxShadow( color: dotColor.withOpacity(0.4), blurRadius: 4, offset: const Offset(0, 2), ), ], ), ), ), ), ], ); } } /// Pulsing notification badge with animation class PulsingBadge extends StatefulWidget { final Widget child; final int count; final Color badgeColor; const PulsingBadge({ super.key, required this.child, this.count = 0, this.badgeColor = const Color(0xFFD32F2F), }); @override State createState() => _PulsingBadgeState(); } class _PulsingBadgeState extends State with SingleTickerProviderStateMixin { late AnimationController _controller; late Animation _scaleAnimation; @override void initState() { super.initState(); _controller = AnimationController( vsync: this, duration: const Duration(milliseconds: 1500), )..repeat(reverse: true); _scaleAnimation = Tween(begin: 1.0, end: 1.2).animate( CurvedAnimation(parent: _controller, curve: Curves.easeInOut), ); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { if (widget.count == 0) { return widget.child; } return Stack( clipBehavior: Clip.none, children: [ widget.child, Positioned( right: -4.w, top: -4.h, child: AnimatedBuilder( animation: _scaleAnimation, builder: (context, child) { return Transform.scale( scale: _scaleAnimation.value, child: child, ); }, child: Container( padding: EdgeInsets.all(4.w), decoration: BoxDecoration( color: widget.badgeColor, shape: BoxShape.circle, boxShadow: [ BoxShadow( color: widget.badgeColor.withOpacity(0.5), blurRadius: 8, spreadRadius: 2, ), ], ), constraints: BoxConstraints( minWidth: 18.w, minHeight: 18.h, ), child: Center( child: Text( widget.count > 99 ? '99+' : widget.count.toString(), style: TextStyle( color: Colors.white, fontSize: 10.sp, fontWeight: FontWeight.bold, ), textAlign: TextAlign.center, ), ), ), ), ), ], ); } } /// Badge with custom content class CustomBadge extends StatelessWidget { final Widget child; final String text; final Color backgroundColor; final Color textColor; final EdgeInsets? padding; const CustomBadge({ super.key, required this.child, required this.text, this.backgroundColor = const Color(0xFFD32F2F), this.textColor = Colors.white, this.padding, }); @override Widget build(BuildContext context) { return Stack( clipBehavior: Clip.none, children: [ child, Positioned( right: -8.w, top: -8.h, child: TweenAnimationBuilder( tween: Tween(begin: 0.0, end: 1.0), duration: const Duration(milliseconds: 300), curve: Curves.elasticOut, builder: (context, value, child) { return Transform.scale( scale: value, child: child, ); }, child: Container( padding: padding ?? EdgeInsets.symmetric(horizontal: 6.w, vertical: 2.h), decoration: BoxDecoration( color: backgroundColor, borderRadius: BorderRadius.circular(10.r), boxShadow: [ BoxShadow( color: backgroundColor.withOpacity(0.4), blurRadius: 4, offset: const Offset(0, 2), ), ], ), child: Text( text, style: TextStyle( color: textColor, fontSize: 10.sp, fontWeight: FontWeight.bold, ), ), ), ), ), ], ); } } /// Badge for bottom navigation items class BottomNavBadge extends StatelessWidget { final IconData icon; final int count; final bool isSelected; final Color? selectedColor; final Color? unselectedColor; const BottomNavBadge({ super.key, required this.icon, this.count = 0, this.isSelected = false, this.selectedColor, this.unselectedColor, }); @override Widget build(BuildContext context) { final color = isSelected ? (selectedColor ?? Theme.of(context).primaryColor) : (unselectedColor ?? Colors.grey.shade600); return NotificationBadge( count: count, child: Icon( icon, color: color, size: 24.w, ), ); } }