Files
ai_english/client/lib/features/auth/screens/register_screen.dart
2025-11-17 13:39:05 +08:00

519 lines
19 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 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../core/theme/app_colors.dart';
import '../../../core/theme/app_text_styles.dart';
import '../../../core/theme/app_dimensions.dart';
import '../../../core/widgets/custom_button.dart';
import '../../../core/widgets/custom_text_field.dart';
import '../../../core/routes/app_routes.dart';
import '../providers/auth_provider.dart';
/// 注册页面
class RegisterScreen extends ConsumerStatefulWidget {
const RegisterScreen({super.key});
@override
ConsumerState<RegisterScreen> createState() => _RegisterScreenState();
}
class _RegisterScreenState extends ConsumerState<RegisterScreen> {
final _formKey = GlobalKey<FormState>();
final _usernameController = TextEditingController();
final _nicknameController = TextEditingController();
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
final _confirmPasswordController = TextEditingController();
bool _obscurePassword = true;
bool _obscureConfirmPassword = true;
bool _agreeToTerms = false;
bool _isLoading = false;
// 密码强度提示
bool _hasMinLength = false;
bool _hasNumber = false;
bool _hasLowerCase = false;
bool _hasUpperCase = false;
bool _hasSpecialChar = false;
@override
void initState() {
super.initState();
// 监听密码输入,实时验证
_passwordController.addListener(_validatePasswordStrength);
}
/// 验证密码强度
void _validatePasswordStrength() {
final password = _passwordController.text;
setState(() {
_hasMinLength = password.length >= 8;
_hasNumber = RegExp(r'[0-9]').hasMatch(password);
_hasLowerCase = RegExp(r'[a-z]').hasMatch(password);
_hasUpperCase = RegExp(r'[A-Z]').hasMatch(password);
_hasSpecialChar = RegExp(r'[!@#\$%^&*()_+\-=\[\]{};:"\\|,.<>\/?]').hasMatch(password);
});
}
@override
void dispose() {
_usernameController.dispose();
_nicknameController.dispose();
_emailController.dispose();
_passwordController.dispose();
_confirmPasswordController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.background,
appBar: AppBar(
backgroundColor: Colors.transparent,
elevation: 0,
leading: IconButton(
icon: const Icon(Icons.arrow_back_ios, color: AppColors.onSurface),
onPressed: () => Navigator.of(context).pop(),
),
),
body: SafeArea(
child: SingleChildScrollView(
padding: const EdgeInsets.all(AppDimensions.pagePadding),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 标题
Text(
'创建账户',
style: AppTextStyles.headlineLarge.copyWith(
color: AppColors.onSurface,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: AppDimensions.spacingSm),
Text(
'加入AI英语学习平台开启智能学习之旅',
style: AppTextStyles.bodyMedium.copyWith(
color: AppColors.onSurfaceVariant,
),
),
const SizedBox(height: AppDimensions.spacingXl),
// 用户名输入框
CustomTextField(
controller: _usernameController,
labelText: '用户名',
hintText: '请输入用户名',
prefixIcon: Icons.person_outline,
validator: (value) {
if (value == null || value.isEmpty) {
return '请输入用户名';
}
if (value.length < 3) {
return '用户名至少3个字符';
}
if (value.length > 20) {
return '用户名不能超过20个字符';
}
if (!RegExp(r'^[a-zA-Z0-9_]+$').hasMatch(value)) {
return '用户名只能包含字母、数字和下划线';
}
return null;
},
),
const SizedBox(height: AppDimensions.spacingMd),
// 昵称输入框
CustomTextField(
controller: _nicknameController,
labelText: '昵称',
hintText: '请输入昵称',
prefixIcon: Icons.badge_outlined,
validator: (value) {
if (value == null || value.isEmpty) {
return '请输入昵称';
}
if (value.length < 2) {
return '昵称至少2个字符';
}
if (value.length > 20) {
return '昵称不能超过20个字符';
}
return null;
},
),
const SizedBox(height: AppDimensions.spacingMd),
// 邮箱输入框
CustomTextField(
controller: _emailController,
labelText: '邮箱',
hintText: '请输入邮箱地址',
prefixIcon: Icons.email_outlined,
keyboardType: TextInputType.emailAddress,
validator: (value) {
if (value == null || value.isEmpty) {
return '请输入邮箱地址';
}
if (!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(value)) {
return '请输入有效的邮箱地址';
}
return null;
},
),
const SizedBox(height: AppDimensions.spacingMd),
// 密码输入框
CustomTextField(
controller: _passwordController,
labelText: '密码',
hintText: '请输入密码',
prefixIcon: Icons.lock_outline,
obscureText: _obscurePassword,
suffixIcon: IconButton(
icon: Icon(
_obscurePassword ? Icons.visibility_off : Icons.visibility,
color: AppColors.onSurfaceVariant,
),
onPressed: () {
setState(() {
_obscurePassword = !_obscurePassword;
});
},
),
validator: (value) {
if (value == null || value.isEmpty) {
return '请输入密码';
}
if (value.length < 8) {
return '密码至少8个字符';
}
// 至少包含一个数字
if (!RegExp(r'[0-9]').hasMatch(value)) {
return '密码必须包含数字';
}
// 至少包含一个小写字母
if (!RegExp(r'[a-z]').hasMatch(value)) {
return '密码必须包含小写字母';
}
// 至少包含一个大写字母
if (!RegExp(r'[A-Z]').hasMatch(value)) {
return '密码必须包含大写字母';
}
// 至少包含一个特殊字符
if (!RegExp(r'[!@#\$%^&*()_+\-=\[\]{};:"\\|,.<>\/?]').hasMatch(value)) {
return '密码必须包含特殊字符 (!@#\$%^&*等)';
}
return null;
},
),
const SizedBox(height: 8),
// 密码强度提示
Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
decoration: BoxDecoration(
color: Colors.grey[50],
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.grey[200]!),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'密码必须包含:',
style: AppTextStyles.bodySmall.copyWith(
fontWeight: FontWeight.w600,
color: Colors.grey[700],
),
),
const SizedBox(height: 8),
_buildPasswordRequirement('至少8个字符', _hasMinLength),
_buildPasswordRequirement('至少一个数字', _hasNumber),
_buildPasswordRequirement('至少一个小写字母', _hasLowerCase),
_buildPasswordRequirement('至少一个大写字母', _hasUpperCase),
_buildPasswordRequirement('至少一个特殊字符 (!@#\$%^&*等)', _hasSpecialChar),
],
),
),
const SizedBox(height: AppDimensions.spacingMd),
// 确认密码输入框
CustomTextField(
controller: _confirmPasswordController,
labelText: '确认密码',
hintText: '请再次输入密码',
prefixIcon: Icons.lock_outline,
obscureText: _obscureConfirmPassword,
suffixIcon: IconButton(
icon: Icon(
_obscureConfirmPassword ? Icons.visibility_off : Icons.visibility,
color: AppColors.onSurfaceVariant,
),
onPressed: () {
setState(() {
_obscureConfirmPassword = !_obscureConfirmPassword;
});
},
),
validator: (value) {
if (value == null || value.isEmpty) {
return '请确认密码';
}
if (value != _passwordController.text) {
return '两次输入的密码不一致';
}
return null;
},
),
const SizedBox(height: AppDimensions.spacingMd),
// 同意条款
Row(
children: [
Checkbox(
value: _agreeToTerms,
onChanged: (value) {
setState(() {
_agreeToTerms = value ?? false;
});
},
activeColor: AppColors.primary,
),
Expanded(
child: GestureDetector(
onTap: () {
setState(() {
_agreeToTerms = !_agreeToTerms;
});
},
child: RichText(
text: TextSpan(
style: AppTextStyles.bodySmall.copyWith(
color: AppColors.onSurfaceVariant,
),
children: [
const TextSpan(text: '我已阅读并同意'),
TextSpan(
text: '《用户协议》',
style: TextStyle(
color: AppColors.primary,
decoration: TextDecoration.underline,
),
),
const TextSpan(text: ''),
TextSpan(
text: '《隐私政策》',
style: TextStyle(
color: AppColors.primary,
decoration: TextDecoration.underline,
),
),
],
),
),
),
),
],
),
const SizedBox(height: AppDimensions.spacingLg),
// 注册按钮
CustomButton(
text: '注册',
onPressed: _agreeToTerms ? _handleRegister : null,
isLoading: _isLoading,
width: double.infinity,
),
const SizedBox(height: AppDimensions.spacingMd),
// 分割线
Row(
children: [
const Expanded(child: Divider(color: AppColors.divider)),
Padding(
padding: const EdgeInsets.symmetric(horizontal: AppDimensions.buttonPadding),
child: Text(
'',
style: AppTextStyles.bodySmall.copyWith(
color: AppColors.onSurfaceVariant,
),
),
),
const Expanded(child: Divider(color: AppColors.divider)),
],
),
const SizedBox(height: AppDimensions.spacingMd),
// 第三方注册
Row(
children: [
Expanded(
child: CustomButton(
text: '微信注册',
onPressed: () => _handleSocialRegister('wechat'),
backgroundColor: const Color(0xFF07C160),
textColor: Colors.white,
icon: Icons.wechat,
),
),
const SizedBox(width: AppDimensions.spacingMd),
Expanded(
child: CustomButton(
text: 'QQ注册',
onPressed: () => _handleSocialRegister('qq'),
backgroundColor: const Color(0xFF12B7F5),
textColor: Colors.white,
icon: Icons.chat,
),
),
],
),
const SizedBox(height: AppDimensions.spacingLg),
// 登录链接
Center(
child: GestureDetector(
onTap: () {
Navigator.of(context).pop();
},
child: RichText(
text: TextSpan(
style: AppTextStyles.bodyMedium.copyWith(
color: AppColors.onSurfaceVariant,
),
children: [
const TextSpan(text: '已有账户?'),
TextSpan(
text: '立即登录',
style: TextStyle(
color: AppColors.primary,
fontWeight: FontWeight.w600,
),
),
],
),
),
),
),
],
),
),
),
),
);
}
/// 构建密码要求项
Widget _buildPasswordRequirement(String text, bool isMet) {
return Padding(
padding: const EdgeInsets.only(bottom: 4),
child: Row(
children: [
Icon(
isMet ? Icons.check_circle : Icons.circle_outlined,
size: 16,
color: isMet ? AppColors.success : Colors.grey[400],
),
const SizedBox(width: 8),
Text(
text,
style: AppTextStyles.bodySmall.copyWith(
color: isMet ? AppColors.success : Colors.grey[600],
fontSize: 13,
),
),
],
),
);
}
/// 处理注册
Future<void> _handleRegister() async {
if (!_formKey.currentState!.validate()) {
return;
}
if (!_agreeToTerms) {
_showSnackBar('请先同意用户协议和隐私政策');
return;
}
setState(() {
_isLoading = true;
});
try {
await ref.read(authProvider.notifier).register(
username: _usernameController.text.trim(),
email: _emailController.text.trim(),
password: _passwordController.text,
nickname: _nicknameController.text.trim(),
);
if (mounted) {
_showSnackBar('注册成功,正在自动登录...', isSuccess: true);
// 注册成功后自动登录
try {
await ref.read(authProvider.notifier).login(
account: _usernameController.text.trim(),
password: _passwordController.text,
);
if (mounted) {
// 登录成功,跳转到首页
Navigator.of(context).pushReplacementNamed(Routes.home);
}
} catch (loginError) {
if (mounted) {
// 自动登录失败,跳转到登录页
_showSnackBar('注册成功,请登录', isSuccess: true);
Future.delayed(const Duration(milliseconds: 1500), () {
if (mounted) {
Navigator.of(context).pushReplacementNamed(Routes.login);
}
});
}
}
}
} catch (e) {
if (mounted) {
_showSnackBar(e.toString());
}
} finally {
if (mounted) {
setState(() {
_isLoading = false;
});
}
}
}
/// 处理第三方注册
Future<void> _handleSocialRegister(String provider) async {
try {
// TODO: 实现第三方注册逻辑
_showSnackBar('第三方注册功能即将上线');
} catch (e) {
_showSnackBar(e.toString());
}
}
/// 显示提示信息
void _showSnackBar(String message, {bool isSuccess = false}) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
backgroundColor: isSuccess ? AppColors.success : AppColors.error,
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.0),
),
),
);
}
}