chitfund/luckychit/lib/shared/widgets/notification_badge.dart

342 lines
8.6 KiB
Dart

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<double>(
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<double>(
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<PulsingBadge> createState() => _PulsingBadgeState();
}
class _PulsingBadgeState extends State<PulsingBadge>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _scaleAnimation;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 1500),
)..repeat(reverse: true);
_scaleAnimation = Tween<double>(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<double>(
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,
),
);
}
}