diff --git a/lib/core/bindings/initial_binding.dart b/lib/core/bindings/initial_binding.dart index c6539c0e2..08bb41500 100644 --- a/lib/core/bindings/initial_binding.dart +++ b/lib/core/bindings/initial_binding.dart @@ -9,6 +9,7 @@ import '../../data/services/language_manager.dart'; import '../../core/utils/logger.dart'; import '../../data/services/bluetooth_manager.dart'; import '../../data/services/ble_manager.dart'; +import '../../data/services/account_service.dart'; /// 初始绑定,用于管理全局依赖 class InitialBinding extends Bindings { @@ -42,6 +43,9 @@ class InitialBinding extends Bindings { // 火山AI服务 // Get.lazyPut(() => VolcanoAIService(), fenix: true); + // 账号管理服务 + Get.lazyPut(() => AccountService(), fenix: true); + // 杰理蓝牙服务 // // 语言管理器(需要最先初始化) diff --git a/lib/data/services/account_service.dart b/lib/data/services/account_service.dart new file mode 100644 index 000000000..6c01ac65c --- /dev/null +++ b/lib/data/services/account_service.dart @@ -0,0 +1,144 @@ +import 'package:get/get.dart'; +import 'package:get_storage/get_storage.dart'; +import 'package:logger/logger.dart'; +import '../../modules/login/controllers/googleLogin.dart'; +import '../../modules/login/controllers/facebookLogin.dart'; +import '../services/network/api.dart'; + +/// 账号管理服务 +/// +/// 负责处理所有与用户账号相关的操作,包括登出和账号删除 +class AccountService extends GetxService { + final GetStorage _storage = GetStorage(); + final logger = Logger(); + + /// 初始化服务 + @override + void onInit() { + super.onInit(); + logger.d('AccountService 已初始化'); + } + + /// 清理本地用户数据 + /// + /// 返回操作是否成功 + Future clearUserData() async { + try { + // 清理所有存储 + await _storage.erase(); + // 或者逐个清理关键存储项 + await _storage.write("logintoken", null); + await _storage.write("user_info", null); + await _storage.write("local_avatar", null); + logger.d('用户数据清理成功'); + return true; + } catch (e) { + logger.e('用户数据清理失败: $e'); + return false; + } + } + + /// 退出第三方登录 + /// + /// 返回操作是否成功 + Future logoutThirdParty() async { + try { + final google = Google(); + final facebook = Facebook(); + await Future.wait([ + google.signOutGoogle(), + facebook.signOutFacebook(), + ]); + logger.d('第三方登录退出成功'); + return true; + } catch (e) { + logger.e('第三方登录退出失败: $e'); + // 返回true,因为这不应阻止整体登出流程 + return true; + } + } + + /// 用户登出 + /// + /// 清理本地数据并退出第三方登录 + /// 返回操作结果,包含成功状态和错误信息 + Future signOut() async { + try { + // 清理本地数据 + final dataCleaned = await clearUserData(); + if (!dataCleaned) { + return AccountOperationResult( + success: false, + errorMessage: '清理用户数据失败', + ); + } + + // 退出第三方登录 + await logoutThirdParty(); + + return AccountOperationResult( + success: true, + message: '已退出登录', + ); + } catch (e) { + logger.e('登出过程发生错误: $e'); + return AccountOperationResult( + success: false, + errorMessage: '退出登录失败,请重试', + ); + } + } + + /// 删除用户账号 + /// + /// 调用API删除账号,然后清理本地数据 + /// 返回操作结果,包含成功状态和错误信息 + Future deleteAccount() async { + try { + // 调用API删除账号 + final response = await Api.deleteUser(); + if (response == null) { + return AccountOperationResult( + success: false, + errorMessage: '删除用户数据失败', + ); + } + + // 清理本地数据 + final dataCleaned = await clearUserData(); + if (!dataCleaned) { + return AccountOperationResult( + success: false, + errorMessage: '清理用户数据失败', + ); + } + + // 退出第三方登录 + await logoutThirdParty(); + + return AccountOperationResult( + success: true, + message: '账号已被永久删除', + ); + } catch (e) { + logger.e('删除账号过程发生错误: $e'); + return AccountOperationResult( + success: false, + errorMessage: '删除账号失败,请重试', + ); + } + } +} + +/// 账号操作结果 +class AccountOperationResult { + final bool success; + final String? message; + final String? errorMessage; + + AccountOperationResult({ + required this.success, + this.message, + this.errorMessage, + }); +} diff --git a/lib/modules/agent/views/agent_view.dart b/lib/modules/agent/views/agent_view.dart index b07c4efff..fb1887ee5 100644 --- a/lib/modules/agent/views/agent_view.dart +++ b/lib/modules/agent/views/agent_view.dart @@ -168,34 +168,22 @@ class AgentView extends StatelessWidget { final message = controller .messages[controller.messages.length - 1 - index]; return Padding( - padding: const EdgeInsets.only(bottom: 8), - child: !message.isUser - ? GestureDetector( - onLongPressStart: - (LongPressStartDetails details) { - _onLongPressMessageContent( - context, isDarkMode, details, message); - }, - child: MessageBubble( - isUser: message.isUser, - message: message.text, - timestamp: message.timestamp, - isRecognizing: message.isRecognizing, - hasImage: message.hasImage, - imagePath: message.imagePath, - meta: message.meta, - ), - ) - : MessageBubble( - isUser: message.isUser, - message: message.text, - timestamp: message.timestamp, - isRecognizing: message.isRecognizing, - hasImage: message.hasImage, - imagePath: message.imagePath, - meta: message.meta, - ), - ); + padding: const EdgeInsets.only(bottom: 8), + child: GestureDetector( + onLongPressStart: (LongPressStartDetails details) { + _onLongPressMessageContent( + context, isDarkMode, details, message); + }, + child: MessageBubble( + isUser: message.isUser, + message: message.text, + timestamp: message.timestamp, + isRecognizing: message.isRecognizing, + hasImage: message.hasImage, + imagePath: message.imagePath, + meta: message.meta, + ), + )); }, ), ), @@ -602,164 +590,36 @@ class AgentView extends StatelessWidget { )); }, ), - // 自定义分隔线样式 - PopupMenuItem( - height: 1, // 控制间距 - enabled: false, - child: Divider( - height: 1, - thickness: 0.8, - indent: 0, - endIndent: 0, - color: isDarkMode ? Colors.grey[700] : Colors.grey[300], + if (!message.isUser) ...[ + // 自定义分隔线样式 + PopupMenuItem( + height: 1, // 控制间距 + enabled: false, + child: Divider( + height: 1, + thickness: 0.8, + indent: 0, + endIndent: 0, + color: isDarkMode ? Colors.grey[700] : Colors.grey[300], + ), ), - ), - _popupMenuEntry( - Icon( - Icons.report, - size: 20.w, - color: isDarkMode ? Colors.red[700] : Colors.red, + _popupMenuEntry( + Icon( + Icons.report, + size: 20.w, + color: isDarkMode ? Colors.red[700] : Colors.red, + ), + '举报'.tr, // 举报 + isDarkMode ? Colors.red[700]! : Colors.red, + () { + // 实现举报功能 + final reportController = Get.find(); + reportController.initReport(message); + + _showReportForm(context, isDarkMode); + }, ), - '举报'.tr, // 举报 - isDarkMode ? Colors.red[700]! : Colors.red, - () { - // 实现举报功能 - final reportController = Get.find(); - reportController.initReport(message); - - _showReportForm(context, isDarkMode); - // showModalBottomSheet( - // context: context, - // isScrollControlled: true, // 关键:允许弹窗随键盘移动 - // backgroundColor: - // isDarkMode ? AppColors.cardBackground : Colors.white, - // shape: RoundedRectangleBorder( - // borderRadius: BorderRadius.vertical(top: Radius.circular(16.r)), - // ), - // builder: (BuildContext context) { - // return Padding( - // // 添加底部内边距以容纳键盘 - // padding: EdgeInsets.only( - // bottom: MediaQuery.of(context).viewInsets.bottom, - // ), - // child: Container( - // padding: EdgeInsets.all(16.w), - // child: SingleChildScrollView( - // // 添加滚动视图 - // child: Column( - // mainAxisSize: MainAxisSize.min, - // crossAxisAlignment: CrossAxisAlignment.stretch, - // children: [ - // Stack( - // alignment: Alignment.center, - // children: [ - // Center( - // child: Text( - // '举报', - // style: TextStyle( - // fontSize: 18.sp, - // fontWeight: FontWeight.bold, - // color: isDarkMode - // ? Colors.white - // : Colors.black, - // ), - // ), - // ), - // SizedBox( - // height: 20.w, - // ), - // Positioned( - // right: 0, - // child: IconButton( - // icon: Icon( - // Icons.close, - // color: isDarkMode - // ? Colors.grey[400] - // : Colors.black54, - // ), - // onPressed: () => Navigator.pop(context), - // ), - // ), - // ], - // ), - // Container( - // padding: EdgeInsets.symmetric(horizontal: 12.w), - // margin: EdgeInsets.only(bottom: 10.w), - // child: Divider( - // height: 1, - // color: Colors.grey[300], - // ), - // ), - // Wrap( - // spacing: 8.w, - // runSpacing: 8.h, - // children: [ - // _buildReportChip('有害/不安全', isDarkMode), - // _buildReportChip('色情低俗', isDarkMode), - // _buildReportChip('误导攻击', isDarkMode), - // _buildReportChip('违法犯罪', isDarkMode), - // _buildReportChip('隐私相关', isDarkMode), - // _buildReportChip('侵犯我的著作权、名誉权等', isDarkMode), - // _buildReportChip('其他', isDarkMode), - // ], - // ), - // SizedBox(height: 16.h), - // Container( - // padding: EdgeInsets.all(12.w), - // decoration: BoxDecoration( - // color: isDarkMode - // ? Colors.grey[800] - // : Colors.grey[200], - // borderRadius: BorderRadius.circular(8.r), - // ), - // child: TextField( - // maxLines: 3, - // style: TextStyle( - // color: isDarkMode ? Colors.white : Colors.black, - // ), - // decoration: InputDecoration( - // hintText: '我们想知道你举报的原因,你可以描述你遇到的问题。', - // hintStyle: TextStyle( - // color: isDarkMode - // ? Colors.white70 - // : Colors.black54, - // fontSize: 14.sp, - // ), - // border: InputBorder.none, - // ), - // ), - // ), - // SizedBox(height: 14.h), - // ElevatedButton( - // style: ElevatedButton.styleFrom( - // backgroundColor: Colors.blue, - // foregroundColor: Colors.white, - // padding: EdgeInsets.symmetric(vertical: 12.h), - // shape: RoundedRectangleBorder( - // borderRadius: BorderRadius.circular(8.r), - // ), - // ), - // child: Text('提交'), - // onPressed: () { - // Navigator.pop(context); - // Get.snackbar( - // '提示', - // '举报已提交,我们会尽快处理', - // backgroundColor: Colors.green.withOpacity(0.8), - // colorText: Colors.white, - // ); - // }, - // ), - // SizedBox(height: 8.h), - // ], - // ), - // ), - // ), - // ); - // }, - // ); - }, - ), + ] ], ); } @@ -784,27 +644,6 @@ class AgentView extends StatelessWidget { ); } - // // 添加这个辅助方法来创建举报选项 - // Widget _buildReportChip(String label, bool isDarkMode) { - // return ChoiceChip( - // label: Text( - // label, - // style: TextStyle( - // color: isDarkMode ? Colors.white : Colors.black87, - // fontSize: 14.sp, - // ), - // ), - // selected: false, - // backgroundColor: isDarkMode ? Colors.grey[800] : Colors.grey[200], - // shape: RoundedRectangleBorder( - // borderRadius: BorderRadius.circular(16.r), - // ), - // onSelected: (bool selected) { - // // 可以在这里处理选择逻辑 - // }, - // ); - // } - PopupMenuEntry _popupMenuEntry( Icon icon, String title, diff --git a/lib/modules/login/controllers/login_controller.dart b/lib/modules/login/controllers/login_controller.dart index c512d1eb7..d6cc76ca1 100644 --- a/lib/modules/login/controllers/login_controller.dart +++ b/lib/modules/login/controllers/login_controller.dart @@ -201,6 +201,9 @@ class LoginController extends GetxController { // 显示隐私政策弹窗 void _showPrivacyDialog() { + // 获取当前主题模式 + final bool isDarkMode = Get.theme.brightness == Brightness.dark; + Get.dialog( WillPopScope( onWillPop: () async => false, // 禁止通过返回键关闭弹窗 @@ -208,16 +211,20 @@ class LoginController extends GetxController { shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), + backgroundColor: + isDarkMode ? Colors.grey[800] : Colors.white, // 暗黑模式使用深灰色背景 child: Container( padding: const EdgeInsets.all(20), child: Column( mainAxisSize: MainAxisSize.min, children: [ - const Text( + Text( 'DeapSound 声明', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, + color: + isDarkMode ? Colors.white : Colors.black, // 暗黑模式使用白色文字 ), ), const SizedBox(height: 16), @@ -226,9 +233,14 @@ class LoginController extends GetxController { child: Text.rich( TextSpan( children: [ - const TextSpan( + TextSpan( text: '欢迎使用DeapSound,请充分阅读', - style: TextStyle(fontSize: 14), + style: TextStyle( + fontSize: 14, + color: isDarkMode + ? Colors.white70 + : Colors.black87, // 暗黑模式使用浅白色 + ), ), WidgetSpan( alignment: PlaceholderAlignment.middle, @@ -244,18 +256,21 @@ class LoginController extends GetxController { title: 'privacyPolicy'.tr, )); }, - child: Text( + child: const Text( '《隐私政策》', style: TextStyle( fontSize: 14, - color: const Color(0xFF3B82F6), + color: Color(0xFF3B82F6), ), ), ), ), - const TextSpan( + TextSpan( text: '和', - style: TextStyle(fontSize: 14), + style: TextStyle( + fontSize: 14, + color: isDarkMode ? Colors.white70 : Colors.black87, + ), ), WidgetSpan( alignment: PlaceholderAlignment.middle, @@ -271,18 +286,21 @@ class LoginController extends GetxController { title: 'userAgreement'.tr, )); }, - child: Text( + child: const Text( '《用户协议》', style: TextStyle( fontSize: 14, - color: const Color(0xFF3B82F6), + color: Color(0xFF3B82F6), ), ), ), ), - const TextSpan( + TextSpan( text: ',点击“同意并进入”表示您已充分阅读并同意上述协议。点击“不同意”将退出应用。', - style: TextStyle(fontSize: 14), + style: TextStyle( + fontSize: 14, + color: isDarkMode ? Colors.white70 : Colors.black87, + ), ), ], ), @@ -299,9 +317,17 @@ class LoginController extends GetxController { exit(0); }, style: TextButton.styleFrom( - foregroundColor: Colors.black54, + foregroundColor: isDarkMode + ? Colors.grey[400] + : Colors.black54, // 暗黑模式使用浅灰色 + ), + child: Text( + '不同意', + style: TextStyle( + color: isDarkMode ? Colors.grey[400] : Colors.black54, + ), ), - child: const Text('不同意'), + //child: const Text('不同意'), ), ElevatedButton( onPressed: () { @@ -317,6 +343,9 @@ class LoginController extends GetxController { shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), + elevation: isDarkMode ? 4 : 2, // 暗黑模式下增加阴影效果提高辨识度 + padding: const EdgeInsets.symmetric( + horizontal: 16, vertical: 10), // 统一的内边距 ), child: const Text('同意并进入'), ), diff --git a/lib/modules/login/views/login_view.dart b/lib/modules/login/views/login_view.dart index 98cdfae0f..deb838681 100644 --- a/lib/modules/login/views/login_view.dart +++ b/lib/modules/login/views/login_view.dart @@ -177,9 +177,23 @@ class LoginView extends GetView { return Container( decoration: BoxDecoration( color: isDarkMode - ? Colors.grey[900]!.withOpacity(0.4) + ? Colors.grey[850]!.withOpacity(0.6) // 更深的背景色,增加不透明度 : Colors.grey.withOpacity(0.1), borderRadius: BorderRadius.circular(16.r), + border: Border.all( + color: + isDarkMode ? Colors.grey[700]! : Colors.transparent, // 暗黑模式添加边框 + width: 1, + ), + boxShadow: isDarkMode + ? [ + BoxShadow( + color: Colors.black.withOpacity(0.2), + blurRadius: 4, + offset: const Offset(0, 2), + ) + ] + : [], // 暗黑模式添加阴影 ), child: Padding( padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 4.h), @@ -213,32 +227,27 @@ class LoginView extends GetView { Container( height: 20.h, width: 1, - color: isDarkMode ? Colors.grey[800] : Colors.grey[300], + color: + isDarkMode ? Colors.grey[600] : Colors.grey[300], // 调整分隔线颜色 margin: EdgeInsets.symmetric(horizontal: 8.w), ), - // Icon( - // Icons.person_outline, - // color: isDarkMode ? Colors.grey[400] : Colors.black54, - // size: 20.sp, - // ), - //SizedBox(width: 12.w), Expanded( child: TextField( controller: controller.contactController, - // keyboardType: controller.selectedCountryFlag == '✉️' - // ? TextInputType.emailAddress - // : TextInputType.phone, decoration: InputDecoration( hintText: controller.selectedCountryFlag == '✉️' ? 'email'.tr // 需要在翻译文件中添加 : 'phoneNumber'.tr, // 需要在翻译文件中添加 hintStyle: TextStyle( fontSize: 14.sp, - color: isDarkMode ? Colors.grey[600] : Colors.black38, + color: isDarkMode + ? Colors.grey[500] + : Colors.black38, // 调整提示文字颜色 ), border: InputBorder.none, fillColor: Colors.transparent, filled: true, + focusedBorder: InputBorder.none, ), style: TextStyle( fontSize: 14.sp, @@ -322,9 +331,23 @@ class LoginView extends GetView { return Container( decoration: BoxDecoration( color: isDarkMode - ? Colors.grey[900]!.withOpacity(0.4) + ? Colors.grey[850]!.withOpacity(0.6) // 更深的背景色,增加不透明度 : Colors.grey.withOpacity(0.1), borderRadius: BorderRadius.circular(16.r), + border: Border.all( + color: + isDarkMode ? Colors.grey[700]! : Colors.transparent, // 暗黑模式添加边框 + width: 1, + ), + boxShadow: isDarkMode + ? [ + BoxShadow( + color: Colors.black.withOpacity(0.2), + blurRadius: 4, + offset: const Offset(0, 2), + ) + ] + : [], // 暗黑模式添加阴影 ), child: Padding( padding: EdgeInsets.only(left: 16.w, right: 4.w, top: 4.h, bottom: 4.h), @@ -345,12 +368,15 @@ class LoginView extends GetView { hintText: 'verificationCode'.tr, // 验证码 hintStyle: TextStyle( fontSize: 14.sp, - color: isDarkMode ? Colors.grey[600] : Colors.black38, + color: isDarkMode + ? Colors.grey[500] + : Colors.black38, // 调整提示文字颜色 ), border: InputBorder.none, counterText: '', fillColor: Colors.transparent, filled: true, + focusedBorder: InputBorder.none, ), style: TextStyle( fontSize: 14.sp, diff --git a/lib/modules/meeting/controllers/meeting_controller.dart b/lib/modules/meeting/controllers/meeting_controller.dart index 4277e1cea..f077169cc 100644 --- a/lib/modules/meeting/controllers/meeting_controller.dart +++ b/lib/modules/meeting/controllers/meeting_controller.dart @@ -7,6 +7,7 @@ import 'package:flutter_easyloading/flutter_easyloading.dart'; import 'package:get/get.dart'; import 'package:http/http.dart' as http; import 'package:just_audio/just_audio.dart'; +import 'package:path/path.dart' as path; import 'package:path_provider/path_provider.dart'; import 'package:permission_handler/permission_handler.dart'; @@ -17,6 +18,8 @@ import '../../../core/utils/permission_util.dart'; import 'package:share_plus/share_plus.dart'; +import '../../../routes/app_routes.dart'; + class MeetingController extends GetxController { final ScrollController listController = ScrollController(); final AudioPlayer _audioPlayer = AudioPlayer(); @@ -38,6 +41,7 @@ class MeetingController extends GetxController { List dataListTask = []; bool isDisposed = false; + String filePath = ''; final String _appid = '2316081933'; final String _token = 'mcJTh6OelBqU_L78lgGd77Nyrc_-5-En'; @@ -57,6 +61,15 @@ class MeetingController extends GetxController { }; _getDataList(); addMeetingAudio(); + final Map args = Get.arguments ?? {}; + // 检查参数是否存在且包含有效路径 + if (args != null && + args.containsKey('filePath') && + args['filePath'] is String && + args['filePath'].isNotEmpty) { + final String path = args['filePath']; + addAudio(path); + } } @override @@ -251,6 +264,7 @@ class MeetingController extends GetxController { // 移动文件 await file.copy(newPath); await file.delete(); + addQuantity++; } } catch (e) { @@ -304,39 +318,53 @@ class MeetingController extends GetxController { EasyLoading.dismiss(); } - void addAudio() async { + void addAudio([String? filePath]) async { EasyLoading.show(status: '正在导入...'); - FilePickerResult? result = await FilePicker.platform.pickFiles( - withData: true, - ); - if (result != null) { - PlatformFile file = result.files.first; - String filePath = await _saveFile(file); - String fileName = file.name; - await _audioPlayer.setAudioSource(AudioSource.file(filePath)); - var audiourl = - await UploadOss.upload(filepath: filePath, rootDir: 'ExternalAudio'); - int seconds = _audioPlayer.duration?.inSeconds ?? 0; - int creationTime = DateTime.now().millisecondsSinceEpoch; - Map data = { - 'title': fileName, - 'type': 'IMPORT', - 'seconds': seconds, - 'filepath': filePath, - 'audiourl': audiourl, - 'tasktype': 0, - 'creationtime': creationTime, - }; - final meetingId = await SqfliteApi.insertMeeting(data); - if (meetingId != null) { - dataList.insert(0, { - 'id': meetingId, - 'taskid': '', - ...data, - }); - addQuantity++; + String fileName = ""; + bool isParamProvided = filePath != null; // 更清晰的变量名 + if (filePath != null) { + // 正确截取文件名(跨平台安全方式) + fileName = path.basename(filePath); + } else { + FilePickerResult? result = await FilePicker.platform.pickFiles( + withData: true, + ); + if (result != null) { + PlatformFile file = result.files.first; + filePath = await _saveFile(file); + fileName = file.name; } } + + await _audioPlayer.setAudioSource(AudioSource.file(filePath!)); + var audiourl = + await UploadOss.upload(filepath: filePath, rootDir: 'ExternalAudio'); + int seconds = _audioPlayer.duration?.inSeconds ?? 0; + int creationTime = DateTime.now().millisecondsSinceEpoch; + Map data = { + 'title': fileName, + 'type': 'IMPORT', + 'seconds': seconds, + 'filepath': filePath, + 'audiourl': audiourl, + 'tasktype': 0, + 'creationtime': creationTime, + }; + final meetingId = await SqfliteApi.insertMeeting(data); + if (meetingId != null) { + dataList.insert(0, { + 'id': meetingId, + 'taskid': '', + ...data, + }); + addQuantity++; + } + + if (isParamProvided) { + Get.toNamed(Routes.meetingDetails, arguments: { + 'id': meetingId, + }); + } EasyLoading.dismiss(); } diff --git a/lib/modules/meeting/controllers/meeting_details_controller.dart b/lib/modules/meeting/controllers/meeting_details_controller.dart index c2fcdf142..8577525ba 100644 --- a/lib/modules/meeting/controllers/meeting_details_controller.dart +++ b/lib/modules/meeting/controllers/meeting_details_controller.dart @@ -145,6 +145,7 @@ class MeetingDetailsController extends GetxController } await playerController.preparePlayer( path: meetingData.value.filepath, + shouldExtractWaveform: false, noOfSamples: 88, ); durationMax.value = await playerController.getDuration(DurationType.max); diff --git a/lib/modules/meeting/controllers/meeting_record_controller.dart b/lib/modules/meeting/controllers/meeting_record_controller.dart index aaae77c96..2fea38ea5 100644 --- a/lib/modules/meeting/controllers/meeting_record_controller.dart +++ b/lib/modules/meeting/controllers/meeting_record_controller.dart @@ -153,7 +153,8 @@ class MeetingRecordController extends GetxController } /// Saves new title and exits editing mode - void saveTitleChange() { + Future saveTitleChange() async { + isEditingTitle.value = true; if (isEditingTitle.value) { newName.value = titleEditingController.text; isEditingTitle.value = false; diff --git a/lib/modules/meeting/views/meeting_details_view.dart b/lib/modules/meeting/views/meeting_details_view.dart index f3b33c19b..de44350a7 100644 --- a/lib/modules/meeting/views/meeting_details_view.dart +++ b/lib/modules/meeting/views/meeting_details_view.dart @@ -1,3 +1,5 @@ +import 'dart:math'; + import 'package:audio_waveforms/audio_waveforms.dart'; import 'package:common_utils/common_utils.dart'; import 'package:flutter/material.dart'; @@ -108,6 +110,9 @@ class MeetingDetailsView extends GetView { child: AudioFileWaveforms( size: Size(350.w, 30.w), playerController: controller.playerController, + waveformData: List.generate(88, (int index) { + return 0.01 + Random().nextDouble() * 0.09; + }), waveformType: WaveformType.fitWidth, enableSeekGesture: false, playerWaveStyle: PlayerWaveStyle( @@ -350,6 +355,9 @@ class MeetingDetailsView extends GetView { child: AudioFileWaveforms( size: Size(350.w, 120.w), playerController: controller.playerController, + waveformData: List.generate(88, (int index) { + return 0.01 + Random().nextDouble() * 0.09; + }), waveformType: WaveformType.fitWidth, playerWaveStyle: PlayerWaveStyle( spacing: 4.w, diff --git a/lib/modules/meeting/views/meeting_record_view.dart b/lib/modules/meeting/views/meeting_record_view.dart index ec10ee77b..10d288604 100644 --- a/lib/modules/meeting/views/meeting_record_view.dart +++ b/lib/modules/meeting/views/meeting_record_view.dart @@ -348,7 +348,7 @@ class MeetingRecordView extends GetView { Expanded( child: TextButton( onPressed: () async { - controller.saveTitleChange(); + await controller.saveTitleChange(); await controller.saveRecording(); Get.back(); }, diff --git a/lib/modules/profile/controllers/profile_controller.dart b/lib/modules/profile/controllers/profile_controller.dart index bea4404c0..1dae96f4f 100644 --- a/lib/modules/profile/controllers/profile_controller.dart +++ b/lib/modules/profile/controllers/profile_controller.dart @@ -7,14 +7,15 @@ import 'package:logger/logger.dart'; import '../../../data/models/user_Info.dart'; import '../../settings/controllers/settings_controller.dart'; import 'dart:io'; -import 'dart:convert'; +//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'; import '../../../routes/app_routes.dart'; -import '../../../modules/login/controllers/googleLogin.dart'; -import '../../../modules/login/controllers/facebookLogin.dart'; +//import '../../../modules/login/controllers/googleLogin.dart'; +//import '../../../modules/login/controllers/facebookLogin.dart'; +import '../../../data/services/account_service.dart'; //import 'package:http_parser/http_parser.dart'; // 新增此导入 class ProfileController extends GetxController { @@ -31,9 +32,14 @@ class ProfileController extends GetxController { // 使用响应式变量 final currentLanguage = 'zh'.obs; + // 账号服务 + late final AccountService _accountService; + @override void onInit() { super.onInit(); + // 获取或注册账号服务 + _accountService = Get.find(); loadUserInfo(); loadLanguagePreference(); } @@ -358,49 +364,41 @@ class ProfileController extends GetxController { } } + // 退出登录 Future signOut() async { try { - //显示加载提示 - // Get.dialog( - // const Center(child: CircularProgressIndicator()), - // barrierDismissible: false, - // ); - - // 清理所有存储 - await _storage.erase(); - // 或者逐个清理关键存储项 - await _storage.write("logintoken", null); - await _storage.write("user_info", null); - await _storage.write("local_avatar", null); - print('清理存储成功'); - - // 第三方登录退出 - try { - final google = Google(); - final facebook = Facebook(); - await Future.wait([ - google.signOutGoogle(), - facebook.signOutFacebook(), - ]); - } catch (e) { - print('第三方登录退出错误: $e'); - // 继续执行,不中断退出流程 - } + // 显示加载提示 + Get.dialog( + const Center(child: CircularProgressIndicator()), + barrierDismissible: false, + ); + + // 调用账号服务执行登出 + final result = await _accountService.signOut(); // 关闭加载提示 - //Get.back(); + Get.back(); - // 确保导航到登录页面 - if (Get.currentRoute != Routes.login) { - await Get.offAllNamed(Routes.login); - } + if (result.success) { + // 先显示成功提示 + Get.snackbar('成功', result.message ?? '已退出登录'); + Logger().d('退出登录成功'); - Get.snackbar('成功', '已退出登录'); - print('退出登录成功'); + // 然后导航到登录页面 + if (Get.currentRoute != Routes.login) { + await Get.offAllNamed(Routes.login); + } + } else { + Get.snackbar('错误', result.errorMessage ?? '退出登录失败,请重试'); + Logger().e('退出登录失败: ${result.errorMessage}'); + } } catch (e) { - //Get.back(); // 关闭加载提示 + // 确保加载提示被关闭 + if (Get.isDialogOpen ?? false) { + Get.back(); + } Get.snackbar('错误', '退出登录失败,请重试'); - print('退出登录错误: $e'); + Logger().e('退出登录错误: $e'); } } @@ -413,24 +411,32 @@ class ProfileController extends GetxController { barrierDismissible: false, ); - // 删除后台用户数据 - final responce = await Api.deleteUser(); - if (responce == null) { - Get.back(); // 关闭加载提示 - Get.snackbar('错误', '删除用户数据失败'); - return; - } + // 调用账号服务执行账号删除 + final result = await _accountService.deleteAccount(); + // 关闭加载提示 Get.back(); - signOut(); // 调用 signOut 方法 + if (result.success) { + // 先显示成功提示 + Get.snackbar('成功', result.message ?? '账号已被永久删除'); + Logger().d('账号删除成功'); - Get.snackbar('Success', 'Account has been permanently deleted'); - Logger().d('账号删除成功'); + // 然后导航到登录页面 + if (Get.currentRoute != Routes.login) { + await Get.offAllNamed(Routes.login); + } + } else { + Get.snackbar('错误', result.errorMessage ?? '删除账号失败,请重试'); + Logger().e('删除账号失败: ${result.errorMessage}'); + } } catch (e) { - Get.back(); // 关闭加载提示 + // 确保加载提示被关闭 + if (Get.isDialogOpen ?? false) { + Get.back(); + } Get.snackbar('错误', '删除账号失败,请重试'); - Logger().d('账号删除失败: $e'); + Logger().e('删除账号失败: $e'); } } } diff --git a/lib/modules/translation/controllers/translation_list_controller.dart b/lib/modules/translation/controllers/translation_list_controller.dart index aa8f1a288..7b8d0a98e 100644 --- a/lib/modules/translation/controllers/translation_list_controller.dart +++ b/lib/modules/translation/controllers/translation_list_controller.dart @@ -1,18 +1,11 @@ -import 'dart:convert'; import 'dart:io'; - -import 'package:file_picker/file_picker.dart'; -import 'package:flutter/material.dart'; +import 'package:audio_waveforms/audio_waveforms.dart'; import 'package:flutter_easyloading/flutter_easyloading.dart'; import 'package:get/get.dart'; -import 'package:http/http.dart' as http; import 'package:just_audio/just_audio.dart'; import 'package:path_provider/path_provider.dart'; import 'package:permission_handler/permission_handler.dart'; - import '../../../core/utils/logger.dart'; -import '../../../core/utils/upload_oss.dart'; -import '../../../data/services/db/sqflite_api.dart'; import '../../../core/utils/permission_util.dart'; import 'package:share_plus/share_plus.dart'; @@ -28,14 +21,21 @@ class TranslationListController extends GetxController { final appDocDir = getApplicationDocumentsDirectory(); // final directory = Directory( // '/storage/emulated/0/Android/data/com.yunqiinnovation.deepsound/files'); - List files = []; - TextEditingController titleController = TextEditingController(); // 录音标题 + final RxList files = [].obs; RxList dataList = [].obs; List dataListTask = []; bool isDisposed = false; - + // 添加音频播放器 + // 添加音频时长缓存 + final Map audioDurations = {}; + // 播放状态 + final RxBool isPlaying = false.obs; + // 当前播放的文件路径 + final RxString currentPlayingPath = ''.obs; + final AudioPlayer audioPlayer = AudioPlayer(); + final PlayerController playerController = PlayerController(); @override void onInit() { super.onInit(); @@ -49,16 +49,47 @@ class TranslationListController extends GetxController { // _getDataList(); // addMeetingAudio(); loadTranslatAudio(); + // 监听播放状态变化 + audioPlayer.playerStateStream.listen((playerState) { + isPlaying.value = playerState.playing; + if (playerState.processingState == ProcessingState.completed) { + // 播放完成后重置状态 + currentPlayingPath.value = ''; + } + }); } @override void onClose() { - titleController.dispose(); - - isDisposed = true; + audioPlayer.dispose(); // 释放播放器资源 super.onClose(); } +// 播放/暂停音频 + void togglePlay(FileSystemEntity file) async { + final filePath = file.path; + + if (currentPlayingPath.value == filePath) { + // 同一个文件:切换播放/暂停 + if (isPlaying.value) { + await audioPlayer.pause(); + } else { + await audioPlayer.play(); + } + } else { + // 新文件:停止当前播放,开始新文件 + currentPlayingPath.value = filePath; + try { + await audioPlayer.stop(); + await audioPlayer.setFilePath(filePath); + await audioPlayer.play(); + } catch (e) { + print("播放失败: $e"); + currentPlayingPath.value = ''; + } + } + } + // 请求存储权限 void _requestStoragePermission() async { // final BuildContext context = Get.overlayContext!; @@ -81,16 +112,22 @@ class TranslationListController extends GetxController { }); } - void loadTranslatopus() async { - final appDir = await appDocDir; // 先 await 获取目录 - final dir = Directory("${appDir.path}/OpusAudio"); - - print("appDocDir=${dir?.path}"); + // 添加方法:获取音频时长 + Future getAudioDuration(String filePath) async { + if (audioDurations.containsKey(filePath)) { + return audioDurations[filePath]!; + } - files = dir - .listSync() - .where((f) => f is File && (f.path.endsWith('.opus'))) - .toList(); + try { + final audioPlayer = AudioPlayer(); + await audioPlayer.setFilePath(filePath); + final duration = await audioPlayer.duration ?? Duration.zero; + audioDurations[filePath] = duration; + return duration; + } catch (e) { + print("获取音频时长失败: $e"); + return Duration.zero; + } } void loadTranslatAudio() async { @@ -102,12 +139,12 @@ class TranslationListController extends GetxController { // Create directory if it doesn't exist if (!await dir.exists()) { await dir.create(recursive: true); // Create directory tree if needed - files = []; // No files exist in new directory + files.value = []; // No files exist in new directory return; } // Directory exis - files = dir + files.value = dir .listSync() .where((f) => f is File && @@ -120,7 +157,7 @@ class TranslationListController extends GetxController { .toList(); } catch (e) { print("Error loading files: $e"); - files = []; // Return empty list on error + files.value = []; // Return empty list on error } } // 导入音频文件 @@ -144,48 +181,4 @@ class TranslationListController extends GetxController { EasyLoading.showToast('分享失败: ${e.toString()}'); } } - - Future _saveFile(PlatformFile file) async { - final directory = await getApplicationDocumentsDirectory(); - final directoryDir = Directory('${directory.path}/ExternalAudio'); - if (!await directoryDir.exists()) { - await directoryDir.create(recursive: true); - } - final String filePath = '${directory.path}/ExternalAudio/${file.name}'; - final File localFile = File(filePath); - await localFile.writeAsBytes(file.bytes!); - return filePath; - } - - // 修改标题 - void editMeetingTitle(int id, String title) async { - if (title.isNotEmpty) { - SqfliteApi.editMeetingTitle(id, {'title': title}); - int index = dataList.indexWhere((item) => item['id'] == id); - if (index != -1) { - final dataMap = Map.from(dataList[index]); - dataMap['title'] = title; - dataList[index] = dataMap; - } - } - Get.back(); - } - - // 删除数据 - void deleteMeeting(int id, String filepath) async { - dataList.removeWhere((item) => item['id'] == id); - // 删除数据库中的记录 - SqfliteApi.deleteMeeting(id); - _deleteFile(filepath); - addQuantity--; - } - - // 删除文件 - Future _deleteFile(String filepath) async { - File file = File(filepath); - bool fileExists = await file.exists(); - if (fileExists) { - await file.delete(); - } - } } diff --git a/lib/modules/translation/views/translation_list_view.dart b/lib/modules/translation/views/translation_list_view.dart index 2941eda68..a24f56c01 100644 --- a/lib/modules/translation/views/translation_list_view.dart +++ b/lib/modules/translation/views/translation_list_view.dart @@ -1,12 +1,10 @@ import 'dart:io'; -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:haptic_feedback/haptic_feedback.dart'; import 'package:intl/intl.dart'; - import '../../../core/theme/app_colors.dart'; import '../../../routes/app_routes.dart'; import '../controllers/translation_list_controller.dart'; @@ -39,7 +37,7 @@ class TranslationListView extends GetView { onPressed: () => Get.back(), ), title: Text( - 'conferenceAssistant'.tr, // 商务会议助手 + "翻译录音文件", // style: TextStyle( fontSize: 17.sp, fontWeight: FontWeight.w600, @@ -52,7 +50,8 @@ class TranslationListView extends GetView { Widget _buildAudioList(BuildContext context, bool isDarkMode) { return Obx(() { - if (controller.files.isEmpty) { + final fileList = controller.files; // 不要 toList() + if (fileList.isEmpty) { return Center(child: Text('暂无音频文件')); } @@ -72,115 +71,168 @@ class TranslationListView extends GetView { final fileName = file.path.split('/').last; final fileStat = file.statSync(); // 获取文件信息 final modifiedTime = fileStat.modified; - - return GestureDetector( - onTap: () { - // TODO: 播放音频 - print("播放音频: $fileName"); - }, - onLongPressStart: (details) { - _onLongPressAudioItem(context, isDarkMode, details, file); - print("长按操作: $fileName"); - }, - child: Container( - padding: EdgeInsets.symmetric(horizontal: 15.w, vertical: 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: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - /// 文件名 - Text( - fileName, - style: TextStyle( - color: isDarkMode ? Colors.white : Colors.black87, - fontSize: 18.sp, - ), - overflow: TextOverflow.ellipsis, - ), - - /// 修改时间 & 大小 - Padding( - padding: EdgeInsets.symmetric(vertical: 5.w), - child: Row( - children: [ - Icon( - Icons.access_time, - size: 14.w, - color: isDarkMode ? Colors.grey[400] : Colors.grey[500], - ), - 4.horizontalSpace, - Text( - DateFormat('yyyy-MM-dd HH:mm').format(modifiedTime), - style: TextStyle( - color: isDarkMode ? Colors.grey[400] : Colors.grey[500], - fontSize: 12.sp, - ), - ), - 20.horizontalSpace, - Icon( - Icons.sd_storage, - size: 14.w, - color: isDarkMode ? Colors.grey[400] : Colors.grey[500], - ), - 4.horizontalSpace, - Text( - '${(fileStat.size / 1024).toStringAsFixed(1)} KB', - style: TextStyle( - color: isDarkMode ? Colors.grey[400] : Colors.grey[500], - fontSize: 12.sp, - ), - ), - ], - ), + // 添加 FutureBuilder 来异步获取音频时长 + return FutureBuilder( + future: controller.getAudioDuration(file.path), + builder: (context, snapshot) { + String durationText = "计算中..."; + if (snapshot.hasData) { + final duration = snapshot.data!; + if (duration.inSeconds > 0) { + durationText = + "${duration.inMinutes}:${(duration.inSeconds % 60).toString().padLeft(2, '0')}"; + } else { + durationText = "未知"; + } + } else if (snapshot.hasError) { + durationText = "错误"; + } + return GestureDetector( + onLongPressStart: (details) { + _onLongPressAudioItem(context, isDarkMode, details, file); + print("长按操作: $fileName"); + }, + onTap: () { + // 添加点击播放功能 + controller.togglePlay(file); + }, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 15.w, vertical: 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), + ), + ], ), - - /// 文件类型标签 - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Container( - padding: EdgeInsets.symmetric(horizontal: 5.w, vertical: 2.w), - margin: EdgeInsets.only(top: 5.w), - decoration: BoxDecoration( - color: isDarkMode ? Colors.grey[900] : Colors.grey[100], - borderRadius: BorderRadius.circular(4.w), - ), - child: Text( - file.path.split('.').last.toUpperCase(), - style: TextStyle( - color: isDarkMode ? Colors.grey[400] : Colors.grey[600], - fontSize: 10.sp, - ), + /// 文件名 + Text( + fileName, + style: TextStyle( + color: isDarkMode ? Colors.white : Colors.black87, + fontSize: 18.sp, ), + overflow: TextOverflow.ellipsis, ), + + /// 修改时间 & 大小 Padding( - padding: EdgeInsets.only(top: 5.w), - child: Text( - '点击播放', - style: TextStyle( - color: Colors.blue, - fontSize: 10.sp, - ), + padding: EdgeInsets.symmetric(vertical: 5.w), + child: Row( + children: [ + Icon( + Icons.access_time, + size: 14.w, + color: isDarkMode ? Colors.grey[400] : Colors.grey[500], + ), + 4.horizontalSpace, + Text( + DateFormat('yyyy-MM-dd HH:mm').format(modifiedTime), + style: TextStyle( + color: + isDarkMode ? Colors.grey[400] : Colors.grey[500], + fontSize: 12.sp, + ), + ), + 20.horizontalSpace, + Icon( + Icons.sd_storage, + size: 14.w, + color: isDarkMode ? Colors.grey[400] : Colors.grey[500], + ), + 4.horizontalSpace, + Text( + '${(fileStat.size / 1024).toStringAsFixed(1)} KB', + style: TextStyle( + color: + isDarkMode ? Colors.grey[400] : Colors.grey[500], + fontSize: 12.sp, + ), + ), + 20.horizontalSpace, + Icon(Icons.music_note, + size: 14.w, + color: + isDarkMode ? Colors.grey[400] : Colors.grey[500]), + 4.horizontalSpace, + Text( + durationText, // 使用计算出的时长文本 + style: TextStyle( + color: + isDarkMode ? Colors.grey[400] : Colors.grey[500], + fontSize: 12.sp, + ), + ), + ], ), ), + + /// 文件类型标签 + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Container( + padding: + EdgeInsets.symmetric(horizontal: 5.w, vertical: 2.w), + margin: EdgeInsets.only(top: 5.w), + decoration: BoxDecoration( + color: isDarkMode ? Colors.grey[900] : Colors.grey[100], + borderRadius: BorderRadius.circular(4.w), + ), + child: Text( + file.path.split('.').last.toUpperCase(), + style: TextStyle( + color: + isDarkMode ? Colors.grey[400] : Colors.grey[600], + fontSize: 10.sp, + ), + ), + ), + Obx(() { + final isCurrentFile = + controller.currentPlayingPath.value == file.path; + final isPlaying = controller.isPlaying.value; + + return Row( + children: [ + // 播放/暂停图标 + if (isCurrentFile) + Icon( + isPlaying ? Icons.pause : Icons.play_arrow, + size: 20.w, + color: Colors.blue, + ), + + // 播放状态文本 + Text( + isCurrentFile + ? (isPlaying ? '播放中...' : '已暂停') + : '点击播放', + style: TextStyle( + color: Colors.blue, + fontSize: 10.sp, + ), + ), + ], + ); + }), + ], + ), ], ), - ], - ), - ), + ), + ); + }, ); } @@ -237,6 +289,20 @@ class TranslationListView extends GetView { controller.loadTranslatAudio(); // 刷新列表 }, ), + _popupMenuEntry( + Icon( + Icons.delete_outline, + size: 20.w, + color: isDarkMode ? Colors.red[700] : Colors.red, + ), + '导入', + isDarkMode ? Colors.red[700]! : Colors.red, + () { + Get.toNamed(Routes.meeting, arguments: { + 'filePath': file.path, + }); + }, + ), ], ); }