diff --git a/lib/data/models/user_Info.dart b/lib/data/models/user_Info.dart new file mode 100644 index 000000000..32662c615 --- /dev/null +++ b/lib/data/models/user_Info.dart @@ -0,0 +1,104 @@ +import 'dart:convert'; +import 'dart:ffi'; + +class User { + // 单例实例 + static User? _instance; + + // 单例访问器(增加空安全保护) + static User get instance { + if (_instance == null) { + throw Exception("User must be initialized first"); + } + return _instance!; + } + + // 最终属性(不可变) + String name; + String uid; + String avatar; + String deviceid; + String language; + String token; + String mail; + String phone; + String phonemac; + String password; + // final Int64 lastbettime; + // final Int64 createtime; + // final String wxopenid; + // final String googleopenid; + // final String facebookopenid; + + // 私有构造函数 + User._({ + required this.name, + required this.uid, + required this.avatar, + required this.deviceid, + required this.language, + required this.token, + required this.mail, + required this.phone, + required this.phonemac, + required this.password, + // required this.lastbettime, + // required this.createtime, + // required this.wxopenid, + // required this.googleopenid, + // required this.facebookopenid, + }); + + factory User.fromJson(Map json) { + // 修复1:安全获取嵌套的 user 字段 + final userData = json['data']['user'] as Map?; // 允许空值 + final token = json['data']['token'] as String? ?? ''; + // 更新单例实例 + return _instance = User._( + token: token as String? ?? '', + name: (userData?['name'] as String?) ?? '默认名称', // 提供默认值 + uid: (userData?['uid'] as String?) ?? '', + avatar: (userData?['avatar'] as String?) ?? '', + deviceid: (userData?['deviceid'] as String?) ?? '', + language: (userData?['language'] as String?) ?? 'en', + + mail: (userData?['mail'] as String?) ?? '', + phone: (userData?['phone'] as String?) ?? '', + phonemac: (userData?['phonemac'] as String?) ?? '', + password: (userData?['password'] as String?) ?? '', +// lastbettime: _parseTimestamp(userData['lastbettime']), +// createtime: _parseTimestamp(userData['createtime']), +// wxopenid: (userData?['wxopenid'] as String?) ?? '', +// googleopenid: (userData?['googleopenid'] as String?) ?? '', +// facebookopenid: (userData?['facebookopenid'] as String?) ?? '', + ); + } + + // 完整的JSON序列化 + Map toJson() => { + 'user': { + 'name': name, + 'uid': uid, + 'avatar': avatar, + 'deviceid': deviceid, + 'language': language, + 'token': token, + 'mail': mail, + 'phone': phone, + 'phonemac': phonemac, + 'password': password, + // 'lastbettime': lastbettime, + // 'createtime': createtime, + // 'wxopenid': wxopenid, + // 'googleopenid': googleopenid, + // '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; +} diff --git a/lib/data/services/login_service.dart b/lib/data/services/login_service.dart new file mode 100644 index 000000000..705a37353 --- /dev/null +++ b/lib/data/services/login_service.dart @@ -0,0 +1,25 @@ +import '../../modules/login/controllers/weixinLogin.dart'; +import 'package:firebase_core/firebase_core.dart'; +import '/firebase_options.dart'; + +class LoginService { + LoginService() { + //微信 + FlWX().register( + appSecret: '4cfde33bc22b6cc6b1f8b225dd97d255', + appId: 'wx5bbb7be9324f3e2d', + params: FlWXCallbackParams(onHttp: (url) async { + return ''; + })); + + if (Firebase.apps.isEmpty) { + Firebase.initializeApp( + name: 'DeepSound', + options: DefaultFirebaseOptions.currentPlatform, + //demoProjectId:'1:907754492821:android:2deffc5aa1037ae2145c9c' + ).then((value) { + print('Firebase Initialized:${value.options}'); + }); + } + } +} diff --git a/lib/data/services/network/api.dart b/lib/data/services/network/api.dart new file mode 100644 index 000000000..84ff33446 --- /dev/null +++ b/lib/data/services/network/api.dart @@ -0,0 +1,31 @@ +import 'dio_manager.dart'; +import 'nw_method.dart'; + +class Api { + // 登录 + static login([params]) { + return DioManager().request( + NWMethod.post, + '/api/home/user_sgin', + params: params, + ); + } + + // 获取验证码 + static getCode([params]) { + return DioManager().request( + NWMethod.post, + '/api/home/user_verification', + params: params, + ); + } + + // 获取阿里token + static getAliToken([params]) { + return DioManager().request( + NWMethod.post, + '/api/home/auth_alitoken', + params: params, + ); + } +} diff --git a/lib/data/services/network/auth_interceptor.dart b/lib/data/services/network/auth_interceptor.dart new file mode 100644 index 000000000..655751e4c --- /dev/null +++ b/lib/data/services/network/auth_interceptor.dart @@ -0,0 +1,65 @@ +// import 'package:dio/dio.dart'; +// import 'package:flutter_easyloading/flutter_easyloading.dart'; +// import 'package:get/get.dart' hide Response; + +// import '../scenes/start/start_logic.dart'; +// import 'entity/base_entity.dart'; + +// class AuthInterceptor extends Interceptor { +// final state = Get.find().state; + +// @override +// void onRequest(RequestOptions options, RequestInterceptorHandler handler) { +// options.headers['Authorization'] = state.loginModel.token; +// handler.next(options); +// } + +// @override +// void onResponse(Response response, ResponseInterceptorHandler handler) async { +// BaseEntity entity = BaseEntity.fromJson(response.data); +// if (entity.code == 0) { +// response.data = entity.data ?? {}; +// handler.resolve(response); +// } else { +// response.data = null; +// handler.next(response); +// return EasyLoading.showError(entity.message ?? 'unknownError'.tr); +// } +// } + +// @override +// void onError(DioException err, ErrorInterceptorHandler handler) { +// switch (err.type) { +// case DioExceptionType.cancel: +// { +// EasyLoading.showError('requestCancellation'.tr); +// } +// case DioExceptionType.connectionTimeout: +// { +// EasyLoading.showError('connectionTimeout'.tr); +// } +// case DioExceptionType.sendTimeout: +// { +// EasyLoading.showError('requestTimeout'.tr); +// } +// case DioExceptionType.receiveTimeout: +// { +// EasyLoading.showError('responseTimeout'.tr); +// } +// case DioExceptionType.badResponse: +// { +// try { +// String? errMsg = err.response?.statusMessage; +// EasyLoading.showError(errMsg ?? 'unknownError'.tr); +// } on Exception catch (_) { +// EasyLoading.showError('unknownError'.tr); +// } +// } +// default: +// { +// EasyLoading.showError(err.message ?? 'unknownError'.tr); +// } +// } +// handler.next(err); +// } +// } diff --git a/lib/data/services/network/dio_manager.dart b/lib/data/services/network/dio_manager.dart new file mode 100644 index 000000000..a41e81228 --- /dev/null +++ b/lib/data/services/network/dio_manager.dart @@ -0,0 +1,45 @@ +import 'package:dio/dio.dart'; + +import 'auth_interceptor.dart'; +import 'nw_api.dart'; +import 'nw_method.dart'; + +class DioManager { + static final DioManager _shared = DioManager._internal(); + factory DioManager() => _shared; + + late Dio dio; + + DioManager._internal() { + BaseOptions options = BaseOptions( + baseUrl: NWApi.baseApi, + contentType: Headers.jsonContentType, + responseType: ResponseType.json, + connectTimeout: const Duration(seconds: 30), + receiveTimeout: const Duration(seconds: 30), + ); + + dio = Dio(options); + + // dio.interceptors.add(AuthInterceptor()); + } + + // 请求,返回参数为 T + // method:请求方法,NWMethod.POST等 + // path:请求地址 + // params:请求参数 + Future request( + NWMethod method, + String path, { + Map? params, + Map? queryParameters, + }) async { + Response response = await dio.request( + path, + data: params, + queryParameters: queryParameters, + options: Options(method: nwMethodValues[method]), + ); + return response.data; + } +} diff --git a/lib/data/services/network/entity/base_entity.dart b/lib/data/services/network/entity/base_entity.dart new file mode 100644 index 000000000..fde0289ef --- /dev/null +++ b/lib/data/services/network/entity/base_entity.dart @@ -0,0 +1,17 @@ +import 'entity_factory.dart'; + +class BaseEntity { + int? code; + String? message; + T? data; + + BaseEntity({this.code, this.message, this.data}); + + factory BaseEntity.fromJson(json) { + return BaseEntity( + code: json["code"], + message: json["msg"], + data: EntityFactory.generateOBJ(json["data"]), + ); + } +} diff --git a/lib/data/services/network/entity/entity_factory.dart b/lib/data/services/network/entity/entity_factory.dart new file mode 100644 index 000000000..e11cb6d3c --- /dev/null +++ b/lib/data/services/network/entity/entity_factory.dart @@ -0,0 +1,9 @@ +class EntityFactory { + static T? generateOBJ(json) { + if (json == null) { + return null; + } else { + return json as T; + } + } +} diff --git a/lib/data/services/network/nw_api.dart b/lib/data/services/network/nw_api.dart new file mode 100644 index 000000000..875a166b6 --- /dev/null +++ b/lib/data/services/network/nw_api.dart @@ -0,0 +1,4 @@ +class NWApi { + // static const baseApi = "https://www.deapsound.com"; + static const baseApi = "https://www.deapsound.com"; +} diff --git a/lib/data/services/network/nw_method.dart b/lib/data/services/network/nw_method.dart new file mode 100644 index 000000000..f79ca96e3 --- /dev/null +++ b/lib/data/services/network/nw_method.dart @@ -0,0 +1,8 @@ +enum NWMethod { get, post, delete, put } + +const nwMethodValues = { + NWMethod.get: "get", + NWMethod.post: "post", + NWMethod.delete: "delete", + NWMethod.put: "put" +}; diff --git a/lib/main.dart b/lib/main.dart index 8c9dce259..dc5373757 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -12,9 +12,7 @@ import 'package:window_size/window_size.dart'; import 'core/translations/app_translations.dart'; import 'core/themes/app_theme.dart'; import 'core/themes/theme_service.dart'; -import 'modules/login/controllers/weixinLogin.dart'; -import 'package:firebase_core/firebase_core.dart'; -import 'firebase_options.dart'; +import '../../data/services/login_service.dart'; void main() async { // 确保Flutter绑定初始化 @@ -25,26 +23,7 @@ void main() async { // 初始化环境变量 await dotenv.load(); - - FlWX().register( - appSecret: '4cfde33bc22b6cc6b1f8b225dd97d255', - appId: 'wx5bbb7be9324f3e2d', - params: FlWXCallbackParams(onHttp: (url) async { - return ''; - })); - var err = FlWX().isInstalled; - String msg = err.toString() == 'false' ? '请先下载微信app' : '请授权登录'; - print(msg); - if (Firebase.apps.isEmpty) { - await Firebase.initializeApp( - name: 'DeepSound', - options: DefaultFirebaseOptions.currentPlatform, - //demoProjectId:'1:907754492821:android:2deffc5aa1037ae2145c9c' - ).then((value) { - // logger.i('Firebase Initialized:${value.options}'); - print('Firebase Initialized:${value.options}'); - }); - } + LoginService(); // 设置窗口大小(仅在 macOS 下生效) if (Platform.isMacOS) { diff --git a/lib/modules/login/controllers/facebookLogin.dart b/lib/modules/login/controllers/facebookLogin.dart index d77ae2177..d73cc7f87 100644 --- a/lib/modules/login/controllers/facebookLogin.dart +++ b/lib/modules/login/controllers/facebookLogin.dart @@ -4,6 +4,12 @@ import 'package:get/get.dart'; class Facebook { String token = ''; + + String name = ''; + String email = ''; + String pictureurl = ''; + String id = ''; + // FaceBook登录 // 修改后的 signInWithFacebook Future signInWithFacebook() async { @@ -19,7 +25,13 @@ class Facebook { final credential = FacebookAuthProvider.credential(token); print("Facebook Token: $credential"); final userData = await FacebookAuth.instance.getUserData(); + name = userData["name"].toString(); + + email = userData["email"].toString(); + pictureurl = userData["picture"]["data"]["url"].toString(); + id = userData["id"].toString(); print("user login facebook ${userData..values}"); + print("user login facebook ${email}${pictureurl}${name}${id}"); return true; } else { diff --git a/lib/modules/login/controllers/googleLogin.dart b/lib/modules/login/controllers/googleLogin.dart index 3e118e6f8..36a080cc6 100644 --- a/lib/modules/login/controllers/googleLogin.dart +++ b/lib/modules/login/controllers/googleLogin.dart @@ -13,14 +13,19 @@ class Google { Future signInWithGoogle() async { try { // 确保用户已经登录,并获取当前用户的身份验证令牌 + final currentUser = FirebaseAuth.instance.currentUser; print('Future响应数据: $currentUser'); if (currentUser != null) { // 这里处理您需要使用这个JWT令牌的逻辑,例如将它存储到本地存储中作为凭据。 - - token != await currentUser.getIdToken(); + email = currentUser.email; + displayName = currentUser.displayName; + id = currentUser.uid; + token = await currentUser.getIdToken(); print('Future响应数据tokenResult: $token'); + // IdTokenResult token1 = await currentUser.getIdTokenResult(); + // print('Future响应数据token1: $token1'); return true; } else { @@ -47,6 +52,8 @@ class Google { id = googleUser?.id; displayName = googleUser?.displayName; print('loginGoogle: $googleUser'); + print("user login loginGoogle ${email}${id}${displayName}"); + // Obtain the auth details from the request final GoogleSignInAuthentication? googleAuth = await googleUser?.authentication; diff --git a/lib/modules/login/controllers/login_controller.dart b/lib/modules/login/controllers/login_controller.dart index f591b32cc..142c71ca7 100644 --- a/lib/modules/login/controllers/login_controller.dart +++ b/lib/modules/login/controllers/login_controller.dart @@ -1,15 +1,25 @@ import 'package:get/get.dart'; import 'package:flutter/material.dart'; import '../../../routes/app_routes.dart'; +import 'dart:convert'; +import 'dart:ffi'; +import '../../settings/controllers/settings_controller.dart'; +import 'facebookLogin.dart'; +import 'googleLogin.dart'; +import 'weixinLogin.dart'; +import '../entity/api.dart'; +import '../../../../data/models/user_Info.dart'; +//import 'package:shared_preferences/shared_preferences.dart'; +import 'package:get_storage/get_storage.dart'; enum LoginViewState { - welcome, // 欢迎页面 + welcome, // 欢迎页面 verification // 验证码页面 } class LoginController extends GetxController { static LoginController get to => Get.find(); - + final GetStorage _storage = GetStorage(); // 状态管理 final _viewState = LoginViewState.welcome.obs; final _isLoading = false.obs; @@ -17,7 +27,7 @@ class LoginController extends GetxController { final _contactValue = ''.obs; final _displayContact = ''.obs; final _verificationCode = ['', '', '', ''].obs; - + int loginType = -1; // Getters LoginViewState get viewState => _viewState.value; bool get isLoading => _isLoading.value; @@ -25,29 +35,29 @@ class LoginController extends GetxController { String get contactValue => _contactValue.value; String get displayContact => _displayContact.value; List get verificationCode => _verificationCode; - + // 文本控制器 final contactController = TextEditingController(); final verificationCodeController = TextEditingController(); - + @override void onInit() { super.onInit(); _viewState.value = LoginViewState.welcome; } - + @override void onClose() { contactController.dispose(); verificationCodeController.dispose(); super.onClose(); } - + // 获取验证码 void getVerificationCode() { if (contactController.text.isEmpty) { Get.snackbar( - '提示', + '提示', '请输入手机号码或邮箱', snackPosition: SnackPosition.TOP, backgroundColor: Colors.red.withOpacity(0.1), @@ -55,19 +65,19 @@ class LoginController extends GetxController { ); return; } - + _contactValue.value = contactController.text; _formatDisplayContact(); _startCountdown(); _sendVerificationCode(); } - + // 使用验证码直接登录 void loginWithVerificationCode() { //测试期间暂时关闭验证码登录 // if (contactController.text.isEmpty) { // Get.snackbar( - // '提示', + // '提示', // '请输入手机号码或邮箱', // snackPosition: SnackPosition.TOP, // backgroundColor: Colors.red.withOpacity(0.1), @@ -75,10 +85,10 @@ class LoginController extends GetxController { // ); // return; // } - + // if (verificationCodeController.text.isEmpty) { // Get.snackbar( - // '提示', + // '提示', // '请输入验证码', // snackPosition: SnackPosition.TOP, // backgroundColor: Colors.red.withOpacity(0.1), @@ -86,10 +96,10 @@ class LoginController extends GetxController { // ); // return; // } - + // if (verificationCodeController.text.length < 6) { // Get.snackbar( - // '提示', + // '提示', // '请输入完整的验证码', // snackPosition: SnackPosition.TOP, // backgroundColor: Colors.red.withOpacity(0.1), @@ -97,9 +107,9 @@ class LoginController extends GetxController { // ); // return; // } - + _isLoading.value = true; - + // 模拟验证过程 Future.delayed(const Duration(seconds: 2), () { _isLoading.value = false; @@ -107,12 +117,12 @@ class LoginController extends GetxController { Get.offAllNamed(Routes.pairing); }); } - + // 切换到验证码页面 void showVerificationView() { if (contactController.text.isEmpty) { Get.snackbar( - '提示', + '提示', '请输入手机号码或邮箱', snackPosition: SnackPosition.TOP, backgroundColor: Colors.red.withOpacity(0.1), @@ -120,33 +130,38 @@ class LoginController extends GetxController { ); return; } - + _contactValue.value = contactController.text; _formatDisplayContact(); _viewState.value = LoginViewState.verification; _startCountdown(); _sendVerificationCode(); } - + // 返回欢迎页面 void showWelcomeView() { _viewState.value = LoginViewState.welcome; _resetVerificationCode(); } - + // 格式化显示的联系方式(隐藏部分字符) void _formatDisplayContact() { String contact = _contactValue.value; - if (contact.contains('@')) { // 邮箱 + if (contact.contains('@')) { + // 邮箱 final parts = contact.split('@'); if (parts[0].length > 3) { _displayContact.value = parts[0].substring(0, 3) + '****@' + parts[1]; } else { _displayContact.value = contact; } - } else if (RegExp(r'^\d+$').hasMatch(contact)) { // 手机号 + } else if (RegExp(r'^\d+$').hasMatch(contact)) { + // 手机号 if (contact.length > 7) { - _displayContact.value = '+86 ' + contact.substring(0, 3) + '****' + contact.substring(contact.length - 4); + _displayContact.value = '+86 ' + + contact.substring(0, 3) + + '****' + + contact.substring(contact.length - 4); } else { _displayContact.value = contact; } @@ -154,7 +169,7 @@ class LoginController extends GetxController { _displayContact.value = contact; } } - + // 开始倒计时 void _startCountdown() { _countdownSeconds.value = 60; @@ -164,20 +179,20 @@ class LoginController extends GetxController { return _countdownSeconds.value > 0; }); } - + // 发送验证码 void _sendVerificationCode() { // 这里应该是实际发送验证码的逻辑 // 为了演示,我们只是模拟发送 } - + // 重新发送验证码 void resendVerificationCode() { if (_countdownSeconds.value > 0) return; _startCountdown(); _sendVerificationCode(); } - + // 更新验证码输入 void updateVerificationCode(int index, String value) { if (index >= 0 && index < 4) { @@ -186,23 +201,23 @@ class LoginController extends GetxController { _verificationCode.value = newList; } } - + // 重置验证码 void _resetVerificationCode() { _verificationCode.value = ['', '', '', '']; } - + // 检查验证码是否完整 bool isVerificationCodeComplete() { return !_verificationCode.contains(''); } - + // 登录 Future login() async { // 暂时关闭验证码验证 // if (!isVerificationCodeComplete()) { // Get.snackbar( - // '提示', + // '提示', // '请输入完整的验证码', // snackPosition: SnackPosition.TOP, // backgroundColor: Colors.red.withOpacity(0.1), @@ -210,18 +225,102 @@ class LoginController extends GetxController { // ); // return; // } - + _isLoading.value = true; - + try { // 模拟验证过程 await Future.delayed(const Duration(seconds: 2)); - - // 验证成功,跳转到配对页面 - Get.offAllNamed(Routes.pairing); + switch (loginType) { + case 1: + { + print("数据获取Facebook"); + final facebook = Facebook(); + bool authResult = await facebook.signInWithFacebook(); + print("数据获取Facebook().token${authResult}"); + if (authResult) { + print("数据获取_Facebook.token${facebook.token}"); + // SettingsController().userName = Facebook().name; + // SettingsController().avatar = Facebook().pictureurl; + // SettingsController().userId = Facebook().id; + //SettingsController().avatar = Google().email; + + // 验证成功,跳转到配对页面 + requestLogin({ + "email": facebook.email, + "name": facebook.name, + "avatar": facebook.pictureurl, + "stype": 4, + "ttoken": facebook.token, + }); + } + break; + } + case 2: + { + print("数据获取_Google()"); + final google = Google(); + bool authResult = await google.signInWithGoogle(); + print("数据获取_Google().token${authResult}"); + if (authResult) { + // 验证成功,跳转到配对页面 + // SettingsController().userName = Google().displayName!; + // //SettingsController().avatar = Google().email; + // SettingsController().userId = Google().id!; + requestLogin({ + "name": google.displayName, + "email": google.email, + "stype": 3, + "ttoken": google.token, + }); + } + + break; + } + case 3: + { + print("数据获取_FlWX()"); + + bool authResult = await FlWX().authBy( + NormalAuth( + scope: "snsapi_userinfo", + state: "wechat_sdk_demo_test", + ), + getUserInfo: true, // 按需获取用户信息 + ); + + if (authResult) { + // 确保 FlWX() 返回的是同一个实例,并且 token 已经被设置 + // 这里假设 FlWX().token 在 authBy 成功后被设置 + print("数据获取_FlWX().token${FlWX().token}"); + // SettingsController().userName = FlWX().nickname; + // SettingsController().avatar = FlWX().headimgurl; + // SettingsController().userId = FlWX().openid; + requestLogin({ + "name": FlWX().nickname, + "avatar": FlWX().headimgurl, + "stype": 2, + "ttoken": FlWX().code, + }); + // 验证成功,跳转到配对页面 + } + break; + } + default: + { + Get.snackbar( + '错误', + '登录失败,请重试', + snackPosition: SnackPosition.TOP, + backgroundColor: Colors.red.withOpacity(0.1), + colorText: Colors.red, + ); + break; + } + } } catch (e) { Get.snackbar( - '错误', + '错误', '登录失败,请重试', snackPosition: SnackPosition.TOP, backgroundColor: Colors.red.withOpacity(0.1), @@ -231,16 +330,87 @@ class LoginController extends GetxController { _isLoading.value = false; } } - + + // 使用登陆请求服务器 + Future requestLogin(Map params) async { + // switch (loginType) { + // case 0: //邮箱 + // { + // String verificationCode = verificationCodeController.text; + // String contactControll22er = contactController.text; + // } + // case 1: //手机号 + // { + // String verificationCode = verificationCodeController.text; + // String contactContr22oller = contactController.text; + // } + // case 2: //微信 + // {} + // case 3: //谷歌 + // {} + // case 4: //facebook + // {} + // } + + var response = await Api.login({ + "mail": params?["mail"].toString() ?? "", + "phone": params?["phone"].toString() ?? "", + "stype": params["stype"], + "vcode": params?["vcode"].toString() ?? "", + "ttoken": params?["ttoken"].toString() ?? "", + "phonemodel": params?["phonemodel"].toString() ?? "", + "phonemac": params?["phonemac"].toString() ?? "", + "name": params?["name"].toString() ?? "", + "avatar": params?["avatar"].toString() ?? "", + }); + if (response != null) { + print("请求后台数据login回复数据=${response}"); + + // final instance = await GetStorage(); + await _storage.write('loginInfo', jsonEncode(response)); + // 2. 将响应数据转换为User对象 + try { + print('响应数据: ${response.toString()}'); // 调试输出原始数据 + + if (response['code'].toString() == '0' && + response['msg'].toString() == "Success") { + // 新增:检查 user 字段是否存在且类型正确 + if (response.containsKey('data') && response['data'] is Map) { + final user = User.fromJson(response); + print('用户初始化成功: ${user.name}'); + + // 存储到 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 Exception('接口返回错误: ${response['msg']}'); + } + } catch (e, stack) { + // 新增:打印堆栈跟踪 + print('用户数据处理失败: $e'); + print('堆栈跟踪: $stack'); + Get.snackbar('错误', '用户数据解析失败,请检查网络'); + } + + // Get.offAll(() => IndexWidget()); + } + } + // 使用密码登录 void usePasswordLogin() { // 这里可以实现密码登录的逻辑 Get.snackbar( - '提示', + '提示', '密码登录功能尚未实现', snackPosition: SnackPosition.TOP, backgroundColor: Colors.blue.withOpacity(0.1), colorText: Colors.blue, ); } -} \ No newline at end of file +} diff --git a/lib/modules/login/controllers/weixinLogin.dart b/lib/modules/login/controllers/weixinLogin.dart index 214c5a663..38819a60a 100644 --- a/lib/modules/login/controllers/weixinLogin.dart +++ b/lib/modules/login/controllers/weixinLogin.dart @@ -5,7 +5,6 @@ import 'dart:typed_data'; import 'package:fluwx/fluwx.dart'; export 'package:fluwx/fluwx.dart'; import 'package:http/http.dart' as http; -import 'package:get/get.dart'; typedef FlWeChatResponseCallback = void Function(WeChatResponse response); @@ -76,8 +75,14 @@ class FlWX { String _appName = ''; String token = ''; + String code = ''; String refresh_token = ''; String openid = ''; + + String nickname = ''; + String sex = ''; + String headimgurl = ''; + String city = ''; // 参数 说明 // access_token 接口调用凭证 // expires_in access_token 接口调用凭证超时时间,单位(秒) @@ -257,68 +262,97 @@ class FlWX { } } - /// 调用微信登录 +// graph TD +// A[开始] --> B{微信是否安装?} +// B -->|否| C[返回 false] +// B -->|是| D[注册监听器] +// D --> E[调起微信授权] +// E -->|失败| F[触发 completer(false)] +// E -->|成功| G[等待微信响应] +// G -->|用户取消/接口错误| H[触发 completer(false)] +// G -->|成功获取 code| I[获取 Token] +// I -->|Token 失败| H +// I -->|Token 成功| J[刷新 Token] +// J -->|刷新失败| H +// J -->|刷新成功| K{需用户信息?} +// K -->|是| L[获取用户信息] +// L -->|信息失败| H +// K -->|否| M[触发 completer(true)] +// L -->|信息成功| M Future authBy( AuthType which, { - /// 原始数据回调 FlWeChatAuthResponseCallback? onResponse, - - /// 获取token回调 FlWeChatAuthResponseTokenCallback? onToken, - - /// 获取userinfo回调 FlWeChatAuthResponseUserinfoCallback? onUserinfo, bool getUserInfo = false, }) async { - if (!await isInstalled) return false; - final cancelable = _onListener((response) async { + if (!await isInstalled) { + _params?.onToast?.call('微信未安装'); + return false; + } + + final completer = Completer(); // 创建 Completer 控制 Future 完成时机 + FluwxCancelable? subscription; // 用于取消监听 + + subscription = _onListener((response) async { if (response is WeChatAuthResponse) { onResponse?.call(response); - print( - "用户点击事件${response.isSuccessful},${response.code},${response.lang},${response.state},${response.country}", - ); if (response.isSuccessful && response.code != null) { - print("数据获取失败${onToken},${onUserinfo}"); - // if (onToken != null || onUserinfo != null) { - try { - var Istoken = await fetchData( - _getTokenUrl(response.code!), - ); // 触发GET请求 - //token有效期是30天,30天后需要重新授权,需要看看API文档 - print("数据获取fetchData${Istoken}"); - - if (Istoken != {}) { - token = Istoken["access_token"].toString(); - refresh_token = Istoken["refresh_token"].toString(); - openid = Istoken["openid"].toString(); - print("数据获取token=${token}"); - print("数据获取openid=${openid}"); - print("数据获取refresh_token=${refresh_token}"); - Istoken = await fetchData(_getRefreshTokenUrl(response.code!)); - print("数据获取_getUserTokenEffertive${Istoken}"); - Istoken = await fetchData( - _getUserTokenEffertive(openId: openid, token: token), - ); // 触发GET请求 - print("数据获取_getUserTokenEffertive${Istoken}"); - Istoken = await fetchData( - _getUserInfoUrl(openId: openid, token: token), - ); // 触发GET请求 - print("数据获取_getUserInfoUrl${Istoken}"); - } - } catch (e) { - _params!.onLog?.call('数据解析失败 $e'); - } + code = response.code!; + completer.complete(true); // 所有操作成功时完成 Future + + // try { + // code = response.code!; + // // 获取 token + // final tokenData = await fetchData(_getTokenUrl(response.code!)); + // if (tokenData.isEmpty) { + // _params!.onLog?.call('Token 数据获取失败'); + // completer.complete(false); // 失败时完成 Future + // return; + // } + + // // 更新 token + // token = tokenData["access_token"].toString(); + // openid = tokenData["openid"].toString(); + // // Istoken = await fetchData( + // // _getUserTokenEffertive(openId: openid, token: token), + // // ); // 触发GET请求 + // // print("数据获取_getUserTokenEffertive${Istoken}"); + // // 可选:刷新 token 或获取用户信息 + // if (getUserInfo) { + // final userInfo = await fetchData( + // _getUserInfoUrl(openId: openid, token: token)); + // List bytes = latin1.encode(userInfo["nickname"].toString()); + // print("ISO-8859-1 字节数组: $bytes"); + // nickname = String.fromCharCodes(bytes); + // // nickname = userInfo["nickname"].toString(); + // sex = userInfo["sex"].toString(); + // headimgurl = userInfo["headimgurl"].toString(); + // city = userInfo["city"].toString(); + // print("_getUserInfoUrl${nickname}"); + // // onUserinfo?.call(userInfo); + // } + + // completer.complete(true); // 所有操作成功时完成 Future + // } catch (e) { + // _params!.onLog?.call('授权流程异常: $e'); + // completer.complete(false); // } + } else { + completer.complete(false); // 用户取消授权或授权失败 } - } else if (response is WeChatAuthByQRCodeFinishedResponse) {} + } }); + + // 调起微信授权 final result = await fluwx.authBy(which: which); - print("authBy数据获取${result}"); if (!result) { - cancelable.cancel(); + subscription.cancel(); // 调起失败时取消监听 _params!.onToast?.call('微信授权失败'); + completer.complete(false); } - return result; + + return completer.future; // 返回由回调控制的 Future } Future autoDeduct( diff --git a/lib/modules/login/entity/api.dart b/lib/modules/login/entity/api.dart new file mode 100644 index 000000000..22c78ce9d --- /dev/null +++ b/lib/modules/login/entity/api.dart @@ -0,0 +1,32 @@ +import 'dio_manager.dart'; +import 'nw_method.dart'; + +class Api { + // 登录 + static login([params]) { + print("请求后台数据params=${params}"); + return DioManager().request( + NWMethod.post, + '/api/home/user_sgin', + params: params, + ); + } + + // 获取验证码 + static getCode([params]) { + return DioManager().request( + NWMethod.post, + '/api/home/user_verification', + params: params, + ); + } + + // 获取阿里token + static getAliToken([params]) { + return DioManager().request( + NWMethod.post, + '/api/home/auth_alitoken', + params: params, + ); + } +} diff --git a/lib/modules/login/entity/dio_manager.dart b/lib/modules/login/entity/dio_manager.dart new file mode 100644 index 000000000..0635524c0 --- /dev/null +++ b/lib/modules/login/entity/dio_manager.dart @@ -0,0 +1,45 @@ +import 'package:dio/dio.dart'; + +//import 'auth_interceptor.dart'; +import 'nw_api.dart'; +import 'nw_method.dart'; + +class DioManager { + static final DioManager _shared = DioManager._internal(); + factory DioManager() => _shared; + + late Dio dio; + + DioManager._internal() { + BaseOptions options = BaseOptions( + baseUrl: NWApi.baseApi, + contentType: Headers.jsonContentType, + responseType: ResponseType.json, + connectTimeout: const Duration(seconds: 30), + receiveTimeout: const Duration(seconds: 30), + ); + + dio = Dio(options); + + //dio.interceptors.add(AuthInterceptor()); + } + + // 请求,返回参数为 T + // method:请求方法,NWMethod.POST等 + // path:请求地址 + // params:请求参数 + Future request( + NWMethod method, + String path, { + Map? params, + Map? queryParameters, + }) async { + Response response = await dio.request( + path, + data: params, + queryParameters: queryParameters, + options: Options(method: nwMethodValues[method]), + ); + return response.data; + } +} diff --git a/lib/modules/login/entity/nw_api.dart b/lib/modules/login/entity/nw_api.dart new file mode 100644 index 000000000..875a166b6 --- /dev/null +++ b/lib/modules/login/entity/nw_api.dart @@ -0,0 +1,4 @@ +class NWApi { + // static const baseApi = "https://www.deapsound.com"; + static const baseApi = "https://www.deapsound.com"; +} diff --git a/lib/modules/login/entity/nw_method.dart b/lib/modules/login/entity/nw_method.dart new file mode 100644 index 000000000..f79ca96e3 --- /dev/null +++ b/lib/modules/login/entity/nw_method.dart @@ -0,0 +1,8 @@ +enum NWMethod { get, post, delete, put } + +const nwMethodValues = { + NWMethod.get: "get", + NWMethod.post: "post", + NWMethod.delete: "delete", + NWMethod.put: "put" +}; diff --git a/lib/modules/login/views/login_view.dart b/lib/modules/login/views/login_view.dart index 84ceff40b..85581536e 100644 --- a/lib/modules/login/views/login_view.dart +++ b/lib/modules/login/views/login_view.dart @@ -397,45 +397,21 @@ class LoginView extends GetView { switch (icon) { case FontAwesomeIcons.facebook: { - print("数据获取Facebook"); - bool authResult = await Facebook().signInWithFacebook(); - print("数据获取Facebook().token${authResult}"); - if (authResult) { - print("数据获取_Facebook.token${Facebook().token}"); - controller.login(); - } + controller.loginType = 1; break; } case FontAwesomeIcons.google: { - print("数据获取_Google()"); - bool authResult = await Google().signInWithGoogle(); - print("数据获取_Google().token${authResult}"); - if (authResult) { - controller.login(); - } - + controller.loginType = 2; break; } case FontAwesomeIcons.weixin: { - print("数据获取_FlWX()"); - bool authResult = await FlWX().authBy( - NormalAuth( - scope: "snsapi_userinfo", - state: "wechat_sdk_demo_test", - ), - ); - - if (authResult) { - // 确保 FlWX() 返回的是同一个实例,并且 token 已经被设置 - // 这里假设 FlWX().token 在 authBy 成功后被设置 - print("数据获取_FlWX().token${FlWX().token}"); - controller.login(); - } + controller.loginType = 3; break; } } + controller.login(); } // 用户协议和隐私政策 diff --git a/lib/modules/meeting/bindings/meeting_record_binding.dart b/lib/modules/meeting/bindings/meeting_record_binding.dart new file mode 100644 index 000000000..3fd92a57c --- /dev/null +++ b/lib/modules/meeting/bindings/meeting_record_binding.dart @@ -0,0 +1,10 @@ +import 'package:get/get.dart'; +import '../controllers/meeting_record_controller.dart'; + +class MeetingRecordBinding extends Bindings { + @override + void dependencies() { + // 注册控制器 + Get.lazyPut(() => MeetingRecordController()); + } +} diff --git a/lib/modules/meeting/controllers/meeting_controller.dart b/lib/modules/meeting/controllers/meeting_controller.dart index acd45276f..42d951ee0 100644 --- a/lib/modules/meeting/controllers/meeting_controller.dart +++ b/lib/modules/meeting/controllers/meeting_controller.dart @@ -1,19 +1,26 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; -class MeetingController extends GetxController - with GetSingleTickerProviderStateMixin { - late AnimationController lottieController; +class MeetingController extends GetxController { + final ScrollController listController = ScrollController(); + + // 是否还有更多数据 + bool isLoadingMore = true; + int page = 1; + + TextEditingController titleController = TextEditingController(); // 录音标题 + + RxList dataList = [].obs; @override void onInit() { super.onInit(); - lottieController = AnimationController(vsync: this); } @override void onClose() { - lottieController.dispose(); + listController.dispose(); + titleController.dispose(); super.onClose(); } } diff --git a/lib/modules/meeting/controllers/meeting_record_controller.dart b/lib/modules/meeting/controllers/meeting_record_controller.dart new file mode 100644 index 000000000..d00b823c1 --- /dev/null +++ b/lib/modules/meeting/controllers/meeting_record_controller.dart @@ -0,0 +1,98 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +import '../../../data/services/asr_service.dart'; + +class MeetingRecordController extends GetxController + with GetSingleTickerProviderStateMixin { + late AnimationController lottieController; + late AsrService _asrService; + + Timer? timer; + + RxBool isRecording = false.obs; //是否录音中 + RxInt seconds = 0.obs; + + RxString intermediateContent = ''.obs; + RxString finalContent = ''.obs; + + StreamSubscription? _recognitionSubscription; + + @override + void onInit() { + super.onInit(); + lottieController = AnimationController(vsync: this); + _asrService = Get.find(); + } + + @override + void onClose() { + lottieController.dispose(); + timer?.cancel(); + _asrService.stopContinuousRecognition(); + _recognitionSubscription?.cancel(); + super.onClose(); + } + + // 点击开始、暂停按钮 + void recorderHandle() { + if (isRecording.value) { + _pause(); + } else { + _start(); + } + } + + // 开始 + void _start() async { + await _startRecorder(); + lottieController.repeat(); + isRecording.value = true; + timer = Timer.periodic(const Duration(seconds: 1), (_) => seconds++); + } + + // 暂停 + void _pause() async { + await _stopRecorder(); + lottieController.stop(); + isRecording.value = false; + timer?.cancel(); + } + + //开始录音 + Future _startRecorder() async { + final recognitionStream = await _asrService.startContinuousRecognition(); + _recognitionSubscription = recognitionStream.listen( + _handleRecognitionEvent, + ); + } + + //结束录音 + Future _stopRecorder() async { + await _asrService.stopContinuousRecognition(); + _recognitionSubscription?.cancel(); + } + + // 处理语音识别事件 + void _handleRecognitionEvent(RecognitionEvent event) { + switch (event.type) { + case RecognitionEventType.intermediateResult: + intermediateContent.value = event.text; + break; + case RecognitionEventType.finalResult: + finalContent.value += event.text; + intermediateContent.value = ''; + break; + default: + // 不做任何处理 + break; + } + } + + // 完成 + void complete() async { + Get.back(); + } +} diff --git a/lib/modules/meeting/model/meeting_details_model.dart b/lib/modules/meeting/model/meeting_details_model.dart new file mode 100644 index 000000000..e314222a7 --- /dev/null +++ b/lib/modules/meeting/model/meeting_details_model.dart @@ -0,0 +1,26 @@ +import 'dart:typed_data'; + +class MeetingDetailsModel { + MeetingDetailsModel(); + + late int id; + late int noteid; + late Uint8List audio; + late String content; + + factory MeetingDetailsModel.fromJson(Map map) => + MeetingDetailsModel() + ..id = map["id"] + ..noteid = map["noteid"] + ..audio = map["audio"] + ..content = map["content"]; + + Map toJson() { + final data = {}; + data['id'] = id; + data['noteid'] = noteid; + data['audio'] = audio; + data['content'] = content; + return data; + } +} diff --git a/lib/modules/meeting/model/meeting_model.dart b/lib/modules/meeting/model/meeting_model.dart new file mode 100644 index 000000000..db7d5488b --- /dev/null +++ b/lib/modules/meeting/model/meeting_model.dart @@ -0,0 +1,29 @@ +class MeetingModel { + MeetingModel(); + + late int id; + late String title; + late String content; + late String language; + late int seconds; + late int creationtime; + + factory MeetingModel.fromJson(Map map) => MeetingModel() + ..id = map["id"] + ..title = map["title"] + ..content = map["content"] + ..language = map["language"] + ..seconds = map["seconds"] + ..creationtime = map["creationtime"]; + + Map toJson() { + final data = {}; + data['id'] = id; + data['title'] = title; + data['content'] = content; + data['language'] = language; + data['seconds'] = seconds; + data['creationtime'] = creationtime; + return data; + } +} diff --git a/lib/modules/meeting/views/meeting_record_view.dart b/lib/modules/meeting/views/meeting_record_view.dart new file mode 100644 index 000000000..d83db0cc9 --- /dev/null +++ b/lib/modules/meeting/views/meeting_record_view.dart @@ -0,0 +1,320 @@ +import 'package:common_utils/common_utils.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:get/get.dart'; +import 'package:lottie/lottie.dart'; + +import '../../../core/theme/app_colors.dart'; +import '../controllers/meeting_record_controller.dart'; + +class MeetingRecordView extends GetView { + const MeetingRecordView({super.key}); + + @override + Widget build(BuildContext context) { + final isDarkMode = Theme.of(context).brightness == Brightness.dark; + + return PopScope( + canPop: false, + onPopInvokedWithResult: (didPop, result) { + if (didPop) return; + _showAlertDialog(isDarkMode); + }, + child: 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: () { + _showAlertDialog(isDarkMode); + }, + ), + title: Padding( + padding: const EdgeInsets.only(right: 56), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Obx( + () => Icon( + Icons.keyboard_voice, + color: isDarkMode + ? controller.seconds.value > 0 + ? AppColors.primary + : Colors.white + : controller.seconds.value > 0 + ? Colors.blue + : Colors.black, + ), + ), + 5.horizontalSpace, + Obx( + () => Text( + DateUtil.formatDateMs( + controller.seconds.value * 1000, + format: 'HH:mm:ss', + isUtc: true, + ), + style: TextStyle( + fontSize: 17.sp, + fontWeight: FontWeight.w600, + color: isDarkMode + ? controller.seconds.value > 0 + ? AppColors.primary + : Colors.white + : controller.seconds.value > 0 + ? Colors.blue + : Colors.black, + ), + ), + ), + ], + ), + ), + centerTitle: true, + ); + } + + // 构建主体内容 + Widget _buildBody(bool isDarkMode) { + return Padding( + padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 10.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: Text( + '2025-03-03 录音', + style: TextStyle( + color: isDarkMode ? Colors.white : Colors.black87, + fontSize: 17.sp, + ), + ), + ), + 10.horizontalSpace, + Text( + '中文', + style: TextStyle( + color: isDarkMode ? Colors.white : Colors.black87, + fontSize: 15.sp, + ), + ), + ], + ), + Padding( + padding: EdgeInsets.symmetric(vertical: 5.w), + child: Text( + '2025-03-03 16:18', + style: TextStyle( + color: isDarkMode ? Colors.grey[400] : Colors.grey[500], + fontSize: 14.sp, + ), + ), + ), + _audioWave(isDarkMode), + _recordContent(isDarkMode), + _bottomButton(isDarkMode), + ], + ), + ); + } + + // 音波 + Widget _audioWave(bool isDarkMode) { + return Container( + width: double.infinity, + height: 70.w, + margin: EdgeInsets.symmetric(vertical: 5.w), + decoration: BoxDecoration( + color: isDarkMode ? Colors.white.withOpacity(0.05) : Colors.white, + borderRadius: BorderRadius.circular(12.w), + boxShadow: [ + BoxShadow( + color: isDarkMode + ? Colors.black.withOpacity(0.2) + : Colors.black.withOpacity(0.05), + blurRadius: 10, + offset: const Offset(0, 2), + ), + ], + ), + child: Row( + children: [ + Expanded( + child: Lottie.asset( + 'assets/lottie/audio_wave.json', + controller: controller.lottieController, + fit: BoxFit.fill, + onLoaded: (composition) { + controller.lottieController.duration = composition.duration; + }, + ), + ), + Expanded( + child: Lottie.asset( + 'assets/lottie/audio_wave.json', + controller: controller.lottieController, + fit: BoxFit.fill, + ), + ), + Expanded( + child: Lottie.asset( + 'assets/lottie/audio_wave.json', + controller: controller.lottieController, + fit: BoxFit.fill, + ), + ), + ], + ), + ); + } + + // 记录内容 + Widget _recordContent(bool isDarkMode) { + return Expanded( + child: Container( + width: double.infinity, + padding: EdgeInsets.all(10.w), + margin: EdgeInsets.symmetric(vertical: 5.w), + decoration: BoxDecoration( + color: isDarkMode ? Colors.white.withOpacity(0.05) : Colors.white, + borderRadius: BorderRadius.circular(12.w), + boxShadow: [ + BoxShadow( + color: isDarkMode + ? Colors.black.withOpacity(0.2) + : Colors.black.withOpacity(0.05), + blurRadius: 10, + offset: const Offset(0, 2), + ), + ], + ), + child: Align( + alignment: Alignment.topLeft, + child: SingleChildScrollView( + reverse: true, + child: Obx( + () => Text( + '${controller.finalContent.value}${controller.intermediateContent.value}', + ), + ), + ), + ), + ), + ); + } + + // 底部按钮 + Widget _bottomButton(bool isDarkMode) { + return Padding( + padding: EdgeInsets.only(top: 40.w, bottom: 30.w), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + GestureDetector( + onTap: controller.recorderHandle, + child: Container( + width: 130.w, + height: 60.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(60.w), + border: Border.all( + color: isDarkMode ? AppColors.primary : Colors.blue, + width: 2.w, + ), + ), + child: Obx( + () => Icon( + controller.isRecording.value ? Icons.pause : Icons.play_arrow, + color: isDarkMode ? AppColors.primary : Colors.blue, + size: 40.w, + ), + ), + ), + ), + GestureDetector( + onTap: () { + _showAlertDialog(isDarkMode); + }, + child: Container( + width: 130.w, + height: 60.w, + decoration: BoxDecoration( + color: isDarkMode ? AppColors.primary : Colors.blue, + borderRadius: BorderRadius.circular(60.w), + ), + child: Icon( + Icons.fiber_manual_record_outlined, + color: Colors.white, + size: 40.w, + ), + ), + ), + ], + ), + ); + } + + // 显示对话框 + void _showAlertDialog(bool isDarkMode) { + if (controller.seconds.value > 0) { + Get.dialog( + AlertDialog( + backgroundColor: isDarkMode ? Colors.grey[900] : Colors.white, + title: Text( + '注意', + style: TextStyle( + color: isDarkMode ? Colors.white : Colors.black87, + ), + ), + content: Text( + '你想结束录音吗?', + style: TextStyle( + color: isDarkMode ? Colors.white70 : Colors.black54, + ), + ), + actions: [ + TextButton( + onPressed: () => Get.back(), + child: Text( + 'cancel'.tr, // 取消 + style: TextStyle( + color: isDarkMode ? Colors.white70 : Colors.black54, + ), + ), + ), + TextButton( + onPressed: () { + Get.back(); + controller.complete(); + }, + child: Text( + 'confirm'.tr, // 确定 + style: TextStyle( + color: isDarkMode ? Colors.red[300] : Colors.red[600], + ), + ), + ), + ], + ), + ); + } else { + Get.back(); + } + } +} diff --git a/lib/modules/meeting/views/meeting_view.dart b/lib/modules/meeting/views/meeting_view.dart index e48d03c2d..beebef661 100644 --- a/lib/modules/meeting/views/meeting_view.dart +++ b/lib/modules/meeting/views/meeting_view.dart @@ -1,10 +1,12 @@ +import 'package:common_utils/common_utils.dart'; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; -import 'package:lottie/lottie.dart'; import '../../../core/theme/app_colors.dart'; +import '../../../routes/app_routes.dart'; import '../controllers/meeting_controller.dart'; +import '../model/meeting_model.dart'; class MeetingView extends GetView { const MeetingView({super.key}); @@ -16,7 +18,7 @@ class MeetingView extends GetView { return Scaffold( backgroundColor: isDarkMode ? Colors.black : Colors.grey[50], appBar: _buildAppBar(isDarkMode), - body: _buildBody(isDarkMode), + body: _buildBody(context, isDarkMode), ); } @@ -33,25 +35,12 @@ class MeetingView extends GetView { ), onPressed: () => Get.back(), ), - title: Padding( - padding: const EdgeInsets.only(right: 56), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - Icons.keyboard_voice, - color: isDarkMode ? Colors.white : Colors.black, - ), - 5.horizontalSpace, - Text( - '00:00:00', - style: TextStyle( - fontSize: 17.sp, - fontWeight: FontWeight.w600, - color: isDarkMode ? Colors.white : Colors.black, - ), - ), - ], + title: Text( + 'conferenceAssistant'.tr, // 商务会议助手 + style: TextStyle( + fontSize: 17.sp, + fontWeight: FontWeight.w600, + color: isDarkMode ? Colors.white : Colors.black, ), ), centerTitle: true, @@ -59,107 +48,57 @@ class MeetingView extends GetView { } // 构建主体内容 - Widget _buildBody(bool isDarkMode) { - return Padding( - padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 10.w), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Expanded( - child: Text( - '2025-03-03 录音', - style: TextStyle( - color: isDarkMode ? Colors.white : Colors.black87, - fontSize: 17.sp, - ), - ), - ), - 10.horizontalSpace, - Text( - '中文', - style: TextStyle( - color: isDarkMode ? Colors.white : Colors.black87, - fontSize: 15.sp, - ), - ), - ], + Widget _buildBody(BuildContext context, bool isDarkMode) { + return Stack( + children: [ + Obx( + () => ListView.builder( + itemCount: controller.dataList.length, + padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 5.w), + itemBuilder: (BuildContext context, int index) { + // MeetingModel data = + // MeetingModel.fromJson(controller.dataList[index]); + return _item(context, isDarkMode); + }, ), - Padding( - padding: EdgeInsets.symmetric(vertical: 5.w), - child: Text( - '2025-03-03 16:18', - style: TextStyle( - color: isDarkMode ? Colors.grey[400] : Colors.grey[500], - fontSize: 14.sp, + ), + Align( + alignment: Alignment.bottomCenter, + child: GestureDetector( + onTap: () { + _bottomSheet(context, isDarkMode); + }, + 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), + ), + ], ), ), ), - _audioWave(isDarkMode), - _recordContent(isDarkMode), - _bottomButton(isDarkMode), - ], - ), - ); - } - - // 音波 - Widget _audioWave(bool isDarkMode) { - return Container( - width: double.infinity, - height: 70.w, - margin: EdgeInsets.symmetric(vertical: 5.w), - decoration: BoxDecoration( - color: isDarkMode ? Colors.white.withOpacity(0.05) : Colors.white, - borderRadius: BorderRadius.circular(12.w), - boxShadow: [ - BoxShadow( - color: isDarkMode - ? Colors.black.withOpacity(0.2) - : Colors.black.withOpacity(0.05), - blurRadius: 10, - offset: const Offset(0, 2), - ), - ], - ), - child: Row( - children: [ - Expanded( - child: Lottie.asset( - 'assets/lottie/audio_wave.json', - controller: controller.lottieController, - fit: BoxFit.fill, - onLoaded: (composition) { - controller.lottieController.duration = composition.duration; - }, - ), - ), - Expanded( - child: Lottie.asset( - 'assets/lottie/audio_wave.json', - controller: controller.lottieController, - fit: BoxFit.fill, - ), - ), - Expanded( - child: Lottie.asset( - 'assets/lottie/audio_wave.json', - controller: controller.lottieController, - fit: BoxFit.fill, - ), - ), - ], - ), + ), + ], ); } - // 记录内容 - Widget _recordContent(bool isDarkMode) { - return Expanded( + Widget _item(BuildContext context, bool isDarkMode) { + return GestureDetector( + onTap: () {}, child: Container( - width: double.infinity, - padding: EdgeInsets.all(10.w), + padding: EdgeInsets.symmetric(horizontal: 15.w, vertical: 8.w), margin: EdgeInsets.symmetric(vertical: 5.w), decoration: BoxDecoration( color: isDarkMode ? Colors.white.withOpacity(0.05) : Colors.white, @@ -174,54 +113,235 @@ class MeetingView extends GetView { ), ], ), - ), - ); - } - - // 底部按钮 - Widget _bottomButton(bool isDarkMode) { - return Padding( - padding: EdgeInsets.only(top: 40.w, bottom: 30.w), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - GestureDetector( - onTap: () {}, - child: Container( - width: 130.w, - height: 60.w, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(60.w), - border: Border.all( - color: isDarkMode ? AppColors.primary : Colors.blue, - width: 2.w, - ), - ), - child: Icon( - Icons.pause, - color: isDarkMode ? AppColors.primary : Colors.blue, - size: 40.w, + child: Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'data.title', + style: TextStyle( + color: isDarkMode ? Colors.white : Colors.black87, + fontSize: 16.sp, + ), + overflow: TextOverflow.ellipsis, + ), + Text( + '无内容', + style: TextStyle( + color: isDarkMode ? Colors.white70 : Colors.black54, + fontSize: 12.sp, + ), + overflow: TextOverflow.ellipsis, + ), + Text( + '12-13', + style: TextStyle( + color: isDarkMode ? Colors.white70 : Colors.black54, + fontSize: 12.sp, + ), + ), + ], ), ), - ), - GestureDetector( - onTap: () {}, - child: Container( - width: 130.w, - height: 60.w, + Container( + width: 76.w, + height: 28.w, + margin: EdgeInsets.only(left: 10.w), + alignment: Alignment.center, decoration: BoxDecoration( color: isDarkMode ? AppColors.primary : Colors.blue, - borderRadius: BorderRadius.circular(60.w), + borderRadius: BorderRadius.circular(28.w), ), - child: Icon( - Icons.fiber_manual_record_outlined, - color: Colors.white, - size: 40.w, + child: Text( + '00:00:00', + style: TextStyle(color: Colors.white, fontSize: 14.sp), ), ), - ), - ], + ], + ), ), ); } + + void _bottomSheet(BuildContext context, bool isDarkMode) { + showModalBottomSheet( + context: context, + isScrollControlled: true, + clipBehavior: Clip.hardEdge, + builder: (BuildContext context) { + return Padding( + padding: MediaQuery.of(context).viewInsets, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 20.w), + color: isDarkMode ? AppColors.darkBackground : Colors.grey[50], + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + '录音语言转换', + style: TextStyle( + color: isDarkMode ? Colors.white : Colors.black87, + fontSize: 17.sp, + ), + ), + Container( + height: 48.w, + margin: EdgeInsets.only(top: 10.w, bottom: 20.w), + decoration: BoxDecoration( + color: isDarkMode + ? Colors.white.withOpacity(0.05) + : Colors.white, + borderRadius: BorderRadius.circular(12.w), + boxShadow: [ + BoxShadow( + color: isDarkMode + ? Colors.black.withOpacity(0.2) + : Colors.black.withOpacity(0.05), + blurRadius: 10, + offset: const Offset(0, 2), + ), + ], + ), + child: GestureDetector( + onTap: () {}, + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 10.w), + child: Row( + children: [ + Expanded( + child: Text( + '中文', + style: TextStyle( + color: + isDarkMode ? Colors.white : Colors.black87, + fontSize: 16.sp, + ), + ), + ), + Icon( + Icons.keyboard_arrow_right, + color: isDarkMode ? Colors.white : Colors.black87, + ), + ], + ), + ), + ), + ), + Text( + '录音标题(非必填)', + style: TextStyle( + color: isDarkMode ? Colors.white : Colors.black87, + fontSize: 16.sp, + ), + ), + Container( + height: 48.w, + margin: EdgeInsets.only(top: 10.w, bottom: 20.w), + alignment: Alignment.center, + decoration: BoxDecoration( + color: isDarkMode + ? Colors.white.withOpacity(0.05) + : Colors.white, + borderRadius: BorderRadius.circular(12.w), + boxShadow: [ + BoxShadow( + color: isDarkMode + ? Colors.black.withOpacity(0.2) + : Colors.black.withOpacity(0.05), + blurRadius: 10, + offset: const Offset(0, 2), + ), + ], + ), + child: TextField( + controller: controller.titleController, + style: TextStyle( + color: isDarkMode ? Colors.white : Colors.black87, + fontSize: 16.sp, + ), + decoration: InputDecoration( + hintText: '最多50个字符', + hintStyle: TextStyle( + color: isDarkMode ? Colors.white70 : Colors.black54, + fontSize: 16.sp, + ), + fillColor: Colors.transparent, + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(horizontal: 10.w), + ), + ), + ), + Padding( + padding: EdgeInsets.only(top: 30.w), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + GestureDetector( + onTap: () { + Get.back(); + }, + child: Container( + width: 120.w, + height: 40.w, + alignment: Alignment.center, + decoration: BoxDecoration( + border: Border.all( + width: 2.w, + color: + isDarkMode ? AppColors.primary : Colors.blue, + ), + borderRadius: BorderRadius.circular(12.w), + ), + child: Text( + '取消', + style: TextStyle( + fontSize: 14.sp, + color: + isDarkMode ? AppColors.primary : Colors.blue, + ), + ), + ), + ), + GestureDetector( + onTap: () { + int time = DateTime.now().millisecondsSinceEpoch; + if (controller.titleController.text.isEmpty) { + controller.titleController.text = + '${DateUtil.formatDateMs(time, format: DateFormats.y_mo_d)} 录音'; + } + Get.back(); + Get.toNamed(Routes.meetingRecord); + }, + child: Container( + width: 120.w, + height: 40.w, + alignment: Alignment.center, + decoration: BoxDecoration( + color: isDarkMode ? AppColors.primary : Colors.blue, + borderRadius: BorderRadius.circular(12.w), + ), + child: Text( + '确定', + style: TextStyle( + color: Colors.white, + fontSize: 14.sp, + ), + ), + ), + ), + ], + ), + ), + ], + ), + ), + ); + }, + ).then((value) { + controller.titleController.text = ''; + }); + } } diff --git a/lib/modules/settings/views/settings_view.dart b/lib/modules/settings/views/settings_view.dart index 9b8a498e0..e545a083c 100644 --- a/lib/modules/settings/views/settings_view.dart +++ b/lib/modules/settings/views/settings_view.dart @@ -1,17 +1,22 @@ -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 'dart:io'; +import '../../../data/models/user_Info.dart'; class SettingsView extends GetView { const SettingsView({Key? key}) : super(key: key); + // 正确获取用户名方式 + 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(); @override Widget build(BuildContext context) { // 直接使用 Theme.of(context) 获取当前主题 @@ -23,7 +28,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, @@ -73,7 +78,7 @@ class SettingsView extends GetView { ), ], ), - child: ClipRRect(//裁剪圆角矩形 + child: ClipRRect( borderRadius: BorderRadius.circular(16.r), child: BackdropFilter( filter: isDarkMode @@ -81,9 +86,7 @@ class SettingsView extends GetView { : ImageFilter.blur(sigmaX: 0, sigmaY: 0), child: Row( children: [ - Obx(()=>GestureDetector( - //onTap: ()=>controller.updateUserAvatar(), - child:Container(//用户头像部分 + Container( width: 60.w, height: 60.w, decoration: BoxDecoration( @@ -96,33 +99,26 @@ class SettingsView extends GetView { ), ], ), - child: ClipRRect(// 裁剪圆形头像 + 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.file( - File(controller.userInfo.value.avatar), - fit: BoxFit.cover,// 图片填充方式 - ) + child: Image.network( + avatar, + fit: BoxFit.cover, + ), ), ), - )), SizedBox(width: 16.w), - Expanded(// 占据剩余空间 - child: Obx(()=>Column( - crossAxisAlignment: CrossAxisAlignment.start,// 左对齐 + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(//用户名 - controller.userInfo.value.name, + Text( + userName, style: TextStyle( fontSize: 18.sp, fontWeight: FontWeight.w600, color: isDarkMode ? Colors.white : Colors.black87, ), - overflow: TextOverflow.ellipsis, ), SizedBox(height: 4.h), Row( @@ -135,7 +131,7 @@ class SettingsView extends GetView { borderRadius: BorderRadius.circular(10.r), ), child: Text( - controller.userInfo.value.membershipLevel, + standing, style: TextStyle( fontSize: 10.sp, color: Colors.white, @@ -145,7 +141,7 @@ class SettingsView extends GetView { SizedBox(width: 8.w), Flexible( child: Text( - '${'joinDevice'.tr} DeepSound Pro', // 已连接设备: + deviceInfo, style: TextStyle( fontSize: 12.sp, color: @@ -158,7 +154,7 @@ class SettingsView extends GetView { ), SizedBox(height: 4.h), Text( - controller.userInfo.value.id, + userId, style: TextStyle( fontSize: 12.sp, color: isDarkMode ? Colors.white70 : Colors.black54, @@ -166,8 +162,8 @@ class SettingsView extends GetView { ), ], ), - ),), - Container(// 编辑按钮 + ), + Container( width: 32.w, height: 32.w, decoration: BoxDecoration( @@ -183,15 +179,11 @@ class SettingsView extends GetView { ), ], ), - 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], + ), ), ], ), @@ -208,7 +200,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, @@ -239,25 +231,22 @@ class SettingsView extends GetView { : ImageFilter.blur(sigmaX: 0, sigmaY: 0), child: Column( children: [ - // 语言设置 - 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, - ), + // 通知设置 + _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, ), Divider( height: 1, @@ -267,8 +256,8 @@ class SettingsView extends GetView { // 主题设置 _buildToggleSetting( - title: 'darkMode'.tr, // 暗黑模式 - subtitle: 'darkModeInfo'.tr, // 切换应用的明暗主题 + title: '暗黑模式', + subtitle: '切换应用的明暗主题', icon: isDarkMode ? Icons.dark_mode : Icons.light_mode, iconBgColor: isDarkMode ? Colors.indigo[900]!.withOpacity(0.3) @@ -289,15 +278,15 @@ class SettingsView extends GetView { // 备份与恢复 _buildNavigationSetting( - title: 'backups'.tr, // 备份与恢复 - subtitle: 'backupsInfo'.tr, // 备份设置和AI互动历史 + title: '备份与恢复', + subtitle: '备份设置和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: '${'lastBackup'.tr} 今天', // 上次备份: + value: '上次备份: 今天', valueStyle: TextStyle( fontSize: 12.sp, color: isDarkMode ? Colors.white70 : Colors.grey[500], @@ -307,15 +296,6 @@ class SettingsView extends GetView { }, isDarkMode: isDarkMode, ), - Divider( - height: 1, - color: isDarkMode - ? Colors.white.withOpacity(0.1) - : Colors.grey[200]), - - // 语音服务选择 - _buildSpeechServiceSetting(isDarkMode), - Divider( height: 1, color: isDarkMode @@ -324,8 +304,8 @@ class SettingsView extends GetView { // 隐私设置 _buildSimpleNavigationSetting( - title: 'privacySettings'.tr, // 隐私设置 - subtitle: 'privacySettingsInfo'.tr, // 管理数据收集和权限 + title: '隐私设置', + subtitle: '管理数据收集和权限', icon: Icons.shield_outlined, iconBgColor: isDarkMode ? Colors.purple[900]!.withOpacity(0.3) @@ -335,7 +315,7 @@ class SettingsView extends GetView { titleColor: isDarkMode ? Colors.white : null, subtitleColor: isDarkMode ? Colors.white70 : null, onTap: () { - AppSettings.openAppSettings(); + // 打开隐私设置页面 }, isDarkMode: isDarkMode, ), @@ -347,7 +327,7 @@ class SettingsView extends GetView { // 退出登录 _buildSimpleNavigationSetting( - title: 'logout'.tr, // 退出登录 + title: '退出登录', icon: Icons.logout, iconBgColor: isDarkMode ? Colors.red[900]!.withOpacity(0.3) @@ -362,13 +342,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, @@ -378,7 +358,7 @@ class SettingsView extends GetView { TextButton( onPressed: () => Get.back(), child: Text( - 'cancel'.tr, // 取消 + '取消', style: TextStyle( color: isDarkMode ? Colors.white70 @@ -392,7 +372,7 @@ class SettingsView extends GetView { controller.signOut(); }, child: Text( - 'confirm'.tr, // 确定 + '确定', style: TextStyle( color: isDarkMode ? Colors.red[300] @@ -423,7 +403,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, @@ -485,7 +465,7 @@ class SettingsView extends GetView { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - 'DeepSound', + 'DeepVoice', style: TextStyle( fontSize: 16.sp, fontWeight: FontWeight.w500, @@ -494,7 +474,7 @@ class SettingsView extends GetView { ), SizedBox(height: 2.h), Text( - '${'versions'.tr} 2.4.1 (Build 87)', // 版本 + '版本 2.4.1 (Build 87)', style: TextStyle( fontSize: 12.sp, color: @@ -515,7 +495,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], @@ -532,7 +512,7 @@ class SettingsView extends GetView { child: Column( children: [ Text( - '© 2025 DeepSound Technologies', + '© 2023 DeepVoice Technologies', style: TextStyle( fontSize: 12.sp, color: isDarkMode ? Colors.grey[400] : Colors.grey[500], @@ -540,7 +520,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], @@ -569,51 +549,45 @@ class SettingsView extends GetView { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Expanded( - child: 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, - ), + Row( + children: [ + Container( + width: 32.w, + height: 32.w, + decoration: BoxDecoration( + color: iconBgColor, + borderRadius: BorderRadius.circular(16.r), ), - 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, - ), - ], - ), + 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, + ), + ), + if (subtitle != null) SizedBox(height: 2.h), + if (subtitle != null) + Text( + subtitle, + style: TextStyle( + fontSize: 12.sp, + color: isDarkMode ? Colors.white70 : Colors.black54, + ), + ), + ], + ), + ], ), Switch( value: value, @@ -644,66 +618,55 @@ class SettingsView extends GetView { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Expanded( - child: 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, - ), + Row( + children: [ + Container( + width: 32.w, + height: 32.w, + decoration: BoxDecoration( + color: iconBgColor, + borderRadius: BorderRadius.circular(16.r), ), - 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, - ), - ], - ), + 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, + ), + ), + if (subtitle != null) SizedBox(height: 2.h), + if (subtitle != null) + Text( + subtitle, + style: TextStyle( + fontSize: 12.sp, + color: isDarkMode ? Colors.white70 : Colors.black54, + ), + ), + ], + ), + ], ), Row( - mainAxisSize: MainAxisSize.min, children: [ - Flexible( - child: Text( - value, - style: valueStyle ?? - TextStyle( - fontSize: 14.sp, - color: isDarkMode ? Colors.white : Colors.black87, - ), - overflow: TextOverflow.ellipsis, - ), + Text( + value, + style: valueStyle ?? + TextStyle( + fontSize: 14.sp, + color: isDarkMode ? Colors.white : Colors.black87, + ), ), SizedBox(width: 8.w), Container( @@ -748,391 +711,64 @@ class SettingsView extends GetView { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Expanded( - child: 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), - 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, - 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], - ), - ), - ], - ), - ), - ); - } - - // 语音服务设置项 - 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], + color: iconBgColor, borderRadius: BorderRadius.circular(16.r), ), child: Icon( - Icons.chevron_right, + icon, size: 16.w, - color: isDarkMode ? Colors.white70 : Colors.grey[500], + color: iconColor, ), ), - ], - ), - ], - ), - ), - ); - } - - // 显示语音服务选择对话框 - 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, // 取消 + SizedBox(width: 12.w), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, style: TextStyle( - color: isDarkMode ? Colors.white70 : Colors.black54, + fontSize: 16.sp, + fontWeight: FontWeight.w500, + color: titleColor ?? + (isDarkMode ? Colors.white : Colors.black87), ), ), - ), - ], - ), - ], - ), - ), - ), - ); - } - - // 显示语言选择对话框 - 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, - ), + if (subtitle != null) SizedBox(height: 2.h), + if (subtitle != null) + Text( + subtitle, + style: TextStyle( + fontSize: 12.sp, + color: subtitleColor ?? + (isDarkMode ? Colors.white70 : Colors.black54), ), - 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, ), - ), - ), + ], ), + ], + ), + 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], + ), + ), + ], ), ), ); diff --git a/lib/routes/app_pages.dart b/lib/routes/app_pages.dart index 04467cd53..0114183d9 100644 --- a/lib/routes/app_pages.dart +++ b/lib/routes/app_pages.dart @@ -4,6 +4,8 @@ import '../modules/home/bindings/home_binding.dart'; import '../modules/explore/views/explore_view.dart'; import '../modules/explore/bindings/explore_binding.dart'; import '../modules/meeting/bindings/meeting_binding.dart'; +import '../modules/meeting/bindings/meeting_record_binding.dart'; +import '../modules/meeting/views/meeting_record_view.dart'; import '../modules/meeting/views/meeting_view.dart'; import '../modules/profile/views/profile_view.dart'; import '../modules/profile/bindings/profile_binding.dart'; @@ -58,7 +60,7 @@ abstract class AppPages { ), GetPage( name: Routes.chat, - page: () => ChatView(), + page: () => const ChatView(), binding: ChatBinding(), ), GetPage( @@ -70,8 +72,7 @@ abstract class AppPages { name: Routes.editProfile, page: () => const EditProfileView(), binding: ProfileBinding(), - ), - + ), GetPage( name: Routes.translation, page: () => const TranslationView(), @@ -87,6 +88,11 @@ abstract class AppPages { page: () => const MeetingView(), binding: MeetingBinding(), ), + GetPage( + name: Routes.meetingRecord, + page: () => const MeetingRecordView(), + binding: MeetingRecordBinding(), + ), GetPage( name: Routes.settings, page: () => const SettingsView(), diff --git a/lib/routes/app_routes.dart b/lib/routes/app_routes.dart index b7e94af7e..0504f2c71 100644 --- a/lib/routes/app_routes.dart +++ b/lib/routes/app_routes.dart @@ -8,6 +8,7 @@ abstract class Routes { static const chat = '/chat'; static const profile = '/profile'; static const meeting = '/meeting'; + static const meetingRecord = '/meetingRecord'; static const settings = '/settings'; static const speechDemo = '/speech_demo'; static const TTS_TEST = '/tts_test'; @@ -19,5 +20,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 + static const editProfile = '/edit_profile'; +} diff --git a/pubspec.yaml b/pubspec.yaml index 9e29df437..b023e3efe 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -55,9 +55,11 @@ dependencies: firebase_auth: ^5.5.0 google_sign_in: ^6.2.2 flutter_facebook_auth: ^7.1.1 + common_utils: ^2.1.0 flutter_image_compress: ^2.4.0 # image_cropper: ^9.0.0 - + dio: ^5.8.0+1 + flutter_easyloading: ^3.0.5 dev_dependencies: flutter_test: sdk: flutter