From 964f77ca8be89c9627dd692980cab2b32263e919 Mon Sep 17 00:00:00 2001 From: lxm <1274844347@qq.com> Date: Wed, 19 Mar 2025 23:36:47 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BF=AE=E6=94=B9=E5=A4=B4?= =?UTF-8?q?=E5=83=8F=E5=92=8C=E7=94=A8=E6=88=B7=E5=90=8D=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/data/models/user_model.dart | 39 +++++ .../controllers/profile_controller.dart | 133 ++++++++++++------ lib/modules/profile/views/profile_view.dart | 104 +++++++++++++- .../controllers/settings_controller.dart | 62 ++++++++ lib/modules/settings/views/settings_view.dart | 57 +++++--- lib/routes/app_pages.dart | 5 + lib/routes/app_routes.dart | 1 + 7 files changed, 335 insertions(+), 66 deletions(-) create mode 100644 lib/data/models/user_model.dart diff --git a/lib/data/models/user_model.dart b/lib/data/models/user_model.dart new file mode 100644 index 000000000..4334df54e --- /dev/null +++ b/lib/data/models/user_model.dart @@ -0,0 +1,39 @@ +class UserModel { + String name; // 用户名 + String avatar; // 头像 + String id; + //final String deviceName; + String membershipLevel; // 会员等级 + + UserModel({ + required this.name, + required this.avatar, + required this.id, + //required this.deviceName, + required this.membershipLevel, + }); + + Map toJson() => { + 'name': name, + 'avatar': avatar, + 'id': id, + //'deviceName': deviceName, + 'membershipLevel': membershipLevel, + }; + + factory UserModel.fromJson(Map json) => UserModel( + name: json['name'] ?? '未设置昵称', + avatar: json['avatar'] ?? '', + id: json['id'] ?? '', + //deviceName: json['deviceName'] ?? '', + membershipLevel: json['membershipLevel'] ?? '普通会员', + ); + + factory UserModel.defaultUser() => UserModel( + name: '未设置昵称', + avatar: '', + id: 'deepvoice_${DateTime.now().millisecondsSinceEpoch.toString()}', + //deviceName: '', + membershipLevel: '普通会员', + ); +} \ No newline at end of file diff --git a/lib/modules/profile/controllers/profile_controller.dart b/lib/modules/profile/controllers/profile_controller.dart index f3d7a94eb..837a59b64 100644 --- a/lib/modules/profile/controllers/profile_controller.dart +++ b/lib/modules/profile/controllers/profile_controller.dart @@ -1,40 +1,47 @@ import 'package:get/get.dart'; import 'package:get_storage/get_storage.dart'; import 'package:flutter/material.dart'; +import 'package:image_picker/image_picker.dart'; +import '../../../data/models/user_model.dart'; class ProfileController extends GetxController { static ProfileController get to => Get.find(); - final _userName = ''.obs; - final _email = ''.obs; - final _avatar = ''.obs; - final _isEditing = false.obs; - final _storage = GetStorage(); + // final _userName = ''.obs; + // final _email = ''.obs; + // final _avatar = ''.obs; + // final _isEditing = false.obs; + // final _storage = GetStorage(); + //用户信息 + static const String userKey='user_info'; + final _storage=GetStorage(); + final ImagePicker _picker=ImagePicker(); + final Rx userInfo=UserModel.defaultUser().obs; // 使用响应式变量 final currentLanguage = 'zh'.obs; - String get userName => _userName.value; - String get email => _email.value; - String get avatar => _avatar.value; - bool get isEditing => _isEditing.value; + // String get userName => _userName.value; + // String get email => _email.value; + // String get avatar => _avatar.value; + // bool get isEditing => _isEditing.value; @override void onInit() { super.onInit(); - loadUserData(); + loadUserInfo(); loadLanguagePreference(); } - void loadUserData() { - try { - _userName.value = _storage.read('userName') ?? 'User Name'; - _email.value = _storage.read('userEmail') ?? 'user@example.com'; - _avatar.value = _storage.read('userAvatar') ?? ''; - } catch (e) { - Get.snackbar('Error', 'Failed to load user data'); - } - } + // void loadUserData() { + // try { + // _userName.value = _storage.read('userName') ?? 'User Name'; + // _email.value = _storage.read('userEmail') ?? 'user@example.com'; + // _avatar.value = _storage.read('userAvatar') ?? ''; + // } catch (e) { + // Get.snackbar('Error', 'Failed to load user data'); + // } + // } // 加载用户语言偏好设置 void loadLanguagePreference() { @@ -72,31 +79,71 @@ class ProfileController extends GetxController { }); } - Future updateUserData({ - String? userName, - String? email, - String? avatar, - }) async { - try { - if (userName != null) { - await _storage.write('userName', userName); - _userName.value = userName; - } - if (email != null) { - await _storage.write('userEmail', email); - _email.value = email; - } - if (avatar != null) { - await _storage.write('userAvatar', avatar); - _avatar.value = avatar; - } - Get.snackbar('Success', 'Profile updated successfully'); - } catch (e) { - Get.snackbar('Error', 'Failed to update profile'); + // Future updateUserData({ + // String? userName, + // String? email, + // String? avatar, + // }) async { + // try { + // if (userName != null) { + // await _storage.write('userName', userName); + // _userName.value = userName; + // } + // if (email != null) { + // await _storage.write('userEmail', email); + // _email.value = email; + // } + // if (avatar != null) { + // await _storage.write('userAvatar', avatar); + // _avatar.value = avatar; + // } + // Get.snackbar('Success', 'Profile updated successfully'); + // } catch (e) { + // Get.snackbar('Error', 'Failed to update profile'); + // } + // } + + // void toggleEditing() { + // _isEditing.value = !_isEditing.value; + // } + + void loadUserInfo() { + final userJson = _storage.read(userKey); + if (userJson != null) { + userInfo.value = UserModel.fromJson(userJson); } } - - void toggleEditing() { - _isEditing.value = !_isEditing.value; + + Future saveUserInfo() async { + await _storage.write(userKey, userInfo.value.toJson()); + } + + Future updateUserAvatar() async { + final XFile? image = await _picker.pickImage( + source: ImageSource.gallery, + maxWidth: 800, + maxHeight: 800, + imageQuality: 85, + ); + + if (image != null) { + userInfo.update((user) { + if (user != null) { + user.avatar = image.path; + } + }); + await saveUserInfo(); + } + } + + Future updateUserName(String name) async { + if (name.trim().isNotEmpty) { + userInfo.update((user) { + if (user != null) { + user.name = name.trim(); + } + }); + await saveUserInfo(); + } } } \ No newline at end of file diff --git a/lib/modules/profile/views/profile_view.dart b/lib/modules/profile/views/profile_view.dart index 84e8a25fa..c23bf2685 100644 --- a/lib/modules/profile/views/profile_view.dart +++ b/lib/modules/profile/views/profile_view.dart @@ -4,7 +4,9 @@ import '../controllers/profile_controller.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import '../../../core/widgets/common_bottom_nav.dart'; import '../../../routes/app_routes.dart'; +import 'dart:io'; +// ProfileView: 用户设置页面 class ProfileView extends GetView { const ProfileView({Key? key}) : super(key: key); @@ -132,4 +134,104 @@ class ProfileView extends GetView { bottomNavigationBar: const CommonBottomNav(currentIndex: 2), ); } -} \ No newline at end of file +} + + +// EditProfileView: 编辑个人资料页面 +class EditProfileView extends GetView { + const EditProfileView({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final isDarkMode = Theme.of(context).brightness == Brightness.dark; + final nameController = TextEditingController(text: controller.userInfo.value.name); + + return Scaffold( + appBar: AppBar( + title: const Text('个人资料'), + actions: [ + TextButton( + onPressed: () { + // 保存用户名 + controller.updateUserName(nameController.text); + // 返回上一级页面 + Get.back(); + }, + child: Text( + '保存', + style: TextStyle( + fontSize: 16.sp, + color: isDarkMode ? Colors.blue[300] : Colors.blue, + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ), + body: Padding( + padding: EdgeInsets.all(16.w), + child: Column( + children: [ + // 头像编辑 + Center( + child: GestureDetector( + onTap: () => controller.updateUserAvatar(), + child: Obx(() => CircleAvatar( + radius: 50.r, + backgroundImage: controller.userInfo.value.avatar.isNotEmpty + ? FileImage(File(controller.userInfo.value.avatar)) + : null, + child: controller.userInfo.value.avatar.isEmpty + ? Icon(Icons.person, size: 50.r) + : null, + // onBackgroundImageError: hasAvatar + // ? (_, __) {} + // : null, + )), + ), + ), + SizedBox(height: 20.h), + // 昵称编辑 - 使用外部定义的controller而不是每次重建 + TextField( + controller: nameController, + decoration: const InputDecoration( + labelText: '昵称', + border: OutlineInputBorder(), + ), + ), + // ... 其他编辑项 ... + + SizedBox(height: 30.h), + // 添加一个大的保存按钮 + SizedBox( + width: double.infinity, + height: 50.h, + child: ElevatedButton( + onPressed: () { + // 保存用户名 + controller.updateUserName(nameController.text); + // 返回上一级页面 + Get.back(); + }, + style: ElevatedButton.styleFrom( + backgroundColor: isDarkMode ? Colors.blue[700] : Colors.blue, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10.r), + ), + ), + child: Text( + '保存', + style: TextStyle( + fontSize: 16.sp, + color: Colors.white, + fontWeight: FontWeight.w500, + ), + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/modules/settings/controllers/settings_controller.dart b/lib/modules/settings/controllers/settings_controller.dart index 1ab68e829..5275b0009 100644 --- a/lib/modules/settings/controllers/settings_controller.dart +++ b/lib/modules/settings/controllers/settings_controller.dart @@ -1,7 +1,10 @@ import 'package:get/get.dart'; import 'package:flutter/material.dart'; +import 'package:image_picker/image_picker.dart'; import '../../../core/themes/theme_service.dart'; import '../../../data/services/speech_factory.dart'; +import 'package:get_storage/get_storage.dart'; +import '../../../data/models/user_model.dart'; class SettingsController extends GetxController { // 主题服务 @@ -21,6 +24,12 @@ class SettingsController extends GetxController { {'type': SpeechServiceType.azure, 'name': 'Azure 语音服务', 'description': '微软云端语音服务,支持高质量语音合成和识别'}, {'type': SpeechServiceType.flutter, 'name': 'Flutter 本地语音服务', 'description': '设备本地语音服务,无需联网即可使用'}, ]; + + //用户信息 + static const String userKey='user_info'; + final _storage=GetStorage(); + final ImagePicker _picker=ImagePicker(); + final Rx userInfo=UserModel.defaultUser().obs; @override void onInit() { @@ -34,7 +43,60 @@ class SettingsController extends GetxController { // 获取当前语音服务类型 _loadCurrentSpeechServiceType(); + + //获取用户信息 + loadUserInfo(); + } + + void loadUserInfo() { + final userJson = _storage.read(userKey); + if (userJson != null) { + userInfo.value = UserModel.fromJson(userJson); + } + } + + Future saveUserInfo() async { + await _storage.write(userKey, userInfo.value.toJson()); } + + Future updateUserAvatar() async { + final XFile? image = await _picker.pickImage( + source: ImageSource.gallery, + maxWidth: 800, + maxHeight: 800, + imageQuality: 85, + ); + + if (image != null) { + userInfo.update((user) { + if (user != null) { + user.avatar = image.path; + } + }); + await saveUserInfo(); + } + } + + Future updateUserName(String name) async { + if (name.trim().isNotEmpty) { + userInfo.update((user) { + if (user != null) { + user.name = name.trim(); + } + }); + await saveUserInfo(); + } + } + +// Future updateDeviceName(String deviceName) async { +// userInfo.update((user) { +// if (user != null) { +// user.deviceName = deviceName; +// } +// }); +// await saveUserInfo(); +// } +// } // 加载当前语音服务类型 void _loadCurrentSpeechServiceType() { diff --git a/lib/modules/settings/views/settings_view.dart b/lib/modules/settings/views/settings_view.dart index d66e6e9c9..126ab3ab5 100644 --- a/lib/modules/settings/views/settings_view.dart +++ b/lib/modules/settings/views/settings_view.dart @@ -5,6 +5,7 @@ import 'dart:ui'; import '../controllers/settings_controller.dart'; import '../../../core/widgets/common_bottom_nav.dart'; import '../../../routes/app_routes.dart'; +import 'dart:io'; class SettingsView extends GetView { const SettingsView({Key? key}) : super(key: key); @@ -74,13 +75,15 @@ class SettingsView extends GetView { ), ], ), - child: ClipRRect( + child: ClipRRect(//裁剪圆角矩形 borderRadius: BorderRadius.circular(16.r), - child: BackdropFilter( + child: BackdropFilter(// 背景滤镜(实现毛玻璃效果) filter: isDarkMode ? ImageFilter.blur(sigmaX: 10, sigmaY: 10) : ImageFilter.blur(sigmaX: 0, sigmaY: 0), child: Row( children: [ - Container( + Obx(()=>GestureDetector( + onTap: ()=>controller.updateUserAvatar(), + child:Container(//用户头像部分 width: 60.w, height: 60.w, decoration: BoxDecoration( @@ -93,21 +96,27 @@ class SettingsView extends GetView { ), ], ), - child: ClipRRect( + child: ClipRRect(// 裁剪圆形头像 borderRadius: BorderRadius.circular(30.r), - child: Image.network( + child: controller.userInfo.value.avatar.isEmpty + ? Image.network( 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?ixlib=rb-1.2.1&auto=format&fit=crop&w=200&q=80', - fit: BoxFit.cover, - ), + fit: BoxFit.cover,// 图片填充方式 + ) + :Image.file( + File(controller.userInfo.value.avatar), + fit: BoxFit.cover,// 图片填充方式 + ) ), ), + )), SizedBox(width: 16.w), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + Expanded(// 占据剩余空间 + child: Obx(()=>Column( + crossAxisAlignment: CrossAxisAlignment.start,// 左对齐 children: [ - Text( - '林美珍', + Text(//用户名 + controller.userInfo.value.name, style: TextStyle( fontSize: 18.sp, fontWeight: FontWeight.w600, @@ -118,14 +127,14 @@ class SettingsView extends GetView { SizedBox(height: 4.h), Row( children: [ - Container( + Container(// 会员等级 padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 2.h), decoration: BoxDecoration( color: Colors.blue[600], borderRadius: BorderRadius.circular(10.r), ), child: Text( - '高级会员', + controller.userInfo.value.membershipLevel, style: TextStyle( fontSize: 10.sp, color: Colors.white, @@ -147,7 +156,7 @@ class SettingsView extends GetView { ), SizedBox(height: 4.h), Text( - '账号ID: deepvoice_834291', + controller.userInfo.value.id, style: TextStyle( fontSize: 12.sp, color: isDarkMode ? Colors.white70 : Colors.black54, @@ -155,8 +164,8 @@ class SettingsView extends GetView { ), ], ), - ), - Container( + ),), + Container(// 编辑按钮 width: 32.w, height: 32.w, decoration: BoxDecoration( @@ -172,11 +181,15 @@ class SettingsView extends GetView { ), ], ), - child: Icon( - Icons.edit, - size: 16.w, - color: isDarkMode ? Colors.white70 : Colors.grey[600], - ), + child: InkWell( + onTap: () => Get.toNamed(Routes.editProfile), + child: Icon( + Icons.edit, + size: 16.w, + color: isDarkMode ? Colors.white70 : Colors.grey[600], + ), + ) + ), ], ), diff --git a/lib/routes/app_pages.dart b/lib/routes/app_pages.dart index 7c3388e5e..034cc859f 100644 --- a/lib/routes/app_pages.dart +++ b/lib/routes/app_pages.dart @@ -64,6 +64,11 @@ abstract class AppPages { page: () => const ProfileView(), binding: ProfileBinding(), ), + GetPage( + name: Routes.editProfile, + page: () => const EditProfileView(), + binding: ProfileBinding(), + ), GetPage( name: Routes.translation, diff --git a/lib/routes/app_routes.dart b/lib/routes/app_routes.dart index 1d6c8a1ea..b5e34e078 100644 --- a/lib/routes/app_routes.dart +++ b/lib/routes/app_routes.dart @@ -18,4 +18,5 @@ abstract class Routes { static const translationHistory = '/translation_history'; static const FLUTTER_TTS_TEST = '/flutter_tts_test'; static const FLUTTER_ASR_TEST = '/flutter_asr_test'; + static const editProfile='/edit_profile'; } \ No newline at end of file