Files
wushu/lib/widgets/common_widgets.dart
2026-03-30 02:35:31 +08:00

329 lines
8.1 KiB
Dart

import 'package:flutter/material.dart';
import '../constants/app_constants.dart';
// 自定义按钮组件
class CustomButton extends StatelessWidget {
final String text;
final VoidCallback? onPressed;
final IconData? icon;
final Color? backgroundColor;
final Color? textColor;
final double? borderRadius;
final bool isLoading;
final double? width;
final double? height;
const CustomButton({
super.key,
required this.text,
this.onPressed,
this.icon,
this.backgroundColor,
this.textColor,
this.borderRadius,
this.isLoading = false,
this.width,
this.height,
});
@override
Widget build(BuildContext context) {
return SizedBox(
width: width,
height: height ?? 48,
child: ElevatedButton(
onPressed: isLoading ? null : onPressed,
style: ElevatedButton.styleFrom(
backgroundColor: backgroundColor ?? AppConstants.primaryColor,
foregroundColor: textColor ?? Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(borderRadius ?? 8),
),
elevation: 2,
),
child: isLoading
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
),
)
: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (icon != null) ...[
Icon(icon, size: 18),
const SizedBox(width: 8),
],
Text(text),
],
),
),
);
}
}
// 自定义卡片组件
class CustomCard extends StatelessWidget {
final Widget child;
final EdgeInsetsGeometry? padding;
final EdgeInsetsGeometry? margin;
final double? borderRadius;
final Color? backgroundColor;
final VoidCallback? onTap;
final BoxBorder? border;
const CustomCard({
super.key,
required this.child,
this.padding,
this.margin,
this.borderRadius,
this.backgroundColor,
this.onTap,
this.border,
});
@override
Widget build(BuildContext context) {
return Container(
margin: margin ?? const EdgeInsets.all(8),
child: Material(
color: backgroundColor ?? AppConstants.surfaceColor,
borderRadius: BorderRadius.circular(borderRadius ?? 12),
elevation: 2,
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(borderRadius ?? 12),
child: Container(
padding: padding ?? const EdgeInsets.all(16),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(borderRadius ?? 12),
border: border,
),
child: child,
),
),
),
);
}
}
// 自定义输入框组件
class CustomTextField extends StatelessWidget {
final String? labelText;
final String? hintText;
final TextEditingController? controller;
final IconData? prefixIcon;
final IconData? suffixIcon;
final VoidCallback? onSuffixIconTap;
final bool obscureText;
final String? Function(String?)? validator;
final void Function(String)? onChanged;
final TextInputType? keyboardType;
final int? maxLines;
final bool enabled;
const CustomTextField({
super.key,
this.labelText,
this.hintText,
this.controller,
this.prefixIcon,
this.suffixIcon,
this.onSuffixIconTap,
this.obscureText = false,
this.validator,
this.onChanged,
this.keyboardType,
this.maxLines = 1,
this.enabled = true,
});
@override
Widget build(BuildContext context) {
return TextFormField(
controller: controller,
obscureText: obscureText,
validator: validator,
onChanged: onChanged,
keyboardType: keyboardType,
maxLines: maxLines,
enabled: enabled,
decoration: InputDecoration(
labelText: labelText,
hintText: hintText,
prefixIcon: prefixIcon != null ? Icon(prefixIcon) : null,
suffixIcon: suffixIcon != null
? IconButton(
icon: Icon(suffixIcon),
onPressed: onSuffixIconTap,
)
: null,
),
);
}
}
// 加载指示器组件
class LoadingIndicator extends StatelessWidget {
final String? message;
final Color? color;
const LoadingIndicator({
super.key,
this.message,
this.color,
});
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(
color ?? AppConstants.primaryColor,
),
),
if (message != null) ...[
const SizedBox(height: 16),
Text(
message!,
style: const TextStyle(
fontSize: 14,
color: Colors.grey,
),
),
],
],
),
);
}
}
// 空状态组件
class EmptyState extends StatelessWidget {
final String title;
final String? subtitle;
final IconData? icon;
final Widget? action;
const EmptyState({
super.key,
required this.title,
this.subtitle,
this.icon,
this.action,
});
@override
Widget build(BuildContext context) {
return Center(
child: Padding(
padding: const EdgeInsets.all(32),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
icon ?? Icons.inbox_outlined,
size: 64,
color: Colors.grey[400],
),
const SizedBox(height: 16),
Text(
title,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.w500,
color: Colors.black87,
),
textAlign: TextAlign.center,
),
if (subtitle != null) ...[
const SizedBox(height: 8),
Text(
subtitle!,
style: const TextStyle(
fontSize: 14,
color: Colors.grey,
),
textAlign: TextAlign.center,
),
],
if (action != null) ...[
const SizedBox(height: 24),
action!,
],
],
),
),
);
}
}
// 错误状态组件
class ErrorState extends StatelessWidget {
final String title;
final String? subtitle;
final VoidCallback? onRetry;
const ErrorState({
super.key,
required this.title,
this.subtitle,
this.onRetry,
});
@override
Widget build(BuildContext context) {
return Center(
child: Padding(
padding: const EdgeInsets.all(32),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.error_outline,
size: 64,
color: AppConstants.errorColor,
),
const SizedBox(height: 16),
Text(
title,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.w500,
color: Colors.black87,
),
textAlign: TextAlign.center,
),
if (subtitle != null) ...[
const SizedBox(height: 8),
Text(
subtitle!,
style: const TextStyle(
fontSize: 14,
color: Colors.grey,
),
textAlign: TextAlign.center,
),
],
if (onRetry != null) ...[
const SizedBox(height: 24),
CustomButton(
text: AppConstants.retryText,
onPressed: onRetry,
icon: AppConstants.refreshIcon,
),
],
],
),
),
);
}
}