708 lines
22 KiB
Dart
708 lines
22 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:image_picker/image_picker.dart';
|
|
import '../../../core/theme/app_colors.dart';
|
|
import '../../../shared/widgets/custom_app_bar.dart';
|
|
import '../../../core/widgets/custom_text_field.dart';
|
|
import '../../../core/widgets/custom_button.dart';
|
|
import '../../../core/providers/app_state_provider.dart';
|
|
import '../../../shared/models/user_model.dart';
|
|
|
|
/// 个人信息编辑页面
|
|
class ProfileEditScreen extends ConsumerStatefulWidget {
|
|
const ProfileEditScreen({super.key});
|
|
|
|
@override
|
|
ConsumerState<ProfileEditScreen> createState() => _ProfileEditScreenState();
|
|
}
|
|
|
|
class _ProfileEditScreenState extends ConsumerState<ProfileEditScreen> {
|
|
final _formKey = GlobalKey<FormState>();
|
|
final _usernameController = TextEditingController();
|
|
final _emailController = TextEditingController();
|
|
final _phoneController = TextEditingController();
|
|
final _bioController = TextEditingController();
|
|
final _realNameController = TextEditingController();
|
|
final _locationController = TextEditingController();
|
|
final _occupationController = TextEditingController();
|
|
|
|
String? _selectedGender;
|
|
DateTime? _selectedBirthday;
|
|
String? _selectedEducation;
|
|
String? _selectedEnglishLevel;
|
|
String? _selectedTargetLevel;
|
|
String? _selectedLearningGoal;
|
|
List<String> _interests = [];
|
|
String? _avatarPath;
|
|
bool _isLoading = false;
|
|
|
|
final List<String> _genderOptions = ['male', 'female', 'other'];
|
|
final List<String> _educationOptions = ['高中', '专科', '本科', '硕士', '博士'];
|
|
final List<String> _englishLevelOptions = ['beginner', 'elementary', 'intermediate', 'upperIntermediate', 'advanced'];
|
|
final List<String> _learningGoalOptions = ['dailyCommunication', 'businessEnglish', 'academicEnglish', 'examPreparation', 'travelEnglish'];
|
|
final List<String> _interestOptions = ['编程', '英语学习', '阅读', '音乐', '电影', '旅行', '运动', '摄影', '绘画', '游戏'];
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_loadUserData();
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_usernameController.dispose();
|
|
_emailController.dispose();
|
|
_phoneController.dispose();
|
|
_bioController.dispose();
|
|
_realNameController.dispose();
|
|
_locationController.dispose();
|
|
_occupationController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
void _loadUserData() {
|
|
final authProviderNotifier = ref.read(authProvider);
|
|
final user = authProviderNotifier.user;
|
|
if (user != null) {
|
|
_usernameController.text = user.username ?? '';
|
|
_emailController.text = user.email ?? '';
|
|
_phoneController.text = user.phone ?? '';
|
|
_bioController.text = user.bio ?? '';
|
|
_selectedGender = user.gender;
|
|
_selectedBirthday = user.birthday;
|
|
_selectedEnglishLevel = user.learningLevel;
|
|
_avatarPath = user.avatar;
|
|
}
|
|
}
|
|
|
|
Future<void> _pickImage() async {
|
|
final ImagePicker picker = ImagePicker();
|
|
final XFile? image = await picker.pickImage(
|
|
source: ImageSource.gallery,
|
|
maxWidth: 512,
|
|
maxHeight: 512,
|
|
imageQuality: 80,
|
|
);
|
|
|
|
if (image != null) {
|
|
setState(() {
|
|
_avatarPath = image.path;
|
|
});
|
|
}
|
|
}
|
|
|
|
Future<void> _selectBirthday() async {
|
|
final DateTime? picked = await showDatePicker(
|
|
context: context,
|
|
initialDate: _selectedBirthday ?? DateTime(1990),
|
|
firstDate: DateTime(1900),
|
|
lastDate: DateTime.now(),
|
|
builder: (context, child) {
|
|
return Theme(
|
|
data: Theme.of(context).copyWith(
|
|
colorScheme: ColorScheme.light(
|
|
primary: AppColors.primary,
|
|
onPrimary: AppColors.onPrimary,
|
|
surface: AppColors.surface,
|
|
onSurface: AppColors.onSurface,
|
|
),
|
|
),
|
|
child: child!,
|
|
);
|
|
},
|
|
);
|
|
|
|
if (picked != null) {
|
|
setState(() {
|
|
_selectedBirthday = picked;
|
|
});
|
|
}
|
|
}
|
|
|
|
void _showInterestsDialog() {
|
|
showDialog(
|
|
context: context,
|
|
builder: (context) {
|
|
List<String> tempInterests = List.from(_interests);
|
|
return StatefulBuilder(
|
|
builder: (context, setDialogState) {
|
|
return AlertDialog(
|
|
title: const Text('选择兴趣爱好'),
|
|
content: SizedBox(
|
|
width: double.maxFinite,
|
|
child: ListView.builder(
|
|
shrinkWrap: true,
|
|
itemCount: _interestOptions.length,
|
|
itemBuilder: (context, index) {
|
|
final interest = _interestOptions[index];
|
|
final isSelected = tempInterests.contains(interest);
|
|
return CheckboxListTile(
|
|
title: Text(interest),
|
|
value: isSelected,
|
|
onChanged: (bool? value) {
|
|
setDialogState(() {
|
|
if (value == true) {
|
|
tempInterests.add(interest);
|
|
} else {
|
|
tempInterests.remove(interest);
|
|
}
|
|
});
|
|
},
|
|
activeColor: AppColors.primary,
|
|
);
|
|
},
|
|
),
|
|
),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.pop(context),
|
|
child: const Text('取消'),
|
|
),
|
|
TextButton(
|
|
onPressed: () {
|
|
setState(() {
|
|
_interests = tempInterests;
|
|
});
|
|
Navigator.pop(context);
|
|
},
|
|
child: const Text('确定'),
|
|
),
|
|
],
|
|
);
|
|
},
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
Future<void> _saveProfile() async {
|
|
if (!_formKey.currentState!.validate()) return;
|
|
|
|
setState(() {
|
|
_isLoading = true;
|
|
});
|
|
|
|
try {
|
|
final authProviderNotifier = ref.read(authProvider);
|
|
final success = await authProviderNotifier.updateProfile(
|
|
nickname: _usernameController.text.trim(),
|
|
avatar: _avatarPath,
|
|
phone: _phoneController.text.trim(),
|
|
birthday: _selectedBirthday,
|
|
gender: _selectedGender,
|
|
bio: _bioController.text.trim(),
|
|
learningLevel: _selectedEnglishLevel,
|
|
targetLanguage: 'english',
|
|
nativeLanguage: 'chinese',
|
|
dailyGoal: 30,
|
|
);
|
|
|
|
if (success) {
|
|
if (mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(
|
|
content: Text('个人信息更新成功'),
|
|
backgroundColor: Colors.green,
|
|
),
|
|
);
|
|
Navigator.pop(context);
|
|
}
|
|
} else {
|
|
if (mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(
|
|
content: Text('更新失败,请重试'),
|
|
backgroundColor: Colors.red,
|
|
),
|
|
);
|
|
}
|
|
}
|
|
} catch (e) {
|
|
if (mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(
|
|
content: Text('更新失败: $e'),
|
|
backgroundColor: Colors.red,
|
|
),
|
|
);
|
|
}
|
|
} finally {
|
|
if (mounted) {
|
|
setState(() {
|
|
_isLoading = false;
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
String _getGenderDisplayName(String gender) {
|
|
switch (gender) {
|
|
case 'male':
|
|
return '男';
|
|
case 'female':
|
|
return '女';
|
|
case 'other':
|
|
return '其他';
|
|
default:
|
|
return gender;
|
|
}
|
|
}
|
|
|
|
String _getEnglishLevelDisplayName(String level) {
|
|
switch (level) {
|
|
case 'beginner':
|
|
return '初学者';
|
|
case 'elementary':
|
|
return '基础';
|
|
case 'intermediate':
|
|
return '中级';
|
|
case 'upperIntermediate':
|
|
return '中高级';
|
|
case 'advanced':
|
|
return '高级';
|
|
default:
|
|
return level;
|
|
}
|
|
}
|
|
|
|
String _getLearningGoalDisplayName(String goal) {
|
|
switch (goal) {
|
|
case 'dailyCommunication':
|
|
return '日常交流';
|
|
case 'businessEnglish':
|
|
return '商务英语';
|
|
case 'academicEnglish':
|
|
return '学术英语';
|
|
case 'examPreparation':
|
|
return '考试准备';
|
|
case 'travelEnglish':
|
|
return '旅游英语';
|
|
default:
|
|
return goal;
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
backgroundColor: AppColors.surface,
|
|
appBar: CustomAppBar(
|
|
title: '编辑个人信息',
|
|
actions: [
|
|
TextButton(
|
|
onPressed: _isLoading ? null : _saveProfile,
|
|
child: Text(
|
|
'保存',
|
|
style: TextStyle(
|
|
color: _isLoading ? AppColors.onSurfaceVariant : AppColors.primary,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
body: Stack(
|
|
children: [
|
|
Form(
|
|
key: _formKey,
|
|
child: SingleChildScrollView(
|
|
padding: const EdgeInsets.all(16),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// 头像部分
|
|
Center(
|
|
child: GestureDetector(
|
|
onTap: _pickImage,
|
|
child: Container(
|
|
width: 100,
|
|
height: 100,
|
|
decoration: BoxDecoration(
|
|
shape: BoxShape.circle,
|
|
border: Border.all(
|
|
color: AppColors.primary,
|
|
width: 2,
|
|
),
|
|
),
|
|
child: CircleAvatar(
|
|
radius: 48,
|
|
backgroundColor: AppColors.surface,
|
|
backgroundImage: _avatarPath != null
|
|
? (_avatarPath!.startsWith('http')
|
|
? NetworkImage(_avatarPath!)
|
|
: FileImage(XFile(_avatarPath!).path as dynamic))
|
|
: null,
|
|
child: _avatarPath == null
|
|
? Icon(
|
|
Icons.camera_alt,
|
|
size: 30,
|
|
color: AppColors.primary,
|
|
)
|
|
: null,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Center(
|
|
child: Text(
|
|
'点击更换头像',
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
color: AppColors.onSurfaceVariant,
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: 32),
|
|
|
|
// 基本信息
|
|
_buildSectionTitle('基本信息'),
|
|
const SizedBox(height: 16),
|
|
|
|
CustomTextField(
|
|
controller: _usernameController,
|
|
labelText: '用户名',
|
|
hintText: '请输入用户名',
|
|
validator: (value) {
|
|
if (value == null || value.trim().isEmpty) {
|
|
return '请输入用户名';
|
|
}
|
|
return null;
|
|
},
|
|
),
|
|
const SizedBox(height: 16),
|
|
|
|
CustomTextField(
|
|
controller: _realNameController,
|
|
labelText: '真实姓名',
|
|
hintText: '请输入真实姓名',
|
|
),
|
|
const SizedBox(height: 16),
|
|
|
|
CustomTextField(
|
|
controller: _emailController,
|
|
labelText: '邮箱',
|
|
hintText: '请输入邮箱地址',
|
|
keyboardType: TextInputType.emailAddress,
|
|
enabled: false, // 邮箱通常不允许修改
|
|
),
|
|
const SizedBox(height: 16),
|
|
|
|
CustomTextField(
|
|
controller: _phoneController,
|
|
labelText: '手机号',
|
|
hintText: '请输入手机号',
|
|
keyboardType: TextInputType.phone,
|
|
),
|
|
const SizedBox(height: 16),
|
|
|
|
// 性别选择
|
|
_buildDropdownField(
|
|
label: '性别',
|
|
value: _selectedGender,
|
|
items: _genderOptions,
|
|
displayNameBuilder: _getGenderDisplayName,
|
|
onChanged: (value) {
|
|
setState(() {
|
|
_selectedGender = value;
|
|
});
|
|
},
|
|
),
|
|
const SizedBox(height: 16),
|
|
|
|
// 生日选择
|
|
_buildDateField(
|
|
label: '生日',
|
|
value: _selectedBirthday,
|
|
onTap: _selectBirthday,
|
|
),
|
|
const SizedBox(height: 16),
|
|
|
|
CustomTextField(
|
|
controller: _locationController,
|
|
labelText: '所在地',
|
|
hintText: '请输入所在地',
|
|
),
|
|
const SizedBox(height: 16),
|
|
|
|
CustomTextField(
|
|
controller: _occupationController,
|
|
labelText: '职业',
|
|
hintText: '请输入职业',
|
|
),
|
|
const SizedBox(height: 16),
|
|
|
|
// 教育背景
|
|
_buildDropdownField(
|
|
label: '教育背景',
|
|
value: _selectedEducation,
|
|
items: _educationOptions,
|
|
displayNameBuilder: (value) => value,
|
|
onChanged: (value) {
|
|
setState(() {
|
|
_selectedEducation = value;
|
|
});
|
|
},
|
|
),
|
|
const SizedBox(height: 32),
|
|
|
|
// 学习信息
|
|
_buildSectionTitle('学习信息'),
|
|
const SizedBox(height: 16),
|
|
|
|
// 当前英语水平
|
|
_buildDropdownField(
|
|
label: '当前英语水平',
|
|
value: _selectedEnglishLevel,
|
|
items: _englishLevelOptions,
|
|
displayNameBuilder: _getEnglishLevelDisplayName,
|
|
onChanged: (value) {
|
|
setState(() {
|
|
_selectedEnglishLevel = value;
|
|
});
|
|
},
|
|
),
|
|
const SizedBox(height: 16),
|
|
|
|
// 目标英语水平
|
|
_buildDropdownField(
|
|
label: '目标英语水平',
|
|
value: _selectedTargetLevel,
|
|
items: _englishLevelOptions,
|
|
displayNameBuilder: _getEnglishLevelDisplayName,
|
|
onChanged: (value) {
|
|
setState(() {
|
|
_selectedTargetLevel = value;
|
|
});
|
|
},
|
|
),
|
|
const SizedBox(height: 16),
|
|
|
|
// 学习目标
|
|
_buildDropdownField(
|
|
label: '学习目标',
|
|
value: _selectedLearningGoal,
|
|
items: _learningGoalOptions,
|
|
displayNameBuilder: _getLearningGoalDisplayName,
|
|
onChanged: (value) {
|
|
setState(() {
|
|
_selectedLearningGoal = value;
|
|
});
|
|
},
|
|
),
|
|
const SizedBox(height: 16),
|
|
|
|
// 兴趣爱好
|
|
_buildInterestsField(),
|
|
const SizedBox(height: 32),
|
|
|
|
// 个人简介
|
|
_buildSectionTitle('个人简介'),
|
|
const SizedBox(height: 16),
|
|
|
|
CustomTextField(
|
|
controller: _bioController,
|
|
labelText: '个人简介',
|
|
hintText: '介绍一下自己吧...',
|
|
maxLines: 4,
|
|
),
|
|
const SizedBox(height: 32),
|
|
|
|
// 保存按钮
|
|
CustomButton(
|
|
text: '保存修改',
|
|
onPressed: _saveProfile,
|
|
isLoading: _isLoading,
|
|
),
|
|
const SizedBox(height: 32),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
if (_isLoading)
|
|
Container(
|
|
color: Colors.black.withOpacity(0.3),
|
|
child: const Center(
|
|
child: CircularProgressIndicator(),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildSectionTitle(String title) {
|
|
return Text(
|
|
title,
|
|
style: TextStyle(
|
|
fontSize: 18,
|
|
fontWeight: FontWeight.bold,
|
|
color: AppColors.onSurface,
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildDropdownField({
|
|
required String label,
|
|
required String? value,
|
|
required List<String> items,
|
|
required String Function(String) displayNameBuilder,
|
|
required void Function(String?) onChanged,
|
|
}) {
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
label,
|
|
style: TextStyle(
|
|
fontSize: 14,
|
|
fontWeight: FontWeight.w500,
|
|
color: AppColors.onSurface,
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Container(
|
|
width: double.infinity,
|
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
|
decoration: BoxDecoration(
|
|
border: Border.all(color: AppColors.outline),
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
child: DropdownButtonHideUnderline(
|
|
child: DropdownButton<String>(
|
|
value: value,
|
|
hint: Text('请选择$label'),
|
|
isExpanded: true,
|
|
items: items.map((item) {
|
|
return DropdownMenuItem<String>(
|
|
value: item,
|
|
child: Text(displayNameBuilder(item)),
|
|
);
|
|
}).toList(),
|
|
onChanged: onChanged,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _buildDateField({
|
|
required String label,
|
|
required DateTime? value,
|
|
required VoidCallback onTap,
|
|
}) {
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
label,
|
|
style: TextStyle(
|
|
fontSize: 14,
|
|
fontWeight: FontWeight.w500,
|
|
color: AppColors.onSurface,
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
GestureDetector(
|
|
onTap: onTap,
|
|
child: Container(
|
|
width: double.infinity,
|
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 16),
|
|
decoration: BoxDecoration(
|
|
border: Border.all(color: AppColors.outline),
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Text(
|
|
value != null
|
|
? '${value.year}-${value.month.toString().padLeft(2, '0')}-${value.day.toString().padLeft(2, '0')}'
|
|
: '请选择$label',
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
color: value != null ? AppColors.onSurface : AppColors.onSurfaceVariant,
|
|
),
|
|
),
|
|
Icon(
|
|
Icons.calendar_today,
|
|
color: AppColors.onSurfaceVariant,
|
|
size: 20,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _buildInterestsField() {
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'兴趣爱好',
|
|
style: TextStyle(
|
|
fontSize: 14,
|
|
fontWeight: FontWeight.w500,
|
|
color: AppColors.onSurface,
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
GestureDetector(
|
|
onTap: _showInterestsDialog,
|
|
child: Container(
|
|
width: double.infinity,
|
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 16),
|
|
decoration: BoxDecoration(
|
|
border: Border.all(color: AppColors.outline),
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Expanded(
|
|
child: _interests.isEmpty
|
|
? Text(
|
|
'请选择兴趣爱好',
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
color: AppColors.onSurfaceVariant,
|
|
),
|
|
)
|
|
: Wrap(
|
|
spacing: 8,
|
|
runSpacing: 4,
|
|
children: _interests.map((interest) {
|
|
return Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
|
decoration: BoxDecoration(
|
|
color: AppColors.primary.withOpacity(0.1),
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
child: Text(
|
|
interest,
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
color: AppColors.primary,
|
|
),
|
|
),
|
|
);
|
|
}).toList(),
|
|
),
|
|
),
|
|
Icon(
|
|
Icons.arrow_forward_ios,
|
|
color: AppColors.onSurfaceVariant,
|
|
size: 16,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
} |