diff --git a/lib/data/models/user_Info.dart b/lib/data/models/user_Info.dart index ecbbaf33e..3f369b0c0 100644 --- a/lib/data/models/user_Info.dart +++ b/lib/data/models/user_Info.dart @@ -24,6 +24,7 @@ class User { String phone; String phonemac; String password; + int membershipLevel=0;//添加会员等级字段,0表示普通用户 // final Int64 lastbettime; // final Int64 createtime; // final String wxopenid; @@ -42,6 +43,7 @@ class User { required this.phone, required this.phonemac, required this.password, + this.membershipLevel = 0, // 添加默认值 // required this.lastbettime, // required this.createtime, // required this.wxopenid, @@ -66,6 +68,7 @@ class User { phone: (userData?['phone'] as String?) ?? '', phonemac: (userData?['phonemac'] as String?) ?? '', password: (userData?['password'] as String?) ?? '', + membershipLevel: (userData?['membershipLevel'] as int?) ?? 0, // 提供默认值 // lastbettime: _parseTimestamp(userData['lastbettime']), // createtime: _parseTimestamp(userData['createtime']), // wxopenid: (userData?['wxopenid'] as String?) ?? '', @@ -111,6 +114,7 @@ class User { 'phone': phone, 'phonemac': phonemac, 'password': password, + 'membershipLevel': membershipLevel, // 'lastbettime': lastbettime, // 'createtime': createtime, // 'wxopenid': wxopenid, diff --git a/lib/data/models/user_model.dart b/lib/data/models/user_model.dart deleted file mode 100644 index ff3c07de4..000000000 --- a/lib/data/models/user_model.dart +++ /dev/null @@ -1,39 +0,0 @@ -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: 'DeepSound_${DateTime.now().millisecondsSinceEpoch.toString()}', - //deviceName: '', - membershipLevel: '普通会员', - ); -} \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 448f33e5e..dc5373757 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -65,7 +65,7 @@ class MainApp extends StatelessWidget { splitScreenMode: true, builder: (_, child) { return GetMaterialApp( - title: 'Deep Voice', + title: 'DeepSound', theme: AppTheme.lightTheme, darkTheme: AppTheme.darkTheme, themeMode: themeMode, // 使用提前获取的主题模式 diff --git a/lib/modules/profile/bindings/profile_binding.dart b/lib/modules/profile/bindings/profile_binding.dart index 6bfc58f45..ca7ef9b5d 100644 --- a/lib/modules/profile/bindings/profile_binding.dart +++ b/lib/modules/profile/bindings/profile_binding.dart @@ -6,4 +6,4 @@ class ProfileBinding extends Bindings { void dependencies() { Get.lazyPut(() => ProfileController()); } -} \ No newline at end of file +} \ 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 083d9b9e4..5f6e09933 100644 --- a/lib/modules/profile/controllers/profile_controller.dart +++ b/lib/modules/profile/controllers/profile_controller.dart @@ -1,11 +1,13 @@ import 'package:get/get.dart'; import 'package:get_storage/get_storage.dart'; import 'package:flutter/material.dart'; +import 'package:http/http.dart'; import 'package:image_picker/image_picker.dart'; import 'package:logger/logger.dart'; -import '../../../data/models/user_model.dart'; +import '../../../data/models/user_Info.dart'; import '../../settings/controllers/settings_controller.dart'; import 'dart:io'; +import 'dart:convert'; import 'package:flutter_image_compress/flutter_image_compress.dart'; class ProfileController extends GetxController { @@ -20,7 +22,8 @@ class ProfileController extends GetxController { static const String userKey = 'user_info'; final _storage = GetStorage(); final ImagePicker _picker = ImagePicker(); - final Rx userInfo = UserModel.defaultUser().obs; + final Rx userInfo = User.instance.obs; + //final RxString localAvatar = ''.obs; // 使用响应式变量 final currentLanguage = 'zh'.obs; @@ -111,10 +114,48 @@ class ProfileController extends GetxController { // _isEditing.value = !_isEditing.value; // } + // 更新用户信息到后端 + Future _syncWithBackend() async { + try { + // 准备数据 + Map data = { + "image": "", // 默认空字符串 + "name": userInfo.value.name, + "language": currentLanguage.value, + }; + + // 如果有头像,转换为base64 + if (userInfo.value.avatar.isNotEmpty) { + File imageFile = File(userInfo.value.avatar); + List imageBytes = await imageFile.readAsBytes(); + String base64Image = base64Encode(imageBytes); + data["image"] = base64Image; + } + + // 调用API + final response = await GetConnect().post( + 'https://www.deapsound.com:443/api/home/user_setting', + headers: { + 'accept': 'application/json', + 'Content-Type': 'application/json', + }, + data, + ); + + if (response.statusCode != 200) { + throw '更新失败:${response.body}'; + } + } catch (e) { + Logger().e('同步到后端失败: $e'); + Get.snackbar('错误','同步到服务器失败,请重试'); + throw e; + } + } + void loadUserInfo() { final userJson = _storage.read(userKey); if (userJson != null) { - userInfo.value = UserModel.fromJson(userJson); + userInfo.value = User.instance; } } @@ -162,7 +203,8 @@ class ProfileController extends GetxController { user.avatar = image.path; } }); - await saveUserInfo(); + await saveUserInfo();//保存到本地 + await _syncWithBackend();//同步到后端 // 通知 SettingsController 更新 if (Get.isRegistered()) { @@ -185,16 +227,17 @@ class ProfileController extends GetxController { } }); - await saveUserInfo(); + await saveUserInfo();//保存到本地 + await _syncWithBackend();//同步到后端 // 同步更新 SettingsController if (Get.isRegistered()) { - final settingsController = Get.find(); + final settingsController = Get.find(); settingsController.loadUserInfo(); } } catch (e) { Logger().e('Error updating name: $e'); - Get.snackbar('Error', 'Failed to update user name'); + Get.snackbar('错误', 'Failed to update user name'); } } } diff --git a/lib/modules/profile/views/profile_view.dart b/lib/modules/profile/views/profile_view.dart index c2947f532..e66e0bdcf 100644 --- a/lib/modules/profile/views/profile_view.dart +++ b/lib/modules/profile/views/profile_view.dart @@ -169,11 +169,10 @@ class EditProfileView extends GetView { children: [ Obx(() => CircleAvatar( radius: 50.r, - backgroundImage: - controller.userInfo.value.avatar.isNotEmpty - ? FileImage( - File(controller.userInfo.value.avatar)) - : null, + backgroundImage: controller + .userInfo.value.avatar.isNotEmpty + ? NetworkImage(controller.userInfo.value.avatar) + : null, child: controller.userInfo.value.avatar.isEmpty ? Icon(Icons.person, size: 50.r) : null, diff --git a/lib/modules/settings/controllers/settings_controller.dart b/lib/modules/settings/controllers/settings_controller.dart index e639e0f91..6730e3aba 100644 --- a/lib/modules/settings/controllers/settings_controller.dart +++ b/lib/modules/settings/controllers/settings_controller.dart @@ -1,3 +1,4 @@ +import 'package:firebase_auth/firebase_auth.dart' as firebase; import 'package:get/get.dart'; import 'package:flutter/material.dart'; import 'package:image_picker/image_picker.dart'; @@ -5,7 +6,8 @@ import 'package:get_storage/get_storage.dart'; import '../../../core/themes/theme_service.dart'; import '../../../data/services/speech_factory.dart'; import '../models/language_model.dart'; -import '../../../data/models/user_model.dart'; +//import '../../../data/models/user_model.dart'; +import '../../../data/models/user_Info.dart'; class SettingsController extends GetxController { // 语言设置 @@ -61,7 +63,7 @@ class SettingsController extends GetxController { static const String userKey='user_info'; //final _storage=GetStorage(); final ImagePicker _picker=ImagePicker(); - final Rx userInfo=UserModel.defaultUser().obs; + final Rx userInfo=User.instance.obs;//使用单例实例 @override void onInit() { @@ -86,7 +88,7 @@ class SettingsController extends GetxController { void loadUserInfo() { final userJson = _storage.read(userKey); if (userJson != null) { - userInfo.value = UserModel.fromJson(userJson); + userInfo.value = User.instance; } } diff --git a/lib/modules/settings/views/settings_view.dart b/lib/modules/settings/views/settings_view.dart index 773d442d3..53864d00c 100644 --- a/lib/modules/settings/views/settings_view.dart +++ b/lib/modules/settings/views/settings_view.dart @@ -1,24 +1,25 @@ +import 'package:app_settings/app_settings.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'dart:ui'; +import '../../../core/theme/app_colors.dart'; import '../controllers/settings_controller.dart'; import '../../../core/widgets/common_bottom_nav.dart'; import '../../../routes/app_routes.dart'; -import '../../../data/models/user_Info.dart'; +//import '../../../data/models/user_Info.dart'; +import 'dart:io'; class SettingsView extends GetView { const SettingsView({Key? key}) : super(key: key); // 正确获取用户名方式 - String get userName => User.getname(); + // String get userName => User.getname(); - String get deviceInfo => User.getdeviceInfo(); - String get userId => User.getuserId(); - String get standing => User.getstanding(); - String get avatar => User.getnavatar(); - //String get avatar => - //"https://lh3.googleusercontent.com/a/ACg8ocJw96pHqo3uceyxyrWziQr8eGS-CZZ_NhRBNC_EvmGkAyf6TQ=s96-c"; + // String get deviceInfo => User.getdeviceInfo(); + // String get userId => User.getuserId(); + // String get standing => User.getstanding(); + // String get avatar => User.getnavatar(); @override Widget build(BuildContext context) { // 直接使用 Theme.of(context) 获取当前主题 @@ -30,7 +31,7 @@ class SettingsView extends GetView { backgroundColor: isDarkMode ? Colors.black : Colors.white, elevation: 0.5, title: Text( - '设置', + 'settings'.tr, // 设置 style: TextStyle( fontSize: 17.sp, fontWeight: FontWeight.w600, @@ -81,6 +82,7 @@ class SettingsView extends GetView { ], ), child: ClipRRect( + //裁剪圆角矩形 borderRadius: BorderRadius.circular(16.r), child: BackdropFilter( filter: isDarkMode @@ -88,105 +90,133 @@ class SettingsView extends GetView { : ImageFilter.blur(sigmaX: 0, sigmaY: 0), child: Row( children: [ - Container( - width: 60.w, - height: 60.w, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(30.r), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.1), - blurRadius: 8, - offset: const Offset(0, 2), + Obx(() => GestureDetector( + //onTap: ()=>controller.updateUserAvatar(), + child: Container( + //用户头像部分 + width: 60.w, + height: 60.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(30.r), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + blurRadius: 8, + offset: const Offset(0, 2), + ), + ], + ), + child: ClipRRect( + // 裁剪圆形头像 + borderRadius: BorderRadius.circular(30.r), + 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, // 图片填充方式 + ) + : Image.network( + controller.userInfo.value.avatar, + fit: BoxFit.cover, // 图片填充方式 + )), ), - ], - ), - child: ClipRRect( - borderRadius: BorderRadius.circular(30.r), - child: Image.network( - avatar, - fit: BoxFit.cover, - ), - ), - ), + )), SizedBox(width: 16.w), Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - userName, - style: TextStyle( - fontSize: 18.sp, - fontWeight: FontWeight.w600, - color: isDarkMode ? Colors.white : Colors.black87, + // 占据剩余空间 + child: Obx( + () => Column( + crossAxisAlignment: CrossAxisAlignment.start, // 左对齐 + children: [ + Text( + //用户名 + controller.userInfo.value.name, + style: TextStyle( + fontSize: 18.sp, + fontWeight: FontWeight.w600, + color: isDarkMode ? Colors.white : Colors.black87, + ), + overflow: TextOverflow.ellipsis, ), - ), - SizedBox(height: 4.h), - Row( - children: [ - Container( - padding: EdgeInsets.symmetric( - horizontal: 8.w, vertical: 2.h), - decoration: BoxDecoration( - color: Colors.blue[600], - borderRadius: BorderRadius.circular(10.r), - ), - child: Text( - standing, - style: TextStyle( - fontSize: 10.sp, - color: Colors.white, + SizedBox(height: 4.h), + Row( + children: [ + Container( + padding: EdgeInsets.symmetric( + horizontal: 8.w, vertical: 2.h), + decoration: BoxDecoration( + color: Colors.blue[600], + borderRadius: BorderRadius.circular(10.r), ), + child: Obx(() { + final membershipMap = { + 0: 'freeUser', //'免费用户' + 1: 'standardUser', //'标准用户' + 2: 'proUser', //'专业用户' + }; + final membershipString = membershipMap[controller + .userInfo.value.membershipLevel] ?? + 'Unknown'; + return Text( + membershipString, + style: TextStyle( + fontSize: 10.sp, + color: Colors.white, + ), + ); + }), ), - ), - SizedBox(width: 8.w), - Flexible( - child: Text( - deviceInfo, - style: TextStyle( - fontSize: 12.sp, - color: - isDarkMode ? Colors.white70 : Colors.black54, + SizedBox(width: 8.w), + Flexible( + child: Text( + '${'joinDevice'.tr} DeepSound Pro', // 已连接设备: + style: TextStyle( + fontSize: 12.sp, + color: isDarkMode + ? Colors.white70 + : Colors.black54, + ), + overflow: TextOverflow.ellipsis, ), - overflow: TextOverflow.ellipsis, ), + ], + ), + SizedBox(height: 4.h), + Text( + controller.userInfo.value.uid, + style: TextStyle( + fontSize: 12.sp, + color: isDarkMode ? Colors.white70 : Colors.black54, ), - ], - ), - SizedBox(height: 4.h), - Text( - userId, - style: TextStyle( - fontSize: 12.sp, - color: isDarkMode ? Colors.white70 : Colors.black54, ), - ), - ], + ], + ), ), ), Container( - width: 32.w, - height: 32.w, - decoration: BoxDecoration( - color: isDarkMode - ? Colors.white.withOpacity(0.1) - : Colors.white.withOpacity(0.7), - borderRadius: BorderRadius.circular(16.r), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.05), - blurRadius: 4, - offset: const Offset(0, 1), + // 编辑按钮 + width: 32.w, + height: 32.w, + decoration: BoxDecoration( + color: isDarkMode + ? Colors.white.withOpacity(0.1) + : Colors.white.withOpacity(0.7), + borderRadius: BorderRadius.circular(16.r), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.05), + blurRadius: 4, + offset: const Offset(0, 1), + ), + ], + ), + child: InkWell( + onTap: () => Get.toNamed(Routes.editProfile), + child: Icon( + Icons.edit, + size: 16.w, + color: isDarkMode ? Colors.white70 : Colors.grey[600], ), - ], - ), - child: Icon( - Icons.edit, - size: 16.w, - color: isDarkMode ? Colors.white70 : Colors.grey[600], - ), - ), + )), ], ), ), @@ -202,7 +232,7 @@ class SettingsView extends GetView { Padding( padding: EdgeInsets.only(left: 4.w, bottom: 8.h), child: Text( - '系统设置', + 'systemSetting'.tr, // 系统设置 style: TextStyle( fontSize: 12.sp, fontWeight: FontWeight.w500, @@ -233,22 +263,25 @@ class SettingsView extends GetView { : ImageFilter.blur(sigmaX: 0, sigmaY: 0), child: Column( children: [ - // 通知设置 - _buildSimpleNavigationSetting( - title: '通知设置', - subtitle: '管理应用推送和提醒', - icon: Icons.notifications_none, - iconBgColor: isDarkMode - ? Colors.indigo[900]!.withOpacity(0.3) - : Colors.indigo[100]!, - iconColor: - isDarkMode ? Colors.indigo[300]! : Colors.indigo[600]!, - titleColor: isDarkMode ? Colors.white : null, - subtitleColor: isDarkMode ? Colors.white70 : null, - onTap: () { - // 打开通知设置页面 - }, - isDarkMode: isDarkMode, + // 语言设置 + Obx( + () => _buildSimpleNavigationSetting( + title: 'languageSetting'.tr, // 语言设置 + subtitle: controller.language.value.name.tr, + icon: Icons.language, + iconBgColor: isDarkMode + ? Colors.indigo[900]!.withOpacity(0.3) + : Colors.indigo[100]!, + iconColor: isDarkMode + ? Colors.indigo[300]! + : Colors.indigo[600]!, + titleColor: isDarkMode ? Colors.white : null, + subtitleColor: isDarkMode ? Colors.white70 : null, + onTap: () { + _showLanguageSelectionDialog(isDarkMode); + }, + isDarkMode: isDarkMode, + ), ), Divider( height: 1, @@ -258,8 +291,8 @@ class SettingsView extends GetView { // 主题设置 _buildToggleSetting( - title: '暗黑模式', - subtitle: '切换应用的明暗主题', + title: 'darkMode'.tr, // 暗黑模式 + subtitle: 'darkModeInfo'.tr, // 切换应用的明暗主题 icon: isDarkMode ? Icons.dark_mode : Icons.light_mode, iconBgColor: isDarkMode ? Colors.indigo[900]!.withOpacity(0.3) @@ -280,15 +313,15 @@ class SettingsView extends GetView { // 备份与恢复 _buildNavigationSetting( - title: '备份与恢复', - subtitle: '备份设置和AI互动历史', + title: 'backups'.tr, // 备份与恢复 + subtitle: 'backupsInfo'.tr, // 备份设置和AI互动历史 icon: Icons.cloud_upload_outlined, iconBgColor: isDarkMode ? Colors.cyan[900]!.withOpacity(0.3) : Colors.cyan[100]!, iconColor: isDarkMode ? Colors.cyan[300]! : Colors.cyan[600]!, - value: '上次备份: 今天', + value: '${'lastBackup'.tr} 今天', // 上次备份: valueStyle: TextStyle( fontSize: 12.sp, color: isDarkMode ? Colors.white70 : Colors.grey[500], @@ -304,10 +337,17 @@ class SettingsView extends GetView { ? Colors.white.withOpacity(0.1) : Colors.grey[200]), + // 语音服务选择 + _buildSpeechServiceSetting(isDarkMode), + Divider( + height: 1, + color: isDarkMode + ? Colors.white.withOpacity(0.1) + : Colors.grey[200]), // 隐私设置 _buildSimpleNavigationSetting( - title: '隐私设置', - subtitle: '管理数据收集和权限', + title: 'privacySettings'.tr, // 隐私设置 + subtitle: 'privacySettingsInfo'.tr, // 管理数据收集和权限 icon: Icons.shield_outlined, iconBgColor: isDarkMode ? Colors.purple[900]!.withOpacity(0.3) @@ -317,7 +357,7 @@ class SettingsView extends GetView { titleColor: isDarkMode ? Colors.white : null, subtitleColor: isDarkMode ? Colors.white70 : null, onTap: () { - // 打开隐私设置页面 + AppSettings.openAppSettings(); }, isDarkMode: isDarkMode, ), @@ -329,7 +369,7 @@ class SettingsView extends GetView { // 退出登录 _buildSimpleNavigationSetting( - title: '退出登录', + title: 'logout'.tr, // 退出登录 icon: Icons.logout, iconBgColor: isDarkMode ? Colors.red[900]!.withOpacity(0.3) @@ -344,13 +384,13 @@ class SettingsView extends GetView { backgroundColor: isDarkMode ? Colors.grey[900] : Colors.white, title: Text( - '退出登录', + 'logout'.tr, // 退出登录 style: TextStyle( color: isDarkMode ? Colors.white : Colors.black87, ), ), content: Text( - '确定要退出当前账号吗?', + 'logoutTips'.tr, // 确定要退出当前账号吗? style: TextStyle( color: isDarkMode ? Colors.white70 : Colors.black54, @@ -360,7 +400,7 @@ class SettingsView extends GetView { TextButton( onPressed: () => Get.back(), child: Text( - '取消', + 'cancel'.tr, // 取消 style: TextStyle( color: isDarkMode ? Colors.white70 @@ -374,7 +414,7 @@ class SettingsView extends GetView { controller.signOut(); }, child: Text( - '确定', + 'confirm'.tr, // 确定 style: TextStyle( color: isDarkMode ? Colors.red[300] @@ -405,7 +445,7 @@ class SettingsView extends GetView { Padding( padding: EdgeInsets.only(left: 4.w, bottom: 8.h), child: Text( - '关于应用', + 'aboutApplication'.tr, // 关于应用 style: TextStyle( fontSize: 12.sp, fontWeight: FontWeight.w500, @@ -467,7 +507,7 @@ class SettingsView extends GetView { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - 'DeepVoice', + 'DeepSound', style: TextStyle( fontSize: 16.sp, fontWeight: FontWeight.w500, @@ -476,7 +516,7 @@ class SettingsView extends GetView { ), SizedBox(height: 2.h), Text( - '版本 2.4.1 (Build 87)', + '${'versions'.tr} 2.4.1 (Build 87)', // 版本 style: TextStyle( fontSize: 12.sp, color: @@ -497,7 +537,7 @@ class SettingsView extends GetView { borderRadius: BorderRadius.circular(16.r), ), child: Text( - '检查更新', + 'checkUpdates'.tr, // 检查更新 style: TextStyle( fontSize: 12.sp, color: isDarkMode ? Colors.blue[300] : Colors.blue[600], @@ -514,7 +554,7 @@ class SettingsView extends GetView { child: Column( children: [ Text( - '© 2023 DeepVoice Technologies', + '© 2025 DeepSound Technologies', style: TextStyle( fontSize: 12.sp, color: isDarkMode ? Colors.grey[400] : Colors.grey[500], @@ -522,7 +562,7 @@ class SettingsView extends GetView { ), SizedBox(height: 4.h), Text( - '连接世界,超越声音', + 'homeSlogan'.tr, // 连接世界,超越声音 style: TextStyle( fontSize: 12.sp, color: isDarkMode ? Colors.grey[400] : Colors.grey[500], @@ -551,45 +591,51 @@ class SettingsView extends GetView { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Row( - children: [ - Container( - width: 32.w, - height: 32.w, - decoration: BoxDecoration( - color: iconBgColor, - borderRadius: BorderRadius.circular(16.r), - ), - child: Icon( - icon, - size: 16.w, - color: iconColor, - ), - ), - SizedBox(width: 12.w), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - title, - style: TextStyle( - fontSize: 16.sp, - fontWeight: FontWeight.w500, - color: isDarkMode ? Colors.white : Colors.black87, - ), + Expanded( + child: Row( + children: [ + Container( + width: 32.w, + height: 32.w, + decoration: BoxDecoration( + color: iconBgColor, + borderRadius: BorderRadius.circular(16.r), ), - if (subtitle != null) SizedBox(height: 2.h), - if (subtitle != null) - Text( - subtitle, - style: TextStyle( - fontSize: 12.sp, - color: isDarkMode ? Colors.white70 : Colors.black54, + child: Icon( + icon, + size: 16.w, + color: iconColor, + ), + ), + SizedBox(width: 12.w), + Flexible( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.w500, + color: isDarkMode ? Colors.white : Colors.black87, + ), + overflow: TextOverflow.ellipsis, ), - ), - ], - ), - ], + if (subtitle != null) SizedBox(height: 2.h), + if (subtitle != null) + Text( + subtitle, + style: TextStyle( + fontSize: 12.sp, + color: isDarkMode ? Colors.white70 : Colors.black54, + ), + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + ], + ), ), Switch( value: value, @@ -620,55 +666,66 @@ class SettingsView extends GetView { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Row( - children: [ - Container( - width: 32.w, - height: 32.w, - decoration: BoxDecoration( - color: iconBgColor, - borderRadius: BorderRadius.circular(16.r), - ), - child: Icon( - icon, - size: 16.w, - color: iconColor, - ), - ), - SizedBox(width: 12.w), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - title, - style: TextStyle( - fontSize: 16.sp, - fontWeight: FontWeight.w500, - color: isDarkMode ? Colors.white : Colors.black87, - ), + Expanded( + child: Row( + children: [ + Container( + width: 32.w, + height: 32.w, + decoration: BoxDecoration( + color: iconBgColor, + borderRadius: BorderRadius.circular(16.r), ), - if (subtitle != null) SizedBox(height: 2.h), - if (subtitle != null) - Text( - subtitle, - style: TextStyle( - fontSize: 12.sp, - color: isDarkMode ? Colors.white70 : Colors.black54, + child: Icon( + icon, + size: 16.w, + color: iconColor, + ), + ), + SizedBox(width: 12.w), + Flexible( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.w500, + color: isDarkMode ? Colors.white : Colors.black87, + ), + overflow: TextOverflow.ellipsis, ), - ), - ], - ), - ], + if (subtitle != null) SizedBox(height: 2.h), + if (subtitle != null) + Text( + subtitle, + style: TextStyle( + fontSize: 12.sp, + color: + isDarkMode ? Colors.white70 : Colors.black54, + ), + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + ], + ), ), Row( + mainAxisSize: MainAxisSize.min, children: [ - Text( - value, - style: valueStyle ?? - TextStyle( - fontSize: 14.sp, - color: isDarkMode ? Colors.white : Colors.black87, - ), + Flexible( + child: Text( + value, + style: valueStyle ?? + TextStyle( + fontSize: 14.sp, + color: isDarkMode ? Colors.white : Colors.black87, + ), + overflow: TextOverflow.ellipsis, + ), ), SizedBox(width: 8.w), Container( @@ -713,47 +770,55 @@ class SettingsView extends GetView { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Row( - children: [ - Container( - width: 32.w, - height: 32.w, - decoration: BoxDecoration( - color: iconBgColor, - borderRadius: BorderRadius.circular(16.r), - ), - child: Icon( - icon, - size: 16.w, - color: iconColor, - ), - ), - SizedBox(width: 12.w), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - title, - style: TextStyle( - fontSize: 16.sp, - fontWeight: FontWeight.w500, - color: titleColor ?? - (isDarkMode ? Colors.white : Colors.black87), - ), + Expanded( + child: Row( + children: [ + Container( + width: 32.w, + height: 32.w, + decoration: BoxDecoration( + color: iconBgColor, + borderRadius: BorderRadius.circular(16.r), ), - if (subtitle != null) SizedBox(height: 2.h), - if (subtitle != null) - Text( - subtitle, - style: TextStyle( - fontSize: 12.sp, - color: subtitleColor ?? - (isDarkMode ? Colors.white70 : Colors.black54), + child: Icon( + icon, + size: 16.w, + color: iconColor, + ), + ), + SizedBox(width: 12.w), + Flexible( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.w500, + color: titleColor ?? + (isDarkMode ? Colors.white : Colors.black87), + ), + overflow: TextOverflow.ellipsis, ), - ), - ], - ), - ], + if (subtitle != null) SizedBox(height: 2.h), + if (subtitle != null) + Text( + subtitle, + style: TextStyle( + fontSize: 12.sp, + color: subtitleColor ?? + (isDarkMode + ? Colors.white70 + : Colors.black54), + ), + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + ], + ), ), Container( width: 32.w, @@ -775,4 +840,321 @@ class SettingsView extends GetView { ), ); } + + // 语音服务设置项 + Widget _buildSpeechServiceSetting(bool isDarkMode) { + return InkWell( + onTap: () { + _showSpeechServiceSelectionDialog(isDarkMode); + }, + child: Padding( + padding: EdgeInsets.all(16.w), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Row( + children: [ + Container( + width: 32.w, + height: 32.w, + decoration: BoxDecoration( + color: isDarkMode + ? Colors.teal[900]!.withOpacity(0.3) + : Colors.teal[100]!, + borderRadius: BorderRadius.circular(16.r), + ), + child: Icon( + Icons.record_voice_over, + size: 16.w, + color: isDarkMode ? Colors.teal[300]! : Colors.teal[600]!, + ), + ), + SizedBox(width: 12.w), + Flexible( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'voiceService'.tr, // 语音服务 + style: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.w500, + color: isDarkMode ? Colors.white : Colors.black87, + ), + ), + SizedBox(height: 2.h), + Text( + 'voiceServiceInfo'.tr, // 选择语音识别和合成服务 + style: TextStyle( + fontSize: 12.sp, + color: isDarkMode ? Colors.white70 : Colors.black54, + ), + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + ], + ), + ), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Obx(() { + final serviceType = controller.speechServiceType.value; + final serviceName = controller.availableSpeechServices + .firstWhere( + (service) => service['type'] == serviceType)['name']; + return Flexible( + child: Text( + '$serviceName'.tr, + style: TextStyle( + fontSize: 12.sp, + color: isDarkMode ? Colors.white70 : Colors.grey[600], + ), + overflow: TextOverflow.ellipsis, + ), + ); + }), + SizedBox(width: 8.w), + Container( + width: 32.w, + height: 32.w, + decoration: BoxDecoration( + color: isDarkMode + ? Colors.white.withOpacity(0.05) + : Colors.grey[100], + borderRadius: BorderRadius.circular(16.r), + ), + child: Icon( + Icons.chevron_right, + size: 16.w, + color: isDarkMode ? Colors.white70 : Colors.grey[500], + ), + ), + ], + ), + ], + ), + ), + ); + } + + // 显示语音服务选择对话框 + void _showSpeechServiceSelectionDialog(bool isDarkMode) { + Get.dialog( + Dialog( + backgroundColor: isDarkMode ? Colors.grey[900] : Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16.r), + ), + child: Padding( + padding: EdgeInsets.all(16.w), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'selectVoiceService'.tr, // 选择语音服务 + style: TextStyle( + fontSize: 18.sp, + fontWeight: FontWeight.w600, + color: isDarkMode ? Colors.white : Colors.black87, + ), + ), + SizedBox(height: 16.h), + ...controller.availableSpeechServices.map((service) { + return Obx(() { + final isSelected = + controller.speechServiceType.value == service['type']; + return InkWell( + onTap: () { + controller.setSpeechServiceType(service['type']); + Get.back(); + }, + child: Container( + margin: EdgeInsets.only(bottom: 12.h), + padding: EdgeInsets.all(12.w), + decoration: BoxDecoration( + color: isSelected + ? (isDarkMode + ? Colors.teal[900]!.withOpacity(0.3) + : Colors.teal[50]!) + : (isDarkMode + ? Colors.white.withOpacity(0.05) + : Colors.grey[100]!), + borderRadius: BorderRadius.circular(12.r), + border: Border.all( + color: isSelected + ? (isDarkMode + ? Colors.teal[300]! + : Colors.teal[600]!) + : Colors.transparent, + width: 1, + ), + ), + child: Row( + children: [ + Container( + width: 24.w, + height: 24.w, + decoration: BoxDecoration( + color: isSelected + ? (isDarkMode + ? Colors.teal[300] + : Colors.teal[600]) + : (isDarkMode + ? Colors.white.withOpacity(0.1) + : Colors.grey[300]), + shape: BoxShape.circle, + ), + child: isSelected + ? Icon( + Icons.check, + size: 16.w, + color: Colors.white, + ) + : null, + ), + SizedBox(width: 12.w), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '${service['name']}'.tr, + style: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.w500, + color: isDarkMode + ? Colors.white + : Colors.black87, + ), + overflow: TextOverflow.ellipsis, + ), + SizedBox(height: 4.h), + Text( + '${service['description']}'.tr, + style: TextStyle( + fontSize: 12.sp, + color: isDarkMode + ? Colors.white70 + : Colors.black54, + ), + overflow: TextOverflow.ellipsis, + maxLines: 2, + ), + ], + ), + ), + ], + ), + ), + ); + }); + }).toList(), + SizedBox(height: 16.h), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton( + onPressed: () => Get.back(), + child: Text( + 'cancel'.tr, // 取消 + style: TextStyle( + color: isDarkMode ? Colors.white70 : Colors.black54, + ), + ), + ), + ], + ), + ], + ), + ), + ), + ); + } + + // 显示语言选择对话框 + void _showLanguageSelectionDialog(bool isDarkMode) { + Get.dialog( + Dialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(24.r), + ), + insetPadding: EdgeInsets.symmetric(horizontal: 20.w), + backgroundColor: isDarkMode ? AppColors.cardBackground : Colors.white, + child: Container( + width: 0.9.sw, + padding: EdgeInsets.only(top: 32.h, bottom: 16.h), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.symmetric(horizontal: 24.w), + child: Text( + 'selectLanguage'.tr, // 选择语言 + style: TextStyle( + fontSize: 20.sp, + fontWeight: FontWeight.bold, + color: isDarkMode ? Colors.white : Colors.black, + ), + ), + ), + SizedBox(height: 16.h), + Container( + constraints: BoxConstraints(maxHeight: 0.5.sh), + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: controller.languageList.map((language) { + return ListTile( + title: Text( + language.name.tr, + style: TextStyle( + color: isDarkMode ? Colors.white : Colors.black, + ), + ), + trailing: + controller.language.value.value == language.value + ? Icon( + Icons.check_circle, + color: isDarkMode + ? AppColors.primary + : Colors.blue, + ) + : const SizedBox.shrink(), + onTap: () { + controller.setLanguage(language); + }, + ); + }).toList(), + ), + ), + ), + SizedBox(height: 16.h), + Align( + alignment: Alignment.centerRight, + child: Padding( + padding: EdgeInsets.only(right: 24.w), + child: TextButton( + onPressed: () => Get.back(), + child: Text( + 'cancel'.tr, // 取消 + style: TextStyle( + color: isDarkMode ? AppColors.primary : Colors.blue, + fontSize: 16.sp, + ), + ), + ), + ), + ), + ], + ), + ), + ), + ); + } }