import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import '../../core/theme/app_color_scheme.dart'; import '../../core/theme/app_spacing.dart'; import '../../core/theme/app_theme.dart'; import '../../core/theme/app_theme_extension.dart'; /// Material Design 3 风格的输入框组件 /// /// 特点: /// - 圆角外边框(OutlineInputBorder) /// - Focus 时边框颜色变化 /// - 标签上浮动画 /// - 统一的视觉效果 class MaterialInput extends StatelessWidget { final TextEditingController? controller; final String? labelText; final String? hintText; final IconData? prefixIcon; final Widget? suffixIcon; final bool obscureText; final TextInputType? keyboardType; final int? maxLines; final int? maxLength; final bool enabled; final bool readOnly; final String? Function(String?)? validator; final ValueChanged? onChanged; final VoidCallback? onTap; final ValueChanged? onSubmitted; final List? inputFormatters; final FocusNode? focusNode; const MaterialInput({ super.key, this.controller, this.labelText, this.hintText, this.prefixIcon, this.suffixIcon, this.obscureText = false, this.keyboardType, this.maxLines = 1, this.maxLength, this.enabled = true, this.readOnly = false, this.validator, this.onChanged, this.onTap, this.onSubmitted, this.inputFormatters, this.focusNode, }); @override Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; final isDark = Theme.of(context).brightness == Brightness.dark; // Material Design 3 的颜色 final primaryColor = isDark ? AppColorScheme.up : colorScheme.primary; final borderColor = isDark ? AppColorScheme.darkOutline.withValues(alpha: 0.3) : colorScheme.outline.withValues(alpha: 0.5); final fillColor = isDark ? AppColorScheme.darkSurfaceContainerHigh.withValues(alpha: 0.3) : colorScheme.surfaceContainerHighest.withValues(alpha: 0.5); return TextFormField( controller: controller, focusNode: focusNode, obscureText: obscureText, keyboardType: keyboardType, maxLines: maxLines, maxLength: maxLength, enabled: enabled, readOnly: readOnly, validator: validator, onChanged: onChanged, onTap: onTap, onFieldSubmitted: onSubmitted, inputFormatters: inputFormatters, style: AppTextStyles.headlineMedium(context).copyWith( color: enabled ? colorScheme.onSurface : colorScheme.onSurface.withValues(alpha: 0.5), ), cursorColor: primaryColor, cursorWidth: 2.0, cursorHeight: 20, decoration: InputDecoration( // 标签(Material Design 3 的浮动标签) labelText: labelText, labelStyle: AppTextStyles.bodyLarge(context).copyWith( color: colorScheme.onSurfaceVariant, ), floatingLabelStyle: AppTextStyles.bodyMedium(context).copyWith( color: primaryColor, fontWeight: FontWeight.w600, ), // 提示文本 hintText: hintText, hintStyle: AppTextStyles.bodyLarge(context).copyWith( color: colorScheme.onSurfaceVariant.withValues(alpha: 0.6), ), // 前置图标 prefixIcon: prefixIcon != null ? Icon( prefixIcon, color: colorScheme.onSurfaceVariant, size: 22, ) : null, prefixIconColor: WidgetStateColor.resolveWith((states) { if (states.contains(WidgetState.focused)) { return primaryColor; } return colorScheme.onSurfaceVariant; }), // 后置图标 suffixIcon: suffixIcon, // 填充颜色 filled: true, fillColor: fillColor, // 内容内边距 contentPadding: const EdgeInsets.symmetric( horizontal: AppSpacing.lg, vertical: AppSpacing.md, ), // Material Design 3 边框样式 border: _buildBorder(borderColor, AppRadius.lg), enabledBorder: _buildBorder(borderColor, AppRadius.lg), focusedBorder: _buildBorder(primaryColor, AppRadius.lg, width: 2.0), errorBorder: _buildBorder(colorScheme.error, AppRadius.lg), focusedErrorBorder: _buildBorder(colorScheme.error, AppRadius.lg, width: 2.0), disabledBorder: _buildBorder( borderColor.withValues(alpha: 0.3), AppRadius.lg, ), // 错误文本样式 errorStyle: AppTextStyles.bodySmall(context).copyWith( color: colorScheme.error, ), // 计数器样式 counterStyle: AppTextStyles.bodySmall(context).copyWith( color: colorScheme.onSurfaceVariant, ), ), ); } OutlineInputBorder _buildBorder(Color color, double radius, {double width = 1.0}) { return OutlineInputBorder( borderRadius: BorderRadius.circular(radius), borderSide: BorderSide( color: color, width: width, ), ); } } /// Material Design 3 风格的密码输入框(带显示/隐藏切换) class MaterialPasswordInput extends StatefulWidget { final TextEditingController? controller; final String? labelText; final String? hintText; final IconData? prefixIcon; final String? Function(String?)? validator; final ValueChanged? onChanged; final ValueChanged? onSubmitted; final FocusNode? focusNode; const MaterialPasswordInput({ super.key, this.controller, this.labelText, this.hintText, this.prefixIcon, this.validator, this.onChanged, this.onSubmitted, this.focusNode, }); @override State createState() => _MaterialPasswordInputState(); } class _MaterialPasswordInputState extends State { bool _obscureText = true; @override Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; return MaterialInput( controller: widget.controller, labelText: widget.labelText, hintText: widget.hintText, prefixIcon: widget.prefixIcon ?? Icons.lock_outline, obscureText: _obscureText, validator: widget.validator, onChanged: widget.onChanged, onSubmitted: widget.onSubmitted, focusNode: widget.focusNode, suffixIcon: IconButton( icon: Icon( _obscureText ? Icons.visibility_off : Icons.visibility, color: colorScheme.onSurfaceVariant, size: 22, ), onPressed: () { setState(() { _obscureText = !_obscureText; }); }, splashRadius: 20, ), ); } }