baseproject

This commit is contained in:
Gaurav Kumar
2025-09-06 19:21:52 +05:30
commit 01be9df2ed
254 changed files with 28342 additions and 0 deletions

View File

@@ -0,0 +1,355 @@
import 'package:flutter/material.dart';
import '../../../core/constants/ui_constants.dart';
enum DashboardCardType {
primary,
secondary,
success,
warning,
info,
danger,
}
class DashboardCard extends StatefulWidget {
final String title;
final String? subtitle;
final String? value;
final num? numericValue;
final String? valueSuffix;
final Duration? animationDuration;
final IconData? icon;
final DashboardCardType type;
final VoidCallback? onTap;
final Widget? trailing;
final bool isLoading;
final Color? customColor;
final double? elevation;
final BorderRadius? borderRadius;
const DashboardCard({
super.key,
required this.title,
this.subtitle,
this.value,
this.numericValue,
this.valueSuffix,
this.animationDuration,
this.icon,
this.type = DashboardCardType.primary,
this.onTap,
this.trailing,
this.isLoading = false,
this.customColor,
this.elevation,
this.borderRadius,
});
@override
State<DashboardCard> createState() => _DashboardCardState();
}
class _DashboardCardState extends State<DashboardCard>
with SingleTickerProviderStateMixin {
late AnimationController _animationController;
late Animation<double> _scaleAnimation;
late Animation<double> _elevationAnimation;
bool _isPressed = false;
@override
void initState() {
super.initState();
_animationController = AnimationController(
duration: UIConstants.durationFast,
vsync: this,
);
_scaleAnimation = Tween<double>(
begin: 1.0,
end: 0.95,
).animate(CurvedAnimation(
parent: _animationController,
curve: UIConstants.curveFast,
));
_elevationAnimation = Tween<double>(
begin: 1.0,
end: 0.0,
).animate(CurvedAnimation(
parent: _animationController,
curve: UIConstants.curveFast,
));
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
void _onTapDown(TapDownDetails details) {
if (widget.onTap != null && !widget.isLoading) {
setState(() {
_isPressed = true;
});
_animationController.forward();
}
}
void _onTapUp(TapUpDetails details) {
if (widget.onTap != null && !widget.isLoading) {
setState(() {
_isPressed = false;
});
_animationController.reverse();
}
}
void _onTapCancel() {
if (widget.onTap != null && !widget.isLoading) {
setState(() {
_isPressed = false;
});
_animationController.reverse();
}
}
Color _getCardColor(ThemeData theme) {
if (widget.customColor != null) return widget.customColor!;
switch (widget.type) {
case DashboardCardType.primary:
return theme.colorScheme.primary;
case DashboardCardType.secondary:
return theme.colorScheme.secondary;
case DashboardCardType.success:
return theme.colorScheme.tertiary;
case DashboardCardType.warning:
return const Color(0xFFF59E0B);
case DashboardCardType.info:
return theme.colorScheme.primary;
case DashboardCardType.danger:
return theme.colorScheme.error;
}
}
Color _getIconColor(ThemeData theme) {
if (widget.customColor != null) return Colors.white;
switch (widget.type) {
case DashboardCardType.primary:
case DashboardCardType.secondary:
case DashboardCardType.success:
case DashboardCardType.warning:
case DashboardCardType.info:
case DashboardCardType.danger:
return Colors.white;
}
}
double _getElevation() {
if (widget.elevation != null) return widget.elevation!;
return UIConstants.elevation4;
}
BorderRadius _getBorderRadius() {
if (widget.borderRadius != null) return widget.borderRadius!;
return BorderRadius.circular(UIConstants.radius16);
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final colorScheme = theme.colorScheme;
final cardColor = _getCardColor(theme);
final iconColor = _getIconColor(theme);
return AnimatedBuilder(
animation: _animationController,
builder: (context, child) {
return Transform.scale(
scale: _scaleAnimation.value,
child: GestureDetector(
onTapDown: _onTapDown,
onTapUp: _onTapUp,
onTapCancel: _onTapCancel,
child: Container(
decoration: BoxDecoration(
borderRadius: _getBorderRadius(),
color: cardColor,
boxShadow: [
BoxShadow(
color: cardColor.withOpacity(0.3),
blurRadius: 8 * _elevationAnimation.value,
offset: Offset(0, 4 * _elevationAnimation.value),
),
],
),
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: widget.onTap,
borderRadius: _getBorderRadius(),
child: Padding(
padding: const EdgeInsets.all(UIConstants.spacing16),
child: LayoutBuilder(
builder: (context, constraints) {
final isTight = constraints.maxHeight < 150;
final isVeryTight = constraints.maxHeight < 130;
final spacing16 = isTight
? UIConstants.spacing8
: UIConstants.spacing16;
final spacing12 = isTight
? UIConstants.spacing6
: UIConstants.spacing12;
final iconSize = isTight
? UIConstants.iconSizeMedium
: UIConstants.iconSizeLarge;
final titleStyle =
theme.textTheme.titleMedium?.copyWith(
color: Colors.white,
fontWeight: FontWeight.w600,
fontSize: isVeryTight ? 12 : (isTight ? 14 : null),
);
final subtitleStyle =
theme.textTheme.bodyMedium?.copyWith(
color: Colors.white.withOpacity(0.8),
fontSize: isVeryTight ? 10 : (isTight ? 12 : null),
);
final valueStyle =
theme.textTheme.headlineSmall?.copyWith(
color: Colors.white,
fontWeight: FontWeight.w700,
fontSize: isVeryTight ? 16 : (isTight ? 18 : null),
);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Header Row
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// Icon
if (widget.icon != null)
Container(
padding: EdgeInsets.all(spacing12),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
borderRadius: BorderRadius.circular(
UIConstants.radius12),
),
child: Icon(
widget.icon,
color: iconColor,
size: iconSize,
),
),
// Trailing Widget
if (widget.trailing != null) widget.trailing!,
],
),
SizedBox(height: spacing16),
// Title
Text(
widget.title,
style: titleStyle,
maxLines: isTight ? 1 : 2,
overflow: TextOverflow.ellipsis,
),
// Subtitle
if (widget.subtitle != null && !isVeryTight) ...[
SizedBox(height: spacing12),
Text(
widget.subtitle!,
style: subtitleStyle,
maxLines: isTight ? 1 : 2,
overflow: TextOverflow.ellipsis,
),
],
// Value (animated if numeric)
SizedBox(height: spacing16),
if (widget.numericValue != null)
TweenAnimationBuilder<double>(
tween: Tween<double>(
begin: 0,
end: widget.numericValue!.toDouble()),
duration: widget.animationDuration ??
UIConstants.durationNormal,
curve: UIConstants.curveNormal,
builder: (context, value, child) {
final text =
_formatNumber(value, widget.valueSuffix);
final valueText = Text(
text,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: valueStyle,
);
return isTight
? FittedBox(
alignment: Alignment.centerLeft,
child: valueText)
: valueText;
},
)
else if (widget.value != null)
Builder(builder: (context) {
final valueText = Text(
widget.value!,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: valueStyle,
);
return isTight
? FittedBox(
alignment: Alignment.centerLeft,
child: valueText)
: valueText;
}),
// Loading Indicator
if (widget.isLoading) ...[
SizedBox(height: spacing16),
const LinearProgressIndicator(
backgroundColor: Colors.white24,
valueColor:
AlwaysStoppedAnimation<Color>(Colors.white),
),
],
],
);
},
),
),
),
),
),
),
);
},
);
}
String _formatNumber(double value, String? suffix) {
String formatted;
if (value >= 1000000) {
formatted = "${(value / 1000000).toStringAsFixed(1)}M";
} else if (value >= 1000) {
formatted = "${(value / 1000).toStringAsFixed(1)}K";
} else {
if (value == value.roundToDouble()) {
formatted = value.toInt().toString();
} else {
formatted = value.toStringAsFixed(1);
}
}
if (suffix != null && suffix.isNotEmpty) {
return "$formatted$suffix";
}
return formatted;
}
}