Browse Source

Merge commit 'b56cf894981e7f0d96d5e6554dd8da3ab5a2f4da'

newdev_shunjiawei
lxm 2 years ago
parent
commit
4054c5ea8c
  1. 12
      android/app/build.gradle.kts
  2. 18
      lib/core/bindings/initial_binding.dart
  3. 19
      lib/data/models/user_Info.dart
  4. 23
      lib/data/services/login_service.dart
  5. 12
      lib/data/services/network/api.dart
  6. 2
      lib/data/services/network/dio_manager.dart
  7. 2
      lib/main.dart
  8. 96
      lib/modules/login/controllers/login_controller.dart
  9. 123
      lib/modules/profile/controllers/profile_controller.dart
  10. 14
      lib/modules/profile/views/profile_view.dart
  11. 1
      lib/modules/settings/controllers/settings_controller.dart
  12. 101
      lib/modules/splash/controllers/splash_controller.dart
  13. 9
      lib/modules/timbre/bindings/timbre_binding.dart
  14. 14
      lib/modules/timbre/controllers/timbre_controller.dart
  15. 168
      lib/modules/timbre/views/timbre_view.dart
  16. 7
      lib/routes/app_pages.dart
  17. 1
      lib/routes/app_routes.dart

12
android/app/build.gradle.kts

@ -57,6 +57,15 @@ android {
enableV1Signing = true
enableV2Signing = true
}
getByName("debug") {
// debug 使用与 release 相同的签名配置
keyAlias = keystoreProperties.getProperty("keyAlias")
keyPassword = keystoreProperties.getProperty("keyPassword")
storeFile = file(keystoreProperties.getProperty("storeFile"))
storePassword = keystoreProperties.getProperty("storePassword")
enableV1Signing = true
enableV2Signing = true
}
}
buildTypes {
@ -68,6 +77,9 @@ android {
signingConfig = signingConfigs.getByName("release")
// signingConfig = signingConfigs.getByName("debug")
}
debug {
signingConfig = signingConfigs.getByName("debug")
}
}
}

18
lib/core/bindings/initial_binding.dart

@ -25,17 +25,25 @@ class InitialBinding extends Bindings {
Get.lazyPut<ChatHistoryService>(() => ChatHistoryService(), fenix: true);
// 火山翻译服务
Get.lazyPut<VolcanoTranslationService>(() => VolcanoTranslationService(), fenix: true);
Get.lazyPut<VolcanoTranslationService>(() => VolcanoTranslationService(),
fenix: true);
// 火山AI服务
Get.lazyPut<VolcanoAIService>(() => VolcanoAIService(), fenix: true);
// 注意:VoiceInteractionService 在 HomeController 中初始化
Get.lazyPut<VoiceInteractionService>(() => VoiceInteractionService(), fenix: true);
Get.lazyPut<VoiceInteractionService>(() => VoiceInteractionService(),
fenix: true);
// 经典蓝牙服务
Get.lazyPut<ClassicBluetoothService>(() => ClassicBluetoothService(), fenix: true);
Get.lazyPut<ClassicBluetoothService>(() => ClassicBluetoothService(),
fenix: true);
// // 语言管理器(需要最先初始化)
// Get.put<LoginService>(LoginService(), permanent: true);
// await LoginService().FirebaseInit();
// print('资源FirebaseInit加载错误: 完成');
// await LoginService().WeChatInit();
// print('资源FWeChatInit加载错误: 完成');
}
}

19
lib/data/models/user_Info.dart

@ -53,11 +53,13 @@ class User {
factory User.fromJson(Map<String, dynamic> json) {
// 修复1:安全获取嵌套的 user 字段
final userData = json['data']['user'] as Map<String, dynamic>?; // 允许空值
final token = json['data']['token'] as String? ?? '';
final userData = json['user'] as Map<String, dynamic>?; // 允许空值
//final token = json['data']['token'] as String? ?? '';
//final token = '';
// 更新单例实例
return _instance = User._(
token: token as String? ?? '',
token: '',
//token: token as String? ?? '',
name: (userData?['name'] as String?) ?? '默认名称', // 提供默认值
uid: (userData?['uid'] as String?) ?? '',
avatar: (userData?['avatar'] as String?) ?? '',
@ -79,10 +81,10 @@ class User {
factory User.fronTokenJson(Map<String, dynamic> json) {
// 修复1:安全获取嵌套的 user 字段
final userData = json['user'] as Map<String, dynamic>?; // 允许空值
final token = '';
print("数据获取fronTokenJson${userData}");
// 更新单例实例
return _instance = User._(
token: token as String? ?? '',
token: '',
name: (userData?['name'] as String?) ?? '默认名称', // 提供默认值
uid: (userData?['uid'] as String?) ?? '',
avatar: (userData?['avatar'] as String?) ?? '',
@ -122,11 +124,4 @@ class User {
// 'facebookopenid': facebookopenid,
}
};
// 获取用户名的快捷方法
static String getname() => instance.name;
static String getdeviceInfo() => instance.deviceid;
static String getuserId() => instance.uid;
static String getstanding() => "";
static String getnavatar() => instance.avatar;
}

23
lib/data/services/login_service.dart

@ -12,14 +12,25 @@ class LoginService {
return '';
}));
if (Firebase.apps.isEmpty) {
Firebase.initializeApp(
_initializeFirebase();
}
Future<void> _initializeFirebase() async {
try {
// 尝试获取已存在的实例
Firebase.app('DeepSound');
print('已存在的 Firebase 实例被重用');
} catch (e) {
try {
// 如果不存在则初始化新实例
await Firebase.initializeApp(
name: 'DeepSound',
options: DefaultFirebaseOptions.currentPlatform,
//demoProjectId:'1:907754492821:android:2deffc5aa1037ae2145c9c'
).then((value) {
print('Firebase Initialized:${value.options}');
});
);
print('Firebase 初始化成功');
} catch (e) {
print('Firebase 初始化失败: $e');
}
}
}
}

12
lib/data/services/network/api.dart

@ -4,7 +4,7 @@ import 'nw_method.dart';
class Api {
// 登录
static login([params]) {
print("请求后台数据params=${params}");
print("请求后台数据login params=${params}");
return DioManager().request(
NWMethod.post,
'/api/home/user_sgin',
@ -22,7 +22,6 @@ class Api {
}
static tokenlogin([params]) {
print("请求后台数据params=${params}");
return DioManager().request(
NWMethod.post,
'/api/home/user_getinfo',
@ -38,4 +37,13 @@ class Api {
params: params,
);
}
//更新用户设置
static updateUserSetting([params]) {
return DioManager().request(
NWMethod.post,
'/api/home/user_setting',
params: params,
);
}
}

2
lib/data/services/network/dio_manager.dart

@ -21,7 +21,7 @@ class DioManager {
dio = Dio(options);
//fdp
// dio.interceptors.add(AuthInterceptor());
dio.interceptors.add(AuthInterceptor());
}
// 请求,返回参数为 T

2
lib/main.dart

@ -24,7 +24,7 @@ void main() async {
// 初始化环境变量
await dotenv.load();
LoginService();
//LoginService.FirebaseInit();
// 设置窗口大小(仅在 macOS 下生效)
if (Platform.isMacOS) {
const double width = 375.0;

96
lib/modules/login/controllers/login_controller.dart

@ -67,7 +67,6 @@ class LoginController extends GetxController {
_contactValue.value = contactController.text;
_formatDisplayContact();
_startCountdown();
_sendVerificationCode();
}
@ -133,7 +132,7 @@ class LoginController extends GetxController {
_contactValue.value = contactController.text;
_formatDisplayContact();
_viewState.value = LoginViewState.verification;
_startCountdown();
_sendVerificationCode();
}
@ -185,6 +184,8 @@ class LoginController extends GetxController {
Future<void> _sendVerificationCode() async {
// 这里应该是实际发送验证码的逻辑
// 为了演示,我们只是模拟发送
if (isEmialContactController() || isPhoneContactController()) {
_startCountdown();
var response = await Api.getCode({
"addr": contactController.text,
"vtype": 0,
@ -194,6 +195,16 @@ class LoginController extends GetxController {
//短信已发送
//EasyLoading.showInfo('codeSend'.tr);
}
} else {
Get.snackbar(
'提示',
'请正确的手机号码或邮箱',
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.red.withOpacity(0.1),
colorText: Colors.red,
);
return;
}
}
// 重新发送验证码
@ -205,6 +216,7 @@ class LoginController extends GetxController {
// 更新验证码输入
void updateVerificationCode(int index, String value) {
print("updateVerificationCode=${index}${value}");
if (index >= 0 && index < 4) {
var newList = [..._verificationCode];
newList[index] = value;
@ -217,40 +229,32 @@ class LoginController extends GetxController {
_verificationCode.value = ['', '', '', ''];
}
// 检查验证码是否完整
bool isVerificationCodeComplete() {
return !_verificationCode.contains('');
// 检查邮箱格式
bool isEmialContactController() {
// 检查每个元素是否为单个字母或数字字符
final text = contactController.text;
return (text.length > 5 &&
RegExp(r'^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$')
.hasMatch(text));
}
Future<void> getLoginToken() async {
// 使用正确语法创建请求头
var response = await Api.tokenlogin();
if (response != null) {
if (response?["code"] == null) {
final user = User.fronTokenJson(response);
await _storage.write(
'user', jsonEncode(user.toJson())); // 使用 user.toJson()
Get.offAllNamed(Routes.pairing);
}
} else {
login();
// 检查手机号码格式
bool isPhoneContactController() {
// 检查每个元素是否为单个字母或数字字符
final text = contactController.text;
return RegExp(r'^[0-9]+$').hasMatch(text);
}
// 检查验证码是否完整
bool isVerificationCodeComplete() {
// 检查每个元素是否为单个字母或数字字符
final text = verificationCodeController.text;
print("数据获取Facebook${text.length}");
return text.length == 4 && RegExp(r'^[0-9]+$').hasMatch(text);
}
// 登录
Future<void> login() async {
// 暂时关闭验证码验证
// if (!isVerificationCodeComplete()) {
// Get.snackbar(
// '提示',
// '请输入完整的验证码',
// snackPosition: SnackPosition.TOP,
// backgroundColor: Colors.red.withOpacity(0.1),
// colorText: Colors.red,
// );
// return;
// }
_isLoading.value = true;
try {
@ -260,16 +264,16 @@ class LoginController extends GetxController {
switch (loginType) {
case 0 || 1:
{
// if (!isVerificationCodeComplete()) {
// Get.snackbar(
// '提示',
// '请输入完整的验证码',
// snackPosition: SnackPosition.TOP,
// backgroundColor: Colors.red.withOpacity(0.1),
// colorText: Colors.red,
// );
// break;
// }
if (!isVerificationCodeComplete()) {
Get.snackbar(
'提示',
'请输入完整的验证码',
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.red.withOpacity(0.1),
colorText: Colors.red,
);
break;
}
requestLogin({
"mail": contactController.text,
"phone": "",
@ -425,21 +429,21 @@ class LoginController extends GetxController {
try {
print('响应数据: ${response.toString()}'); // 调试输出原始数据
if (response['code'].toString() == '0') {
if (response['token'].toString() != '') {
// 新增:检查 user 字段是否存在且类型正确
if (response.containsKey('data') && response['data'] is Map) {
// if (response.containsKey('data') && response['data'] is Map) {
final user = User.fromJson(response);
print('用户初始化成功: ${user.token}');
await _storage.write("logintoken", user.token);
print('用户初始化成功: ${response['token'].toString()}');
await _storage.write("logintoken", response['token'].toString());
// 存储到 SharedPreferences (仅存储用户数据)
//final prefs = await GetStorage();
await _storage.write(
'user', jsonEncode(user.toJson())); // 使用 user.toJson()
Get.offAllNamed(Routes.pairing);
} else {
throw FormatException('API 响应缺少 user 字段');
}
// } else {
// throw FormatException('API 响应缺少 user 字段');
// }
} else {
throw Exception('接口返回错误: ${response['msg']}');
}

123
lib/modules/profile/controllers/profile_controller.dart

@ -1,7 +1,7 @@
import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart';
//import 'package:http/http.dart';
import 'package:image_picker/image_picker.dart';
import 'package:logger/logger.dart';
import '../../../data/models/user_Info.dart';
@ -9,6 +9,9 @@ import '../../settings/controllers/settings_controller.dart';
import 'dart:io';
import 'dart:convert';
import 'package:flutter_image_compress/flutter_image_compress.dart';
import 'package:path_provider/path_provider.dart';
import '../../../data/services/network/api.dart';
import 'package:dio/dio.dart';
class ProfileController extends GetxController {
static ProfileController get to => Get.find();
@ -19,11 +22,11 @@ class ProfileController extends GetxController {
// final _isEditing = false.obs;
// final _storage = GetStorage();
//用户信息
static const String userKey = 'user_info';
final _storage = GetStorage();
final ImagePicker _picker = ImagePicker();
final Rx<User> userInfo = User.instance.obs;
//final RxString localAvatar = ''.obs;
static const String userKey = 'user_info'; //用户信息存储键
final _storage = GetStorage(); //本地存储
final ImagePicker _picker = ImagePicker(); //图片选择器
final Rx<User> userInfo = User.instance.obs; // 响应式用户信息对象,使用单例模式确保全局唯一性
final RxString localAvatar = ''.obs; // 本地头像路径,便于响应式更新UI
// 使用响应式变量
final currentLanguage = 'zh'.obs;
@ -120,30 +123,55 @@ class ProfileController extends GetxController {
// 准备数据
Map<String, dynamic> data = {
"image": "", // 默认空字符串
"name": userInfo.value.name,
"language": currentLanguage.value,
"name": userInfo.value.name,
};
// 如果有头像,转换为base64
if (userInfo.value.avatar.isNotEmpty) {
File imageFile = File(userInfo.value.avatar);
List<int> imageBytes = await imageFile.readAsBytes();
String base64Image = base64Encode(imageBytes);
data["image"] = base64Image;
}
// // 如果有头像,转换为base64
// if (localAvatar.value.isNotEmpty) {
// try{
// File imageFile = File(localAvatar.value);
// if(await imageFile.exists()){
// List<int> imageBytes = await imageFile.readAsBytes();
// String base64Image = base64Encode(imageBytes);
// data["image"] = base64Image;
// }else{
// Logger().e('头像文件不存在');
// }
// }catch(e){
// Logger().e('读取头像文件失败: $e');
// }
// 调用API
final response = await GetConnect().post(
'https://www.deapsound.com:443/api/home/user_setting',
// }
final dio = Dio(BaseOptions(
baseUrl: 'https://www.deapsound.com:443',
headers: {
'accept': 'application/json',
'Authorization': _storage.read<String>("logintoken"),
'Content-Type': 'application/json',
},
data,
));
// 调用API前打印token
Logger().d('当前用户token: ${userInfo.value.token}');
Logger().d('请求数据: ${{"user": data}}');
final response = await dio.post(
'/api/home/user_setting',
data: data,
);
// 调用API
//final response = await Api.updateUserSetting(data);
if (response.statusCode != 200) {
throw '更新失败:${response.body}';
if (response.statusCode == 200) {
if (response.data['code'] == 0) {
Logger().d('更新成功: ${response.data}');
Get.snackbar('成功', '同步到服务器成功');
} else {
throw '更新失败:${response.data['msg'] ?? '未知错误'}';
}
} else {
throw '服务器响应错误: ${response.statusCode}';
}
} catch (e) {
Logger().e('同步到后端失败: $e');
@ -156,6 +184,37 @@ class ProfileController extends GetxController {
final userJson = _storage.read(userKey);
if (userJson != null) {
userInfo.value = User.instance;
// 如果有远程头像地址,下载并保存到本地
if (userInfo.value.avatar.isNotEmpty) {
_downloadAndSaveAvatar(userInfo.value.avatar);
}
}
}
// 下载并保存远程头像
Future<void> _downloadAndSaveAvatar(String url) async {
try {
final response = await GetConnect().get(url);
if (response.status.isOk && response.body != null) {
// 保存到应用文档目录
final appDir = await getApplicationDocumentsDirectory();
final fileName = 'avatar_${DateTime.now().millisecondsSinceEpoch}.jpg';
final localPath = '${appDir.path}/$fileName';
// 写入文件
final file = File(localPath);
final List<int> bytes = response.body.runtimeType == String
? base64Decode(response.body.toString())
: response.body;
await file.writeAsBytes(bytes);
// 更新本地头像路径
localAvatar.value = localPath;
await _storage.write('local_avatar', localPath);
Logger().d('头像已下载到本地: $localPath');
}
} catch (e) {
Logger().e('下载头像失败: $e');
}
}
@ -163,6 +222,7 @@ class ProfileController extends GetxController {
await _storage.write(userKey, userInfo.value.toJson());
}
// 更新用户头像
Future<void> updateUserAvatar() async {
final XFile? image = await _picker.pickImage(
source: ImageSource.gallery,
@ -203,19 +263,24 @@ class ProfileController extends GetxController {
user.avatar = image.path;
}
});
await saveUserInfo();//保存到本地
// 更新本地头像路径
localAvatar.value = image.path;
await _storage.write('local_avatar', image.path);
// await saveUserInfo(); //保存到本地
await _syncWithBackend(); //同步到后端
// 通知 SettingsController 更新
if (Get.isRegistered<SettingsController>()) {
Get.find<SettingsController>().loadUserInfo();
}
// if (Get.isRegistered<SettingsController>()) {
// Get.find<SettingsController>().loadUserInfo();
// }
}
}
// 更新用户名
Future<void> updateUserName(String name) async {
if (name.trim().isEmpty) {
Logger().e('Invalid name: $name');
Logger().e('无效名称: $name');
return;
}
@ -223,11 +288,11 @@ class ProfileController extends GetxController {
userInfo.update((user) {
if (user != null) {
user.name = name.trim();
Logger().d('Name updated to: ${user.name}');
Logger().d('名称更新为: ${user.name}');
}
});
await saveUserInfo();//保存到本地
//await saveUserInfo(); //保存到本地
await _syncWithBackend(); //同步到后端
//同步更新 SettingsController
@ -236,8 +301,8 @@ class ProfileController extends GetxController {
settingsController.loadUserInfo();
}
} catch (e) {
Logger().e('Error updating name: $e');
Get.snackbar('错误', 'Failed to update user name');
Logger().e('更新名称错误: $e');
Get.snackbar('错误', '更新用户名失败');
}
}
}

14
lib/modules/profile/views/profile_view.dart

@ -169,10 +169,12 @@ class EditProfileView extends GetView<ProfileController> {
children: [
Obx(() => CircleAvatar(
radius: 50.r,
backgroundImage: controller
.userInfo.value.avatar.isNotEmpty
? NetworkImage(controller.userInfo.value.avatar)
: null,
backgroundImage: controller.localAvatar.value.isNotEmpty
? FileImage(File(controller.localAvatar.value))
: (controller.userInfo.value.avatar.isNotEmpty
? NetworkImage(
controller.userInfo.value.avatar)
: null) as ImageProvider<Object>?,
child: controller.userInfo.value.avatar.isEmpty
? Icon(Icons.person, size: 50.r)
: null,
@ -205,10 +207,10 @@ class EditProfileView extends GetView<ProfileController> {
controller: nameController,
decoration: InputDecoration(
labelText: 'nickname'.tr,
border: OutlineInputBorder(),
border: const OutlineInputBorder(),
suffixIcon: nameController.text.isNotEmpty
? IconButton(
icon: Icon(Icons.clear),
icon: const Icon(Icons.clear),
onPressed: () {
nameController.clear();
},

1
lib/modules/settings/controllers/settings_controller.dart

@ -205,6 +205,7 @@ class SettingsController extends GetxController {
void signOut() {
// 在此实现退出登录逻辑
_storage.write("logintoken", "");
Get.offAllNamed('/login');
}
}

101
lib/modules/splash/controllers/splash_controller.dart

@ -1,50 +1,49 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart';
import '../../../data/models/user_Info.dart';
import '../../../routes/app_routes.dart';
import '../../../core/themes/theme_service.dart';
import '../../../data/services/network/api.dart';
class SplashController extends GetxController {
final GetStorage _storage = GetStorage();
// 进度条动画控制
final _loadingProgress = 0.0.obs;
double get loadingProgress => _loadingProgress.value;
// 版本号
final String version = 'v1.0.0';
// 资源加载状态
final _isResourceLoaded = false.obs;
bool get isResourceLoaded => _isResourceLoaded.value;
// 版本号
final String version = 'v1.0.0';
// 主题服务
final ThemeService _themeService = ThemeService();
@override
void onInit() {
super.onInit();
// 确保页面先渲染,再执行其他操作
WidgetsBinding.instance.addPostFrameCallback((_) {
// 启动进度条动画
_startLoadingAnimation();
// 异步加载应用资源
_loadAppResources();
});
}
// 进度条动画
// 进度条动画(持续2秒)
void _startLoadingAnimation() {
// 模拟0.5秒的加载进度
Timer.periodic(const Duration(milliseconds: 5), (timer) {
const totalDuration = 2000; // 动画总时长2秒
const steps = totalDuration ~/ 50; // 40次更新(50ms间隔)
final stepValue = 1.0 / steps;
Timer.periodic(const Duration(milliseconds: 50), (timer) {
if (_loadingProgress.value < 1.0) {
_loadingProgress.value += 0.01;
_loadingProgress.value += stepValue;
} else {
timer.cancel();
// 当进度条动画完成且资源加载完毕时,跳转到登录页面
if (_isResourceLoaded.value) {
_navigateToLogin();
}
_checkNavigateCondition();
}
});
}
@ -52,37 +51,63 @@ class SplashController extends GetxController {
// 异步加载应用资源
Future<void> _loadAppResources() async {
try {
// 在这里可以添加应用初始化需要的资源加载
// 例如:预加载图片、初始化数据库、加载配置等
// 模拟耗时操作
await Future.delayed(const Duration(milliseconds: 100));
// 模拟资源加载(如API配置、本地缓存等)
await Future.delayed(const Duration(milliseconds: 1500));
// TODO: 在此处添加实际的资源加载代码
// 实际资源加载代码
// await _loadImages();
// await _initDatabase();
// 标记资源加载完成
_isResourceLoaded.value = true;
_checkNavigateCondition();
} catch (e) {
Get.snackbar('错误', '资源加载失败,请检查网络连接');
_isResourceLoaded.value = true; // 允许用户继续使用
print('资源加载错误: $e');
}
}
// 如果进度条已经走完,则跳转到登录页面
if (_loadingProgress.value >= 1.0) {
// 检查跳转条件
void _checkNavigateCondition() {
if (_loadingProgress.value >= 1.0 && _isResourceLoaded.value) {
_navigateToLogin();
}
} catch (e) {
// 处理资源加载错误
// 即使加载失败,也应该允许用户继续使用应用
_isResourceLoaded.value = true;
}
// 记录错误日志
print('资源加载错误: $e');
// 获取登录Token并跳转
Future<void> _navigateToLogin() async {
final isLoggedIn = await getLoginToken();
if (isLoggedIn) {
// Get.offAllNamed(Routes.login); // 跳转到登录页
// Get.offAllNamed(Routes.home); // 跳转到主页
Get.offAllNamed(Routes.pairing);
} else {
Get.offAllNamed(Routes.login); // 跳转到登录页
}
}
// 跳转到登录页面
void _navigateToLogin() {
// 添加一个小延迟,确保UI完全渲染
Future.delayed(const Duration(milliseconds: 200), () {
Get.offAllNamed(Routes.login);
});
Future<bool> getLoginToken() async {
try {
print('getLoginToken:进来了');
//if (User.getname() != "") {
final response = await Api.tokenlogin();
print('getLoginToken: $response');
if (response == null) return false;
if (response['user'] != '') {
print('getLoginToken:进来了');
// 根据API设计调整
final user = User.fromJson(response);
// 存储用户信息(示例)
await _storage.write('user_info', user.toJson());
return true;
}
//return true;
//}
return false;
} catch (e) {
print('Token获取失败: $e');
return false;
}
}
// fdp挑转登陆
}

9
lib/modules/timbre/bindings/timbre_binding.dart

@ -0,0 +1,9 @@
import 'package:get/get.dart';
import '../controllers/timbre_controller.dart';
class TimbreBinding extends Bindings {
@override
void dependencies() {
Get.put<TimbreController>(TimbreController());
}
}

14
lib/modules/timbre/controllers/timbre_controller.dart

@ -0,0 +1,14 @@
import 'package:get/get.dart';
class TimbreController extends GetxController {
RxList dataList = [
{'type': '1', 'name': '默认音色', 'description': '微软云端音色服务'},
{'type': '2', 'name': '声音复刻', 'description': '复刻本人音色服务'}
].obs;
RxString type = '1'.obs;
@override
void onInit() {
super.onInit();
}
}

168
lib/modules/timbre/views/timbre_view.dart

@ -0,0 +1,168 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import '../../../core/theme/app_colors.dart';
import '../controllers/timbre_controller.dart';
class TimbreView extends GetView<TimbreController> {
const TimbreView({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final isDarkMode = Theme.of(context).brightness == Brightness.dark;
return Scaffold(
backgroundColor: isDarkMode ? Colors.black : Colors.grey[50],
appBar: _buildAppBar(isDarkMode),
body: _buildBody(isDarkMode),
);
}
// 构建应用栏
PreferredSizeWidget _buildAppBar(bool isDarkMode) {
return AppBar(
backgroundColor: isDarkMode ? Colors.black : Colors.white,
elevation: 0.5,
leading: IconButton(
icon: Icon(
Icons.arrow_back_ios,
color: isDarkMode ? Colors.white : Colors.black,
size: 20.sp,
),
onPressed: () => Get.back(),
),
title: Text(
'选择音色',
style: TextStyle(
fontSize: 17.sp,
fontWeight: FontWeight.w600,
color: isDarkMode ? Colors.white : Colors.black,
),
),
centerTitle: true,
);
}
// 构建主体内容
Widget _buildBody(bool isDarkMode) {
return Column(
children: [
Expanded(
child: Obx(
() => ListView.builder(
itemCount: controller.dataList.length,
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 5.w),
itemBuilder: (BuildContext context, int index) {
Map data = controller.dataList[index];
return _item(isDarkMode, data);
},
),
),
),
GestureDetector(
onTap: () {},
child: Container(
width: 130.w,
height: 48.w,
margin: EdgeInsets.symmetric(vertical: 20.w),
decoration: BoxDecoration(
color: isDarkMode ? AppColors.primary : Colors.blue,
borderRadius: BorderRadius.circular(12.w),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.keyboard_voice, color: Colors.white),
5.horizontalSpace,
Text(
'声音复刻',
style: TextStyle(color: Colors.white, fontSize: 16.sp),
),
],
),
),
),
],
);
}
Widget _item(bool isDarkMode, Map data) {
return InkWell(
onTap: () {
controller.type.value = data['type'];
},
child: Obx(
() => Container(
margin: EdgeInsets.symmetric(vertical: 6.h),
padding: EdgeInsets.all(12.w),
decoration: BoxDecoration(
color: controller.type.value == data['type']
? (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: controller.type.value == data['type']
? (isDarkMode ? Colors.teal[300]! : Colors.teal[600]!)
: Colors.transparent,
width: 1,
),
),
child: Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
data['name'],
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w500,
color: isDarkMode ? Colors.white : Colors.black87,
),
overflow: TextOverflow.ellipsis,
),
SizedBox(height: 4.h),
Text(
data['description'],
style: TextStyle(
fontSize: 12.sp,
color: isDarkMode ? Colors.white70 : Colors.black54,
),
overflow: TextOverflow.ellipsis,
maxLines: 2,
),
],
),
),
Container(
width: 24.w,
height: 24.w,
margin: EdgeInsets.only(left: 12.w),
decoration: BoxDecoration(
color: controller.type.value == data['type']
? (isDarkMode ? Colors.teal[300] : Colors.teal[600])
: (isDarkMode
? Colors.white.withOpacity(0.1)
: Colors.grey[300]),
shape: BoxShape.circle,
),
child: controller.type.value == data['type']
? Icon(
Icons.check,
size: 16.w,
color: Colors.white,
)
: null,
),
],
),
),
),
);
}
}

7
lib/routes/app_pages.dart

@ -12,6 +12,8 @@ import '../modules/profile/bindings/profile_binding.dart';
import '../modules/chat/bindings/chat_binding.dart';
import '../modules/chat/views/chat_view.dart';
import '../modules/timbre/bindings/timbre_binding.dart';
import '../modules/timbre/views/timbre_view.dart';
import '../modules/translation/views/translation_view.dart';
import '../modules/translation/bindings/translation_binding.dart';
import '../modules/translation/views/translation_history_view.dart';
@ -93,6 +95,11 @@ abstract class AppPages {
page: () => const MeetingRecordView(),
binding: MeetingRecordBinding(),
),
GetPage(
name: Routes.timbre,
page: () => const TimbreView(),
binding: TimbreBinding(),
),
GetPage(
name: Routes.settings,
page: () => const SettingsView(),

1
lib/routes/app_routes.dart

@ -9,6 +9,7 @@ abstract class Routes {
static const profile = '/profile';
static const meeting = '/meeting';
static const meetingRecord = '/meetingRecord';
static const timbre = '/timbre';
static const settings = '/settings';
static const speechDemo = '/speech_demo';
static const TTS_TEST = '/tts_test';

Loading…
Cancel
Save