Files
monisuo/flutter_monisuo/lib/ui/components/material_input.dart

231 lines
6.8 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<String>? onChanged;
final VoidCallback? onTap;
final ValueChanged<String>? onSubmitted;
final List<TextInputFormatter>? 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<String>? onChanged;
final ValueChanged<String>? 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<MaterialPasswordInput> createState() => _MaterialPasswordInputState();
}
class _MaterialPasswordInputState extends State<MaterialPasswordInput> {
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,
),
);
}
}