gcx/frontend/mobile/lib/features/auth/presentation/pages/register_page.dart

287 lines
9.5 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 '../../../../app/theme/app_colors.dart';
import '../../../../app/theme/app_typography.dart';
import '../../../../app/theme/app_spacing.dart';
import '../../../../shared/widgets/genex_button.dart';
/// A1. 手机号注册页
///
/// 手机号输入、获取验证码、设置密码、用户协议勾选
/// 注册成功后后台静默创建MPC钱包
class RegisterPage extends StatefulWidget {
final bool isEmail;
const RegisterPage({super.key, this.isEmail = false});
@override
State<RegisterPage> createState() => _RegisterPageState();
}
class _RegisterPageState extends State<RegisterPage> {
final _accountController = TextEditingController();
final _codeController = TextEditingController();
final _passwordController = TextEditingController();
bool _obscurePassword = true;
bool _agreeTerms = false;
@override
void dispose() {
_accountController.dispose();
_codeController.dispose();
_passwordController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: const Icon(Icons.arrow_back_ios_new_rounded, size: 20),
onPressed: () => Navigator.of(context).pop(),
),
),
body: SafeArea(
child: SingleChildScrollView(
padding: AppSpacing.pagePadding,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 16),
Text('创建账号', style: AppTypography.displayMedium),
const SizedBox(height: 8),
Text(
widget.isEmail ? '使用邮箱注册 Genex 账号' : '使用手机号注册 Genex 账号',
style: AppTypography.bodyLarge.copyWith(color: AppColors.textSecondary),
),
const SizedBox(height: 40),
// Step indicator
_buildStepIndicator(),
const SizedBox(height: 32),
// Account Input (Phone/Email)
Text(
widget.isEmail ? '邮箱地址' : '手机号',
style: AppTypography.labelMedium,
),
const SizedBox(height: 8),
TextField(
controller: _accountController,
keyboardType:
widget.isEmail ? TextInputType.emailAddress : TextInputType.phone,
decoration: InputDecoration(
hintText: widget.isEmail ? '请输入邮箱地址' : '请输入手机号',
prefixIcon: Icon(
widget.isEmail ? Icons.email_outlined : Icons.phone_android_rounded,
color: AppColors.textTertiary,
),
),
),
const SizedBox(height: 20),
// Verification Code
Text('验证码', style: AppTypography.labelMedium),
const SizedBox(height: 8),
Row(
children: [
Expanded(
child: TextField(
controller: _codeController,
keyboardType: TextInputType.number,
maxLength: 6,
decoration: const InputDecoration(
hintText: '请输入6位验证码',
counterText: '',
prefixIcon: Icon(Icons.shield_outlined, color: AppColors.textTertiary),
),
),
),
const SizedBox(width: 12),
SizedBox(
height: AppSpacing.inputHeight,
child: GenexButton(
label: '获取验证码',
variant: GenexButtonVariant.secondary,
size: GenexButtonSize.medium,
fullWidth: false,
onPressed: () {},
),
),
],
),
const SizedBox(height: 20),
// Password
Text('设置密码', style: AppTypography.labelMedium),
const SizedBox(height: 8),
TextField(
controller: _passwordController,
obscureText: _obscurePassword,
decoration: InputDecoration(
hintText: '8-20位含字母和数字',
prefixIcon: const Icon(Icons.lock_outline_rounded, color: AppColors.textTertiary),
suffixIcon: IconButton(
icon: Icon(
_obscurePassword
? Icons.visibility_off_outlined
: Icons.visibility_outlined,
color: AppColors.textTertiary,
size: 20,
),
onPressed: () => setState(() => _obscurePassword = !_obscurePassword),
),
),
),
const SizedBox(height: 8),
_buildPasswordStrength(),
const SizedBox(height: 32),
// Terms Agreement
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 20,
height: 20,
child: Checkbox(
value: _agreeTerms,
onChanged: (v) => setState(() => _agreeTerms = v ?? false),
activeColor: AppColors.primary,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)),
),
),
const SizedBox(width: 8),
Expanded(
child: GestureDetector(
onTap: () => setState(() => _agreeTerms = !_agreeTerms),
child: RichText(
text: TextSpan(
style: AppTypography.bodySmall,
children: [
const TextSpan(text: '我已阅读并同意 '),
TextSpan(
text: '《用户协议》',
style: AppTypography.bodySmall.copyWith(color: AppColors.primary),
),
const TextSpan(text: ''),
TextSpan(
text: '《隐私政策》',
style: AppTypography.bodySmall.copyWith(color: AppColors.primary),
),
],
),
),
),
),
],
),
const SizedBox(height: 32),
// Register Button
GenexButton(
label: '注册',
onPressed: _agreeTerms ? () {
// Auth: register → silent MPC wallet creation
} : null,
),
const SizedBox(height: 40),
],
),
),
),
);
}
Widget _buildStepIndicator() {
return Row(
children: [
_buildStep(1, '验证', true),
_buildStepLine(true),
_buildStep(2, '设密码', true),
_buildStepLine(false),
_buildStep(3, '完成', false),
],
);
}
Widget _buildStep(int number, String label, bool active) {
return Column(
children: [
Container(
width: 28,
height: 28,
decoration: BoxDecoration(
color: active ? AppColors.primary : AppColors.gray200,
shape: BoxShape.circle,
),
child: Center(
child: Text(
'$number',
style: TextStyle(
color: active ? Colors.white : AppColors.textTertiary,
fontSize: 13,
fontWeight: FontWeight.w600,
),
),
),
),
const SizedBox(height: 4),
Text(
label,
style: AppTypography.caption.copyWith(
color: active ? AppColors.primary : AppColors.textTertiary,
),
),
],
);
}
Widget _buildStepLine(bool active) {
return Expanded(
child: Container(
height: 2,
margin: const EdgeInsets.only(bottom: 18),
color: active ? AppColors.primary : AppColors.gray200,
),
);
}
Widget _buildPasswordStrength() {
final password = _passwordController.text;
final hasLength = password.length >= 8;
final hasLetter = RegExp(r'[a-zA-Z]').hasMatch(password);
final hasDigit = RegExp(r'\d').hasMatch(password);
return Row(
children: [
_buildCheck('8位以上', hasLength),
const SizedBox(width: 16),
_buildCheck('含字母', hasLetter),
const SizedBox(width: 16),
_buildCheck('含数字', hasDigit),
],
);
}
Widget _buildCheck(String label, bool passed) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
passed ? Icons.check_circle_rounded : Icons.circle_outlined,
size: 14,
color: passed ? AppColors.success : AppColors.textTertiary,
),
const SizedBox(width: 4),
Text(
label,
style: AppTypography.caption.copyWith(
color: passed ? AppColors.success : AppColors.textTertiary,
),
),
],
);
}
}