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

768 lines
23 KiB
Dart

import 'package:flutter/material.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 '../providers/auth_provider.dart';
import '../../../core/models/user_model.dart';
/// 个人信息管理页面
class ProfileScreen extends ConsumerStatefulWidget {
const ProfileScreen({super.key});
@override
ConsumerState<ProfileScreen> createState() => _ProfileScreenState();
}
class _ProfileScreenState extends ConsumerState<ProfileScreen> with SingleTickerProviderStateMixin {
late TabController _tabController;
final _formKey = GlobalKey<FormState>();
// 个人信息控制器
final _usernameController = TextEditingController();
final _emailController = TextEditingController();
final _phoneController = TextEditingController();
final _bioController = TextEditingController();
// 密码修改控制器
final _currentPasswordController = TextEditingController();
final _newPasswordController = TextEditingController();
final _confirmPasswordController = TextEditingController();
bool _isLoading = false;
bool _isEditing = false;
bool _obscureCurrentPassword = true;
bool _obscureNewPassword = true;
bool _obscureConfirmPassword = true;
@override
void initState() {
super.initState();
_tabController = TabController(length: 3, vsync: this);
_loadUserData();
}
@override
void dispose() {
_tabController.dispose();
_usernameController.dispose();
_emailController.dispose();
_phoneController.dispose();
_bioController.dispose();
_currentPasswordController.dispose();
_newPasswordController.dispose();
_confirmPasswordController.dispose();
super.dispose();
}
/// 加载用户数据
void _loadUserData() async {
try {
final authNotifier = await ref.read(authProvider.future);
final user = authNotifier.state.user;
if (user != null) {
_usernameController.text = user.username;
_emailController.text = user.email;
_phoneController.text = user.profile?.phone ?? '';
_bioController.text = user.profile?.bio ?? '';
}
} catch (e) {
// 处理错误
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.background,
appBar: AppBar(
backgroundColor: AppColors.surface,
elevation: 0,
title: Text(
'个人信息',
style: AppTextStyles.headlineSmall.copyWith(
color: AppColors.onSurface,
fontWeight: FontWeight.w600,
),
),
actions: [
if (_tabController.index == 0)
TextButton(
onPressed: () {
setState(() {
_isEditing = !_isEditing;
});
},
child: Text(
_isEditing ? '取消' : '编辑',
style: AppTextStyles.labelLarge.copyWith(
color: AppColors.primary,
),
),
),
],
bottom: TabBar(
controller: _tabController,
labelColor: AppColors.primary,
unselectedLabelColor: AppColors.onSurfaceVariant,
indicatorColor: AppColors.primary,
tabs: const [
Tab(text: '基本信息'),
Tab(text: '安全设置'),
Tab(text: '学习偏好'),
],
),
),
body: TabBarView(
controller: _tabController,
children: [
_buildBasicInfoTab(),
_buildSecurityTab(),
_buildPreferencesTab(),
],
),
);
}
/// 基本信息标签页
Widget _buildBasicInfoTab() {
return Consumer(builder: (context, ref, child) {
final authNotifierAsync = ref.watch(authProvider);
return authNotifierAsync.when(
data: (authNotifier) {
final user = authNotifier.state.user;
return SingleChildScrollView(
padding: const EdgeInsets.all(AppDimensions.spacingXl),
child: Form(
key: _formKey,
child: Column(
children: [
// 头像
Center(
child: Stack(
children: [
CircleAvatar(
radius: 60,
backgroundColor: AppColors.surfaceVariant,
backgroundImage: user?.profile?.avatar != null
? NetworkImage(user!.profile!.avatar!)
: null,
child: user?.profile?.avatar == null
? Icon(
Icons.person,
size: 60,
color: AppColors.onSurfaceVariant,
)
: null,
),
if (_isEditing)
Positioned(
bottom: 0,
right: 0,
child: GestureDetector(
onTap: _handleAvatarChange,
child: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: AppColors.primary,
shape: BoxShape.circle,
border: Border.all(
color: AppColors.surface,
width: 2,
),
),
child: Icon(
Icons.camera_alt,
size: 20,
color: AppColors.onPrimary,
),
),
),
),
],
),
),
const SizedBox(height: AppDimensions.spacingXl),
// 用户名
CustomTextField(
controller: _usernameController,
labelText: '用户名',
prefixIcon: Icons.person_outline,
enabled: _isEditing,
validator: (value) {
if (value == null || value.isEmpty) {
return '请输入用户名';
}
if (value.length < 3) {
return '用户名至少3个字符';
}
return null;
},
),
const SizedBox(height: AppDimensions.spacingMd),
// 邮箱
CustomTextField(
controller: _emailController,
labelText: '邮箱',
prefixIcon: Icons.email_outlined,
enabled: false, // 邮箱不允许修改
keyboardType: TextInputType.emailAddress,
),
const SizedBox(height: AppDimensions.spacingMd),
// 手机号
CustomTextField(
controller: _phoneController,
labelText: '手机号',
prefixIcon: Icons.phone_outlined,
enabled: _isEditing,
keyboardType: TextInputType.phone,
validator: (value) {
if (value != null && value.isNotEmpty) {
if (!RegExp(r'^1[3-9]\d{9}$').hasMatch(value)) {
return '请输入有效的手机号';
}
}
return null;
},
),
const SizedBox(height: AppDimensions.spacingMd),
// 个人简介
CustomTextField(
controller: _bioController,
labelText: '个人简介',
prefixIcon: Icons.edit_outlined,
enabled: _isEditing,
maxLines: 3,
hintText: '介绍一下自己吧...',
),
const SizedBox(height: AppDimensions.spacingXl),
// 保存按钮
if (_isEditing)
CustomButton(
text: '保存修改',
onPressed: _handleSaveProfile,
isLoading: _isLoading,
width: double.infinity,
),
],
),
),
);
},
loading: () => const Center(child: CircularProgressIndicator()),
error: (error, stack) => Center(
child: Text('加载失败: $error'),
),
);
});
}
/// 安全设置标签页
Widget _buildSecurityTab() {
return SingleChildScrollView(
padding: const EdgeInsets.all(AppDimensions.spacingXl),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 修改密码
Text(
'修改密码',
style: AppTextStyles.titleMedium.copyWith(
color: AppColors.onSurface,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: AppDimensions.spacingMd),
// 当前密码
CustomTextField(
controller: _currentPasswordController,
labelText: '当前密码',
prefixIcon: Icons.lock_outline,
obscureText: _obscureCurrentPassword,
suffixIcon: IconButton(
icon: Icon(
_obscureCurrentPassword ? Icons.visibility_off : Icons.visibility,
color: AppColors.onSurfaceVariant,
),
onPressed: () {
setState(() {
_obscureCurrentPassword = !_obscureCurrentPassword;
});
},
),
validator: (value) {
if (value == null || value.isEmpty) {
return '请输入当前密码';
}
return null;
},
),
const SizedBox(height: AppDimensions.spacingMd),
// 新密码
CustomTextField(
controller: _newPasswordController,
labelText: '新密码',
prefixIcon: Icons.lock_outline,
obscureText: _obscureNewPassword,
suffixIcon: IconButton(
icon: Icon(
_obscureNewPassword ? Icons.visibility_off : Icons.visibility,
color: AppColors.onSurfaceVariant,
),
onPressed: () {
setState(() {
_obscureNewPassword = !_obscureNewPassword;
});
},
),
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: AppDimensions.spacingMd),
// 确认新密码
CustomTextField(
controller: _confirmPasswordController,
labelText: '确认新密码',
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 != _newPasswordController.text) {
return '两次输入的密码不一致';
}
return null;
},
),
const SizedBox(height: AppDimensions.spacingXl),
// 修改密码按钮
CustomButton(
text: '修改密码',
onPressed: _handleChangePassword,
isLoading: _isLoading,
width: double.infinity,
),
const SizedBox(height: AppDimensions.spacingXl),
// 其他安全选项
_buildSecurityOption(
icon: Icons.security,
title: '两步验证',
subtitle: '为您的账户添加额外的安全保护',
onTap: () => _showSnackBar('两步验证功能即将上线'),
),
_buildSecurityOption(
icon: Icons.devices,
title: '设备管理',
subtitle: '查看和管理已登录的设备',
onTap: () => _showSnackBar('设备管理功能即将上线'),
),
_buildSecurityOption(
icon: Icons.history,
title: '登录历史',
subtitle: '查看最近的登录记录',
onTap: () => _showSnackBar('登录历史功能即将上线'),
),
],
),
);
}
/// 学习偏好标签页
Widget _buildPreferencesTab() {
return Consumer(builder: (context, ref, child) {
final authNotifierAsync = ref.watch(authProvider);
return authNotifierAsync.when(
data: (authNotifier) {
final user = authNotifier.state.user;
final settings = user?.profile?.settings;
return SingleChildScrollView(
padding: const EdgeInsets.all(AppDimensions.spacingXl),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'学习设置',
style: AppTextStyles.titleMedium.copyWith(
color: AppColors.onSurface,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: AppDimensions.spacingMd),
_buildPreferenceOption(
icon: Icons.notifications,
title: '学习提醒',
subtitle: '每日学习提醒通知',
value: settings?.notificationsEnabled ?? true,
onChanged: (value) => _updateSetting('notificationsEnabled', value),
),
_buildPreferenceOption(
icon: Icons.volume_up,
title: '音效',
subtitle: '学习过程中的音效反馈',
value: settings?.soundEnabled ?? true,
onChanged: (value) => _updateSetting('soundEnabled', value),
),
_buildPreferenceOption(
icon: Icons.vibration,
title: '震动反馈',
subtitle: '操作时的震动反馈',
value: settings?.vibrationEnabled ?? true,
onChanged: (value) => _updateSetting('vibrationEnabled', value),
),
const SizedBox(height: AppDimensions.spacingXl),
Text(
'学习目标',
style: AppTextStyles.titleMedium.copyWith(
color: AppColors.onSurface,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: AppDimensions.spacingMd),
_buildGoalOption(
title: '每日单词目标',
value: '${settings?.dailyWordGoal ?? 20}',
onTap: () => _showGoalDialog('dailyWordGoal', settings?.dailyWordGoal ?? 20),
),
_buildGoalOption(
title: '每日学习时长',
value: '${settings?.dailyStudyMinutes ?? 30} 分钟',
onTap: () => _showGoalDialog('dailyStudyMinutes', settings?.dailyStudyMinutes ?? 30),
),
const SizedBox(height: AppDimensions.spacingXl),
Text(
'英语水平',
style: AppTextStyles.titleMedium.copyWith(
color: AppColors.onSurface,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: AppDimensions.spacingMd),
_buildLevelOption(
title: '当前水平',
value: _getLevelText(user?.profile?.englishLevel ?? EnglishLevel.beginner),
onTap: () => _showLevelDialog(),
),
],
),
);
},
loading: () => const Center(child: CircularProgressIndicator()),
error: (error, stack) => Center(
child: Text('加载失败: $error'),
),
);
});
}
/// 构建安全选项
Widget _buildSecurityOption({
required IconData icon,
required String title,
required String subtitle,
required VoidCallback onTap,
}) {
return ListTile(
leading: Icon(icon, color: AppColors.primary),
title: Text(
title,
style: AppTextStyles.bodyLarge.copyWith(
color: AppColors.onSurface,
),
),
subtitle: Text(
subtitle,
style: AppTextStyles.bodyMedium.copyWith(
color: AppColors.onSurfaceVariant,
),
),
trailing: Icon(
Icons.arrow_forward_ios,
size: 16,
color: AppColors.onSurfaceVariant,
),
onTap: onTap,
);
}
/// 构建偏好选项
Widget _buildPreferenceOption({
required IconData icon,
required String title,
required String subtitle,
required bool value,
required ValueChanged<bool> onChanged,
}) {
return ListTile(
leading: Icon(icon, color: AppColors.primary),
title: Text(
title,
style: AppTextStyles.bodyLarge.copyWith(
color: AppColors.onSurface,
),
),
subtitle: Text(
subtitle,
style: AppTextStyles.bodyMedium.copyWith(
color: AppColors.onSurfaceVariant,
),
),
trailing: Switch(
value: value,
onChanged: onChanged,
activeColor: AppColors.primary,
),
);
}
/// 构建目标选项
Widget _buildGoalOption({
required String title,
required String value,
required VoidCallback onTap,
}) {
return ListTile(
title: Text(
title,
style: AppTextStyles.bodyLarge.copyWith(
color: AppColors.onSurface,
),
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
value,
style: AppTextStyles.bodyMedium.copyWith(
color: AppColors.primary,
),
),
const SizedBox(width: 8),
Icon(
Icons.arrow_forward_ios,
size: 16,
color: AppColors.onSurfaceVariant,
),
],
),
onTap: onTap,
);
}
/// 构建水平选项
Widget _buildLevelOption({
required String title,
required String value,
required VoidCallback onTap,
}) {
return ListTile(
title: Text(
title,
style: AppTextStyles.bodyLarge.copyWith(
color: AppColors.onSurface,
),
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
value,
style: AppTextStyles.bodyMedium.copyWith(
color: AppColors.primary,
),
),
const SizedBox(width: 8),
Icon(
Icons.arrow_forward_ios,
size: 16,
color: AppColors.onSurfaceVariant,
),
],
),
onTap: onTap,
);
}
/// 处理头像更改
void _handleAvatarChange() {
// TODO: 实现头像更改功能
_showSnackBar('头像更改功能即将上线');
}
/// 处理保存个人信息
Future<void> _handleSaveProfile() async {
if (!_formKey.currentState!.validate()) {
return;
}
setState(() {
_isLoading = true;
});
try {
final authNotifier = await ref.read(authProvider.future);
await authNotifier.updateProfile(
username: _usernameController.text.trim(),
phone: _phoneController.text.trim(),
);
if (mounted) {
setState(() {
_isEditing = false;
});
_showSnackBar('个人信息更新成功', isSuccess: true);
}
} catch (e) {
if (mounted) {
_showSnackBar(e.toString());
}
} finally {
if (mounted) {
setState(() {
_isLoading = false;
});
}
}
}
/// 处理修改密码
Future<void> _handleChangePassword() async {
if (_currentPasswordController.text.isEmpty ||
_newPasswordController.text.isEmpty ||
_confirmPasswordController.text.isEmpty) {
_showSnackBar('请填写所有密码字段');
return;
}
if (_newPasswordController.text != _confirmPasswordController.text) {
_showSnackBar('两次输入的新密码不一致');
return;
}
setState(() {
_isLoading = true;
});
try {
final authNotifier = await ref.read(authProvider.future);
await authNotifier.changePassword(
currentPassword: _currentPasswordController.text,
newPassword: _newPasswordController.text,
);
if (mounted) {
_currentPasswordController.clear();
_newPasswordController.clear();
_confirmPasswordController.clear();
_showSnackBar('密码修改成功', isSuccess: true);
}
} catch (e) {
if (mounted) {
_showSnackBar(e.toString());
}
} finally {
if (mounted) {
setState(() {
_isLoading = false;
});
}
}
}
/// 更新设置
void _updateSetting(String key, bool value) {
// TODO: 实现设置更新
_showSnackBar('设置已更新', isSuccess: true);
}
/// 显示目标设置对话框
void _showGoalDialog(String type, int currentValue) {
// TODO: 实现目标设置对话框
_showSnackBar('目标设置功能即将上线');
}
/// 显示水平选择对话框
void _showLevelDialog() {
// TODO: 实现水平选择对话框
_showSnackBar('水平设置功能即将上线');
}
/// 获取水平文本
String _getLevelText(EnglishLevel level) {
switch (level) {
case EnglishLevel.beginner:
return '初级';
case EnglishLevel.elementary:
return '基础';
case EnglishLevel.intermediate:
return '中级';
case EnglishLevel.upperIntermediate:
return '中高级';
case EnglishLevel.advanced:
return '高级';
case EnglishLevel.proficient:
return '精通';
case EnglishLevel.expert:
return '专家';
}
}
/// 显示提示信息
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(AppDimensions.radiusSm),
),
),
);
}
}