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

382 lines
10 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
/// Enhanced card widget with interactive animations
class InteractiveCard extends StatefulWidget {
final Widget child;
final VoidCallback? onTap;
final VoidCallback? onLongPress;
final EdgeInsets? padding;
final EdgeInsets? margin;
final Color? backgroundColor;
final double? elevation;
final BorderRadius? borderRadius;
final bool enableHoverEffect;
final bool enableRipple;
final Duration animationDuration;
final Color? splashColor;
final Color? highlightColor;
const InteractiveCard({
super.key,
required this.child,
this.onTap,
this.onLongPress,
this.padding,
this.margin,
this.backgroundColor,
this.elevation,
this.borderRadius,
this.enableHoverEffect = true,
this.enableRipple = true,
this.animationDuration = const Duration(milliseconds: 200),
this.splashColor,
this.highlightColor,
});
@override
State<InteractiveCard> createState() => _InteractiveCardState();
}
class _InteractiveCardState extends State<InteractiveCard>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _scaleAnimation;
late Animation<double> _elevationAnimation;
bool _isPressed = false;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: widget.animationDuration,
);
_scaleAnimation = Tween<double>(
begin: 1.0,
end: 0.98,
).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeInOut),
);
_elevationAnimation = Tween<double>(
begin: widget.elevation ?? 2.0,
end: (widget.elevation ?? 2.0) + 4.0,
).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeInOut),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
void _handleTapDown(TapDownDetails details) {
if (widget.onTap != null) {
setState(() => _isPressed = true);
_controller.forward();
}
}
void _handleTapUp(TapUpDetails details) {
if (widget.onTap != null) {
setState(() => _isPressed = false);
_controller.reverse();
}
}
void _handleTapCancel() {
if (widget.onTap != null) {
setState(() => _isPressed = false);
_controller.reverse();
}
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Transform.scale(
scale: _scaleAnimation.value,
child: Card(
elevation: widget.enableHoverEffect && _isPressed
? _elevationAnimation.value
: widget.elevation ?? 2.0,
color: widget.backgroundColor,
margin: widget.margin ?? EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h),
shape: RoundedRectangleBorder(
borderRadius: widget.borderRadius ?? BorderRadius.circular(16.r),
),
child: Material(
color: Colors.transparent,
borderRadius: widget.borderRadius ?? BorderRadius.circular(16.r),
child: InkWell(
onTap: widget.onTap,
onLongPress: widget.onLongPress,
onTapDown: _handleTapDown,
onTapUp: _handleTapUp,
onTapCancel: _handleTapCancel,
borderRadius: widget.borderRadius ?? BorderRadius.circular(16.r),
splashColor: widget.splashColor ??
Theme.of(context).primaryColor.withOpacity(0.1),
highlightColor: widget.highlightColor ?? Colors.transparent,
enableFeedback: true,
child: Padding(
padding: widget.padding ?? EdgeInsets.all(16.w),
child: child,
),
),
),
),
);
},
child: widget.child,
);
}
}
/// Stats card with interactive hover effect
class InteractiveStatsCard extends StatelessWidget {
final String title;
final String value;
final IconData icon;
final Color color;
final VoidCallback? onTap;
final String? subtitle;
final Widget? trailing;
const InteractiveStatsCard({
super.key,
required this.title,
required this.value,
required this.icon,
required this.color,
this.onTap,
this.subtitle,
this.trailing,
});
@override
Widget build(BuildContext context) {
return InteractiveCard(
onTap: onTap,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Container(
padding: EdgeInsets.all(10.w),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(12.r),
),
child: Icon(
icon,
color: color,
size: 24.w,
),
),
trailing ??
Icon(
Icons.trending_up,
color: Colors.green.shade600,
size: 20.w,
),
],
),
SizedBox(height: 12.h),
Text(
value,
style: TextStyle(
fontSize: 24.sp,
fontWeight: FontWeight.bold,
color: Colors.grey.shade800,
),
),
SizedBox(height: 4.h),
Text(
title,
style: TextStyle(
fontSize: 14.sp,
color: Colors.grey.shade600,
),
overflow: TextOverflow.ellipsis,
),
if (subtitle != null) ...[
SizedBox(height: 4.h),
Text(
subtitle!,
style: TextStyle(
fontSize: 12.sp,
color: Colors.grey.shade500,
),
overflow: TextOverflow.ellipsis,
),
],
],
),
);
}
}
/// Quick action button card with icon
class QuickActionCard extends StatelessWidget {
final String title;
final IconData icon;
final Color color;
final VoidCallback onTap;
final String? subtitle;
const QuickActionCard({
super.key,
required this.title,
required this.icon,
required this.color,
required this.onTap,
this.subtitle,
});
@override
Widget build(BuildContext context) {
return InteractiveCard(
onTap: onTap,
padding: EdgeInsets.symmetric(vertical: 16.h, horizontal: 16.w),
margin: EdgeInsets.symmetric(vertical: 4.h),
child: Row(
children: [
Container(
padding: EdgeInsets.all(12.w),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(12.r),
),
child: Icon(
icon,
color: color,
size: 24.w,
),
),
SizedBox(width: 16.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w600,
color: Colors.grey.shade800,
),
),
if (subtitle != null) ...[
SizedBox(height: 2.h),
Text(
subtitle!,
style: TextStyle(
fontSize: 13.sp,
color: Colors.grey.shade600,
),
),
],
],
),
),
Icon(
Icons.arrow_forward_ios_rounded,
size: 16.w,
color: Colors.grey.shade400,
),
],
),
);
}
}
/// Activity card with timeline indicator
class ActivityCard extends StatelessWidget {
final String title;
final String description;
final String time;
final IconData icon;
final Color color;
final VoidCallback? onTap;
const ActivityCard({
super.key,
required this.title,
required this.description,
required this.time,
required this.icon,
required this.color,
this.onTap,
});
@override
Widget build(BuildContext context) {
return InteractiveCard(
onTap: onTap,
padding: EdgeInsets.all(16.w),
margin: EdgeInsets.symmetric(vertical: 6.h, horizontal: 0),
child: Row(
children: [
Container(
padding: EdgeInsets.all(12.w),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(12.r),
),
child: Icon(
icon,
color: color,
size: 20.w,
),
),
SizedBox(width: 16.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w600,
color: Colors.grey.shade800,
),
),
SizedBox(height: 4.h),
Text(
description,
style: TextStyle(
fontSize: 14.sp,
color: Colors.grey.shade600,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
],
),
),
SizedBox(width: 12.w),
Text(
time,
style: TextStyle(
fontSize: 12.sp,
color: Colors.grey.shade500,
fontWeight: FontWeight.w500,
),
),
],
),
);
}
}