import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; import 'package:nomo/config/theme/theme_extensions/theme_extension.dart'; import '../../config/theme/dark_theme_colors.dart'; import 'click_opacity.dart'; class IXTextField extends StatefulWidget { final String hintText; final IconData? prefixIcon; final bool isPassword; final TextEditingController controller; final FocusNode? focusNode; final TextInputType keyboardType; final TextInputAction textInputAction; final Function(String)? onChanged; final bool showClearButton; final String? errorText; final bool enabled; final bool isEmail; final String? tipText; final bool Function(String)? validator; const IXTextField({ super.key, required this.hintText, this.prefixIcon, this.isPassword = false, required this.controller, this.focusNode, this.keyboardType = TextInputType.text, this.textInputAction = TextInputAction.done, this.onChanged, this.showClearButton = true, this.errorText, this.enabled = true, this.isEmail = false, this.tipText, this.validator, }); @override State createState() => _IXTextFieldState(); } class _IXTextFieldState extends State { bool _obscureText = true; bool _hasFocus = false; bool _hasError = false; @override void initState() { super.initState(); widget.focusNode?.addListener(_onFocusChange); // 初始验证 if (widget.controller.text.isNotEmpty) { _validateInput(widget.controller.text); } } // 添加 didUpdateWidget 监听属性变化 @override void didUpdateWidget(IXTextField oldWidget) { super.didUpdateWidget(oldWidget); if (widget.controller.text.isNotEmpty) { _validateInput(widget.controller.text); } } @override void dispose() { widget.focusNode?.removeListener(_onFocusChange); super.dispose(); } void _onFocusChange() { setState(() { _hasFocus = widget.focusNode?.hasFocus ?? false; // 当失去焦点时进行验证 if (!_hasFocus && widget.controller.text.isNotEmpty) { _validateInput(widget.controller.text); } }); } void _validateInput(String value) { if (widget.validator != null) { // 使用自定义验证器 setState(() { _hasError = !widget.validator!(value); }); } else if (widget.isEmail) { // 默认电子邮件验证 final emailRegex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$'); setState(() { _hasError = !emailRegex.hasMatch(value); }); } else { setState(() {}); } } @override Widget build(BuildContext context) { // 确定边框颜色 Color borderColor = Get.reactiveTheme.dividerColor; Color bgColor = Get.reactiveTheme.highlightColor; if (_hasFocus) { borderColor = Get.reactiveTheme.shadowColor; bgColor = Get.reactiveTheme.highlightColor; } return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( decoration: BoxDecoration( color: bgColor, borderRadius: BorderRadius.circular(12.r), border: Border.all(color: borderColor, width: 1.0), ), alignment: Alignment.center, height: 50.w, child: TextField( controller: widget.controller, focusNode: widget.focusNode, obscureText: widget.isPassword && _obscureText, keyboardType: widget.keyboardType, cursorHeight: 14, enableSuggestions: false, autocorrect: false, textInputAction: widget.textInputAction, inputFormatters: widget.isPassword ? [LengthLimitingTextInputFormatter(20)] : [], onTap: () { // 不要先unfocus if (widget.focusNode != null) { widget.focusNode!.requestFocus(); Future.delayed(const Duration(milliseconds: 150), () { // 每次需要点击两次才能显示键盘 SystemChannels.textInput.invokeMethod('TextInput.show'); }); } }, onChanged: (value) { if (widget.onChanged != null) { widget.onChanged!(value); } // 实时验证 if (value.isNotEmpty) { _validateInput(value); } else { setState(() { _hasError = false; }); } }, enabled: widget.enabled, style: TextStyle( color: Get.reactiveTheme.textTheme.bodyLarge!.color, fontSize: 14, height: 1.6, fontWeight: FontWeight.w400, ), decoration: InputDecoration( hintText: widget.hintText, hintStyle: TextStyle( color: Get.reactiveTheme.hintColor, fontSize: 14, height: 1.6, fontWeight: FontWeight.w400, ), prefixIcon: widget.prefixIcon != null ? Icon( widget.prefixIcon, color: Get.reactiveTheme.hintColor, size: 20, ) : null, suffixIcon: _buildSuffixIcon(), border: InputBorder.none, contentPadding: const EdgeInsets.symmetric( vertical: 12, horizontal: 16, ), isDense: true, ), cursorColor: Get.reactiveTheme.shadowColor, ), ), if (!_hasError && _hasFocus && widget.tipText != null) Padding( padding: EdgeInsets.only(top: 10.w), child: Text( widget.tipText!, style: TextStyle( color: Get.reactiveTheme.shadowColor, fontSize: 13.sp, height: 1.4, fontWeight: FontWeight.w400, ), ), ), if (widget.errorText != null && _hasError) Padding( padding: EdgeInsets.only(top: 10.w), child: Text( widget.errorText!, style: TextStyle( color: DarkThemeColors.errorColor, fontSize: 13.sp, height: 1.4, fontWeight: FontWeight.w400, ), ), ), ], ); } Widget? _buildSuffixIcon() { if (widget.isPassword) { return Row( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.end, children: [ if (widget.showClearButton && widget.controller.text.isNotEmpty) ClickOpacity( child: Icon( Icons.clear, color: Get.reactiveTheme.hintColor, size: 20, ), onTap: () { widget.controller.clear(); if (widget.onChanged != null) { widget.onChanged!(''); } setState(() { _hasError = false; }); }, ), 8.horizontalSpace, ClickOpacity( child: Icon( _obscureText ? Icons.visibility_off : Icons.visibility, color: Get.reactiveTheme.hintColor, size: 20, ), onTap: () { setState(() { _obscureText = !_obscureText; }); }, ), 16.horizontalSpace, ], ); } else if (widget.showClearButton && widget.controller.text.isNotEmpty) { return Row( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.end, children: [ ClickOpacity( child: Icon( Icons.clear, color: Get.reactiveTheme.hintColor, size: 20, ), onTap: () { widget.controller.clear(); if (widget.onChanged != null) { widget.onChanged!(''); } setState(() { _hasError = false; }); }, ), 16.horizontalSpace, ], ); } return null; } }