311 lines
11 KiB
Dart
311 lines
11 KiB
Dart
import 'package:flutter/material.dart';
|
||
import 'package:go_router/go_router.dart';
|
||
|
||
class RegisterScreen extends StatefulWidget {
|
||
const RegisterScreen({super.key});
|
||
|
||
@override
|
||
State<RegisterScreen> createState() => _RegisterScreenState();
|
||
}
|
||
|
||
class _RegisterScreenState extends State<RegisterScreen> {
|
||
final _formKey = GlobalKey<FormState>();
|
||
final _emailController = TextEditingController();
|
||
final _passwordController = TextEditingController();
|
||
final _confirmPasswordController = TextEditingController();
|
||
final _displayNameController = TextEditingController();
|
||
bool _isLoading = false;
|
||
bool _obscurePassword = true;
|
||
bool _obscureConfirmPassword = true;
|
||
bool _acceptTerms = false;
|
||
|
||
@override
|
||
void dispose() {
|
||
_emailController.dispose();
|
||
_passwordController.dispose();
|
||
_confirmPasswordController.dispose();
|
||
_displayNameController.dispose();
|
||
super.dispose();
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return Scaffold(
|
||
backgroundColor: Theme.of(context).colorScheme.background,
|
||
appBar: AppBar(
|
||
backgroundColor: Colors.transparent,
|
||
elevation: 0,
|
||
leading: IconButton(
|
||
icon: const Icon(Icons.arrow_back),
|
||
onPressed: () => context.go('/login'),
|
||
),
|
||
),
|
||
body: SafeArea(
|
||
child: SingleChildScrollView(
|
||
padding: const EdgeInsets.all(24.0),
|
||
child: Form(
|
||
key: _formKey,
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||
children: [
|
||
// Title
|
||
Text(
|
||
'建立新帳號',
|
||
style: Theme.of(context).textTheme.headlineMedium?.copyWith(
|
||
fontWeight: FontWeight.bold,
|
||
),
|
||
textAlign: TextAlign.center,
|
||
),
|
||
const SizedBox(height: 8),
|
||
Text(
|
||
'開始您的語言學習之旅',
|
||
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
|
||
color: Theme.of(context).colorScheme.onBackground.withOpacity(0.7),
|
||
),
|
||
textAlign: TextAlign.center,
|
||
),
|
||
|
||
const SizedBox(height: 32),
|
||
|
||
// Display Name Field
|
||
TextFormField(
|
||
controller: _displayNameController,
|
||
decoration: const InputDecoration(
|
||
labelText: '顯示名稱',
|
||
hintText: '請輸入您的顯示名稱',
|
||
prefixIcon: Icon(Icons.person_outlined),
|
||
),
|
||
validator: (value) {
|
||
if (value == null || value.isEmpty) {
|
||
return '請輸入顯示名稱';
|
||
}
|
||
if (value.length < 2) {
|
||
return '顯示名稱至少需要2個字符';
|
||
}
|
||
return null;
|
||
},
|
||
),
|
||
|
||
const SizedBox(height: 16),
|
||
|
||
// Email Field
|
||
TextFormField(
|
||
controller: _emailController,
|
||
keyboardType: TextInputType.emailAddress,
|
||
decoration: const InputDecoration(
|
||
labelText: '電子郵件',
|
||
hintText: '請輸入您的電子郵件',
|
||
prefixIcon: Icon(Icons.email_outlined),
|
||
),
|
||
validator: (value) {
|
||
if (value == null || value.isEmpty) {
|
||
return '請輸入電子郵件';
|
||
}
|
||
if (!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(value)) {
|
||
return '請輸入有效的電子郵件格式';
|
||
}
|
||
return null;
|
||
},
|
||
),
|
||
|
||
const SizedBox(height: 16),
|
||
|
||
// Password Field
|
||
TextFormField(
|
||
controller: _passwordController,
|
||
obscureText: _obscurePassword,
|
||
decoration: InputDecoration(
|
||
labelText: '密碼',
|
||
hintText: '請輸入密碼(至少8個字符)',
|
||
prefixIcon: const Icon(Icons.lock_outlined),
|
||
suffixIcon: IconButton(
|
||
icon: Icon(
|
||
_obscurePassword
|
||
? Icons.visibility_outlined
|
||
: Icons.visibility_off_outlined,
|
||
),
|
||
onPressed: () {
|
||
setState(() {
|
||
_obscurePassword = !_obscurePassword;
|
||
});
|
||
},
|
||
),
|
||
),
|
||
validator: (value) {
|
||
if (value == null || value.isEmpty) {
|
||
return '請輸入密碼';
|
||
}
|
||
if (value.length < 8) {
|
||
return '密碼長度至少需要8個字符';
|
||
}
|
||
if (!RegExp(r'^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)').hasMatch(value)) {
|
||
return '密碼需包含大寫字母、小寫字母和數字';
|
||
}
|
||
return null;
|
||
},
|
||
),
|
||
|
||
const SizedBox(height: 16),
|
||
|
||
// Confirm Password Field
|
||
TextFormField(
|
||
controller: _confirmPasswordController,
|
||
obscureText: _obscureConfirmPassword,
|
||
decoration: InputDecoration(
|
||
labelText: '確認密碼',
|
||
hintText: '請再次輸入密碼',
|
||
prefixIcon: const Icon(Icons.lock_outlined),
|
||
suffixIcon: IconButton(
|
||
icon: Icon(
|
||
_obscureConfirmPassword
|
||
? Icons.visibility_outlined
|
||
: Icons.visibility_off_outlined,
|
||
),
|
||
onPressed: () {
|
||
setState(() {
|
||
_obscureConfirmPassword = !_obscureConfirmPassword;
|
||
});
|
||
},
|
||
),
|
||
),
|
||
validator: (value) {
|
||
if (value == null || value.isEmpty) {
|
||
return '請確認密碼';
|
||
}
|
||
if (value != _passwordController.text) {
|
||
return '密碼不一致';
|
||
}
|
||
return null;
|
||
},
|
||
),
|
||
|
||
const SizedBox(height: 24),
|
||
|
||
// Terms and Conditions
|
||
Row(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Checkbox(
|
||
value: _acceptTerms,
|
||
onChanged: (value) {
|
||
setState(() {
|
||
_acceptTerms = value ?? false;
|
||
});
|
||
},
|
||
),
|
||
Expanded(
|
||
child: GestureDetector(
|
||
onTap: () {
|
||
setState(() {
|
||
_acceptTerms = !_acceptTerms;
|
||
});
|
||
},
|
||
child: Padding(
|
||
padding: const EdgeInsets.only(top: 12),
|
||
child: RichText(
|
||
text: TextSpan(
|
||
style: Theme.of(context).textTheme.bodyMedium,
|
||
children: [
|
||
const TextSpan(text: '我同意'),
|
||
TextSpan(
|
||
text: '服務條款',
|
||
style: TextStyle(
|
||
color: Theme.of(context).colorScheme.primary,
|
||
decoration: TextDecoration.underline,
|
||
),
|
||
),
|
||
const TextSpan(text: '和'),
|
||
TextSpan(
|
||
text: '隱私政策',
|
||
style: TextStyle(
|
||
color: Theme.of(context).colorScheme.primary,
|
||
decoration: TextDecoration.underline,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
|
||
const SizedBox(height: 24),
|
||
|
||
// Register Button
|
||
SizedBox(
|
||
height: 48,
|
||
child: ElevatedButton(
|
||
onPressed: (_isLoading || !_acceptTerms) ? null : _handleRegister,
|
||
child: _isLoading
|
||
? const SizedBox(
|
||
width: 20,
|
||
height: 20,
|
||
child: CircularProgressIndicator(strokeWidth: 2),
|
||
)
|
||
: const Text('註冊'),
|
||
),
|
||
),
|
||
|
||
const SizedBox(height: 16),
|
||
|
||
// Back to Login
|
||
Row(
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
children: [
|
||
const Text('已經有帳號了?'),
|
||
TextButton(
|
||
onPressed: () => context.go('/login'),
|
||
child: const Text('立即登入'),
|
||
),
|
||
],
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
Future<void> _handleRegister() async {
|
||
if (!_formKey.currentState!.validate()) return;
|
||
|
||
if (!_acceptTerms) {
|
||
ScaffoldMessenger.of(context).showSnackBar(
|
||
const SnackBar(content: Text('請同意服務條款和隱私政策')),
|
||
);
|
||
return;
|
||
}
|
||
|
||
setState(() {
|
||
_isLoading = true;
|
||
});
|
||
|
||
try {
|
||
// TODO: 實現實際的註冊邏輯
|
||
await Future.delayed(const Duration(seconds: 2)); // 模擬API調用
|
||
|
||
if (mounted) {
|
||
// 註冊成功,導航到首頁
|
||
context.go('/home');
|
||
ScaffoldMessenger.of(context).showSnackBar(
|
||
const SnackBar(content: Text('註冊成功!歡迎使用Drama Ling')),
|
||
);
|
||
}
|
||
} catch (e) {
|
||
if (mounted) {
|
||
ScaffoldMessenger.of(context).showSnackBar(
|
||
SnackBar(content: Text('註冊失敗:$e')),
|
||
);
|
||
}
|
||
} finally {
|
||
if (mounted) {
|
||
setState(() {
|
||
_isLoading = false;
|
||
});
|
||
}
|
||
}
|
||
}
|
||
} |