2023-06-21 17:44:28 +08:00
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
|
2024-04-09 17:23:28 +08:00
|
|
|
import '/common/constants.dart';
|
|
|
|
import '/request/signup_signin.dart';
|
2023-06-21 17:44:28 +08:00
|
|
|
|
|
|
|
class SignTextField extends StatefulWidget {
|
|
|
|
const SignTextField({
|
|
|
|
super.key,
|
|
|
|
this.color = kPrimaryColor,
|
|
|
|
this.obscureText = false,
|
|
|
|
required this.type,
|
2023-07-04 11:51:11 +08:00
|
|
|
this.isSignup = true,
|
2023-06-21 17:44:28 +08:00
|
|
|
required this.labelText,
|
|
|
|
required this.controller,
|
|
|
|
required this.isError,
|
|
|
|
});
|
|
|
|
|
|
|
|
final Color color;
|
|
|
|
final bool obscureText;
|
|
|
|
final String type;
|
2023-07-04 11:51:11 +08:00
|
|
|
final bool isSignup;
|
2023-06-21 17:44:28 +08:00
|
|
|
final String labelText;
|
|
|
|
final TextEditingController controller;
|
|
|
|
final void Function(String, bool) isError;
|
|
|
|
|
|
|
|
@override
|
|
|
|
State<SignTextField> createState() => _SignTextFieldState();
|
|
|
|
}
|
|
|
|
|
|
|
|
class _SignTextFieldState extends State<SignTextField> {
|
|
|
|
int _length = 0;
|
|
|
|
String? _errorText;
|
2023-07-04 11:51:11 +08:00
|
|
|
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) {
|
2023-07-11 23:39:36 +08:00
|
|
|
Map<String, dynamic> res =
|
2023-07-04 11:51:11 +08:00
|
|
|
await hasAccountExisted(widget.type, widget.controller.text);
|
|
|
|
if (widget.type == 'username') {
|
|
|
|
setState(() {
|
2024-03-16 16:46:53 +08:00
|
|
|
res['code'] == 10100
|
|
|
|
? _errorText = null
|
|
|
|
: _errorText = '用户名已存在';
|
2023-07-04 11:51:11 +08:00
|
|
|
});
|
|
|
|
} else {
|
|
|
|
setState(() {
|
2024-03-16 16:46:53 +08:00
|
|
|
res['code'] == 10100
|
|
|
|
? _errorText = null
|
|
|
|
: _errorText = '邮箱已被使用';
|
2023-07-04 11:51:11 +08:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
default:
|
|
|
|
null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
void dispose() {
|
|
|
|
if (widget.isSignup) {
|
|
|
|
_node!.removeListener(_handleFocusChange);
|
|
|
|
_node!.dispose();
|
|
|
|
}
|
|
|
|
super.dispose();
|
|
|
|
}
|
2023-06-21 17:44:28 +08:00
|
|
|
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
return TextField(
|
|
|
|
onChanged: (String newValue) {
|
|
|
|
setState(() {
|
|
|
|
_length = newValue.characters.length;
|
|
|
|
});
|
|
|
|
setErrorText();
|
|
|
|
widget.isError(
|
2023-07-04 11:51:11 +08:00
|
|
|
widget.type,
|
|
|
|
(_errorText == null && widget.controller.text.isEmpty) ||
|
|
|
|
_errorText != null,
|
|
|
|
);
|
2023-06-21 17:44:28 +08:00
|
|
|
},
|
|
|
|
controller: widget.controller,
|
2023-07-04 11:51:11 +08:00
|
|
|
focusNode: _node,
|
2023-06-21 17:44:28 +08:00
|
|
|
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;
|
|
|
|
|
2023-07-04 11:51:11 +08:00
|
|
|
case 'password':
|
2023-06-21 17:44:28 +08:00
|
|
|
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;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-16 16:46:53 +08:00
|
|
|
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))),
|
|
|
|
);
|
|
|
|
}
|
2023-06-21 17:44:28 +08:00
|
|
|
}
|
|
|
|
|
2024-03-16 16:46:53 +08:00
|
|
|
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,
|
|
|
|
),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
2023-06-21 17:44:28 +08:00
|
|
|
}
|