Files
ai_english/client/lib/shared/widgets/custom_card.dart

440 lines
12 KiB
Dart
Raw Normal View History

2025-11-17 13:39:05 +08:00
import 'package:flutter/material.dart';
/// 自定义卡片组件
class CustomCard extends StatelessWidget {
final Widget child;
final EdgeInsetsGeometry? padding;
final EdgeInsetsGeometry? margin;
final Color? color;
final double? elevation;
final BorderRadius? borderRadius;
final Border? border;
final VoidCallback? onTap;
final bool isSelected;
final Color? selectedColor;
final double? width;
final double? height;
final BoxShadow? shadow;
const CustomCard({
super.key,
required this.child,
this.padding,
this.margin,
this.color,
this.elevation,
this.borderRadius,
this.border,
this.onTap,
this.isSelected = false,
this.selectedColor,
this.width,
this.height,
this.shadow,
});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final defaultBorderRadius = BorderRadius.circular(12);
Widget card = Container(
width: width,
height: height,
margin: margin,
decoration: BoxDecoration(
color: isSelected
? (selectedColor ?? theme.colorScheme.primaryContainer)
: (color ?? theme.colorScheme.surface),
borderRadius: borderRadius ?? defaultBorderRadius,
border: border ?? (isSelected
? Border.all(
color: theme.colorScheme.primary,
width: 2,
)
: Border.all(
color: theme.colorScheme.outline.withOpacity(0.2),
width: 1,
)),
boxShadow: shadow != null
? [shadow!]
: [
BoxShadow(
color: theme.colorScheme.shadow.withOpacity(0.1),
blurRadius: elevation ?? 4,
offset: const Offset(0, 2),
),
],
),
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: onTap,
borderRadius: borderRadius ?? defaultBorderRadius,
child: Padding(
padding: padding ?? const EdgeInsets.all(16),
child: child,
),
),
),
);
return card;
}
}
/// 信息卡片
class InfoCard extends StatelessWidget {
final String title;
final String? subtitle;
final Widget? leading;
final Widget? trailing;
final VoidCallback? onTap;
final EdgeInsetsGeometry? padding;
final EdgeInsetsGeometry? margin;
final Color? backgroundColor;
final bool isSelected;
const InfoCard({
super.key,
required this.title,
this.subtitle,
this.leading,
this.trailing,
this.onTap,
this.padding,
this.margin,
this.backgroundColor,
this.isSelected = false,
});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return CustomCard(
onTap: onTap,
padding: padding ?? const EdgeInsets.all(16),
margin: margin,
color: backgroundColor,
isSelected: isSelected,
child: Row(
children: [
if (leading != null) ...[
leading!,
const SizedBox(width: 12),
],
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
title,
style: theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w600,
color: isSelected
? theme.colorScheme.onPrimaryContainer
: theme.colorScheme.onSurface,
),
),
if (subtitle != null) ...[
const SizedBox(height: 4),
Text(
subtitle!,
style: theme.textTheme.bodyMedium?.copyWith(
color: isSelected
? theme.colorScheme.onPrimaryContainer.withOpacity(0.8)
: theme.colorScheme.onSurface.withOpacity(0.7),
),
),
],
],
),
),
if (trailing != null) ...[
const SizedBox(width: 12),
trailing!,
],
],
),
);
}
}
/// 统计卡片
class StatCard extends StatelessWidget {
final String title;
final String value;
final String? unit;
final IconData? icon;
final Color? iconColor;
final Color? backgroundColor;
final VoidCallback? onTap;
final EdgeInsetsGeometry? margin;
final Widget? trailing;
const StatCard({
super.key,
required this.title,
required this.value,
this.unit,
this.icon,
this.iconColor,
this.backgroundColor,
this.onTap,
this.margin,
this.trailing,
});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return CustomCard(
onTap: onTap,
margin: margin,
color: backgroundColor,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Row(
children: [
if (icon != null) ...[
Icon(
icon,
size: 20,
color: iconColor ?? theme.colorScheme.primary,
),
const SizedBox(width: 8),
],
Expanded(
child: Text(
title,
style: theme.textTheme.bodyMedium?.copyWith(
color: theme.colorScheme.onSurface.withOpacity(0.7),
),
),
),
if (trailing != null) trailing!,
],
),
const SizedBox(height: 8),
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
value,
style: theme.textTheme.headlineSmall?.copyWith(
fontWeight: FontWeight.bold,
color: theme.colorScheme.onSurface,
),
),
if (unit != null) ...[
const SizedBox(width: 4),
Padding(
padding: const EdgeInsets.only(bottom: 2),
child: Text(
unit!,
style: theme.textTheme.bodySmall?.copyWith(
color: theme.colorScheme.onSurface.withOpacity(0.6),
),
),
),
],
],
),
],
),
);
}
}
/// 功能卡片
class FeatureCard extends StatelessWidget {
final String title;
final String? description;
final IconData icon;
final Color? iconColor;
final Color? backgroundColor;
final VoidCallback? onTap;
final EdgeInsetsGeometry? margin;
final bool isEnabled;
final Widget? badge;
const FeatureCard({
super.key,
required this.title,
this.description,
required this.icon,
this.iconColor,
this.backgroundColor,
this.onTap,
this.margin,
this.isEnabled = true,
this.badge,
});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return CustomCard(
onTap: isEnabled ? onTap : null,
margin: margin,
color: backgroundColor,
child: Stack(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: (iconColor ?? theme.colorScheme.primary).withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: Icon(
icon,
size: 24,
color: isEnabled
? (iconColor ?? theme.colorScheme.primary)
: theme.colorScheme.onSurface.withOpacity(0.4),
),
),
const SizedBox(height: 12),
Text(
title,
style: theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w600,
color: isEnabled
? theme.colorScheme.onSurface
: theme.colorScheme.onSurface.withOpacity(0.4),
),
),
if (description != null) ...[
const SizedBox(height: 4),
Text(
description!,
style: theme.textTheme.bodySmall?.copyWith(
color: isEnabled
? theme.colorScheme.onSurface.withOpacity(0.7)
: theme.colorScheme.onSurface.withOpacity(0.4),
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
],
],
),
if (badge != null)
Positioned(
top: 0,
right: 0,
child: badge!,
),
],
),
);
}
}
/// 进度卡片
class ProgressCard extends StatelessWidget {
final String title;
final String? subtitle;
final double progress;
final String? progressText;
final Color? progressColor;
final Color? backgroundColor;
final VoidCallback? onTap;
final EdgeInsetsGeometry? margin;
final Widget? trailing;
const ProgressCard({
super.key,
required this.title,
this.subtitle,
required this.progress,
this.progressText,
this.progressColor,
this.backgroundColor,
this.onTap,
this.margin,
this.trailing,
});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return CustomCard(
onTap: onTap,
margin: margin,
color: backgroundColor,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w600,
),
),
if (subtitle != null) ...[
const SizedBox(height: 2),
Text(
subtitle!,
style: theme.textTheme.bodySmall?.copyWith(
color: theme.colorScheme.onSurface.withOpacity(0.7),
),
),
],
],
),
),
if (trailing != null) ...[
const SizedBox(width: 12),
trailing!,
],
],
),
const SizedBox(height: 12),
Row(
children: [
Expanded(
child: LinearProgressIndicator(
value: progress.clamp(0.0, 1.0),
backgroundColor: theme.colorScheme.surfaceVariant,
valueColor: AlwaysStoppedAnimation<Color>(
progressColor ?? theme.colorScheme.primary,
),
minHeight: 6,
),
),
if (progressText != null) ...[
const SizedBox(width: 12),
Text(
progressText!,
style: theme.textTheme.bodySmall?.copyWith(
fontWeight: FontWeight.w500,
color: theme.colorScheme.onSurface.withOpacity(0.8),
),
),
],
],
),
],
),
);
}
}