import 'package:flutter/material.dart'; import '/common/constants.dart'; import '/request/signup_signin.dart'; class SignTextField extends StatefulWidget { const SignTextField({ super.key, this.color = kPrimaryColor, this.obscureText = false, required this.type, this.isSignup = true, required this.labelText, required this.controller, required this.isError, }); final Color color; final bool obscureText; final String type; final bool isSignup; final String labelText; final TextEditingController controller; final void Function(String, bool) isError; @override State createState() => _SignTextFieldState(); } class _SignTextFieldState extends State { int _length = 0; String? _errorText; FocusNode? _node; @override void initState() { super.initState(); if (widget.isSignup) { _node = FocusNode(); _node!.addListener(_handleFocusChange); } } void _handleFocusChange() async { if (!widget.isSignup) { return; } switch (widget.type) { case 'username' || 'email': if (!_node!.hasFocus) { if (widget.controller.text.isNotEmpty && _errorText == null) { Map res = await hasAccountExisted(widget.type, widget.controller.text); if (widget.type == 'username') { setState(() { res['code'] == 10100 ? _errorText = null : _errorText = '用户名已存在'; }); } else { setState(() { res['code'] == 10100 ? _errorText = null : _errorText = '邮箱已被使用'; }); } } } default: null; } } @override void dispose() { if (widget.isSignup) { _node!.removeListener(_handleFocusChange); _node!.dispose(); } super.dispose(); } @override Widget build(BuildContext context) { return TextField( onChanged: (String newValue) { setState(() { _length = newValue.characters.length; }); setErrorText(); widget.isError( widget.type, (_errorText == null && widget.controller.text.isEmpty) || _errorText != null, ); }, controller: widget.controller, focusNode: _node, obscureText: widget.obscureText, cursorColor: widget.color, textAlignVertical: TextAlignVertical.bottom, keyboardType: initTextInputType(), decoration: InputDecoration( errorText: _errorText, labelText: widget.labelText, counterText: '$_length 字符', floatingLabelStyle: TextStyle(color: widget.color), focusedBorder: UnderlineInputBorder( borderSide: BorderSide(color: widget.color), ), ), ); } TextInputType initTextInputType() { switch (widget.type) { case 'email': return TextInputType.emailAddress; case 'code': return TextInputType.number; case _: return TextInputType.visiblePassword; } } void setErrorText() { if (widget.controller.text.isEmpty) { setState(() { _errorText = null; }); } switch (widget.type) { case 'username': if (widget.controller.text.contains(RegExp(r'\W+'))) { setState(() { _errorText = '用户名只允许字母, 数字和下划线'; }); } else if (_length < 5 || _length > 20) { setState(() { _errorText = '用户名需为8-20个字符'; }); } else { setState(() { _errorText = null; }); } break; case 'password': if (widget.controller.text.contains(RegExp(r'\s'))) { setState(() { _errorText = '密码不能包含空格'; }); } else if (_length < 8 || _length > 30) { setState(() { _errorText = '密码需为8-30个字符'; }); } else { setState(() { _errorText = null; }); } break; case 'email': var exp = RegExp( r'^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.[a-zA-Z0-9]{2,6}$'); if (!exp.hasMatch(widget.controller.text)) { setState(() { _errorText = '邮箱格式错误'; }); } else { setState(() { _errorText = null; }); } case 'code': if (!RegExp(r'\d{6}').hasMatch(widget.controller.text)) { setState(() { _errorText = '6为数字'; }); } else if (widget.controller.text.characters.length != 6) { _errorText = '6位数字'; } else { setState(() { _errorText = null; }); } } } } class CommonTextField extends StatelessWidget { const CommonTextField({ super.key, this.color = kPrimaryColor, this.obscureText = false, required this.labelText, required this.controller, }); final Color color; final bool obscureText; final String labelText; final TextEditingController controller; @override Widget build(BuildContext context) { return TextField( controller: controller, obscureText: obscureText, cursorColor: kSecondaryColor, textAlignVertical: TextAlignVertical.bottom, decoration: InputDecoration( helperText: '5-10位', errorText: 'xxx', labelText: labelText, floatingLabelStyle: const TextStyle(color: kSecondaryColor), focusedBorder: UnderlineInputBorder(borderSide: BorderSide(color: color))), ); } } class CommonElevatedButton extends StatelessWidget { const CommonElevatedButton({ super.key, required this.onPressed, required this.text, this.color = kPrimaryColor, }); final VoidCallback onPressed; final String text; final Color color; @override Widget build(BuildContext context) { return ElevatedButton( onPressed: onPressed, style: ElevatedButton.styleFrom( backgroundColor: color, elevation: 0, fixedSize: const Size(150, 20), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12))), child: Text( text, style: const TextStyle( fontSize: 20.0, letterSpacing: 10, color: kContentColorDark, ), ), ); } }