From bd9dac631c281582f935e0de2f4922b9546abb1d Mon Sep 17 00:00:00 2001 From: lxm <1274844347@qq.com> Date: Fri, 9 Jan 2026 17:11:20 +0800 Subject: [PATCH] =?UTF-8?q?feat(meeting):=20=E6=B7=BB=E5=8A=A0=E4=BC=9A?= =?UTF-8?q?=E8=AE=AE=E5=BD=95=E9=9F=B3=E9=80=80=E5=87=BA=E7=A1=AE=E8=AE=A4?= =?UTF-8?q?=E5=AF=B9=E8=AF=9D=E6=A1=86=E5=8F=8A=E5=90=8E=E5=8F=B0=E5=BD=95?= =?UTF-8?q?=E9=9F=B3=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 实现会议录音页面的退出确认对话框,提供保存并退出、不保存退出和后台继续录音选项 修改控制器逻辑以支持后台录音状态和不同的退出行为 添加多语言支持用于退出对话框的文本显示 禁用页面返回手势以避免误操作 --- lib/core/translations/language/en_us.dart | 6 + lib/core/translations/language/zh_cn.dart | 5 + .../bindings/meeting_record_binding.dart | 4 +- .../meeting_record_controller.dart | 29 ++- .../navigation_bar_bottom_sheet.dart | 18 +- .../meeting/views/meeting_record_view.dart | 223 +++++++++++++++++- lib/routes/app_pages.dart | 1 + 7 files changed, 272 insertions(+), 14 deletions(-) diff --git a/lib/core/translations/language/en_us.dart b/lib/core/translations/language/en_us.dart index 2e340c301..f9fef5ba7 100644 --- a/lib/core/translations/language/en_us.dart +++ b/lib/core/translations/language/en_us.dart @@ -437,6 +437,12 @@ const Map enUS = { 'audioVideoRecording': 'Audio/Video Recording', 'callRecording': 'Call Recording', 'importAudio': 'Import Audio', + 'meetingRecordExitTitle': 'Exit Recording', // 退出录音 + 'meetingRecordExitMessage': 'Choose what to do before leaving', // 请选择离开后的操作 + 'meetingRecordEndAndSave': 'End recording and save', // 结束录音并保存 + 'meetingRecordEndWithoutSave': 'End recording without saving', // 结束录音不保存 + 'meetingRecordContinueInBackground': + 'Continue recording in background', // 后台继续录音 'transcribing': 'Transcribing...', 'summarizing': 'Summarizing...', 'analyzing': 'Analyzing...', diff --git a/lib/core/translations/language/zh_cn.dart b/lib/core/translations/language/zh_cn.dart index aaeecfba8..afb01b8fa 100644 --- a/lib/core/translations/language/zh_cn.dart +++ b/lib/core/translations/language/zh_cn.dart @@ -414,6 +414,11 @@ const Map zhCN = { 'audioVideoRecording': '音、视频录音', 'callRecording': '通话录音', 'importAudio': '导入音频', + 'meetingRecordExitTitle': '退出录音', // 退出录音 + 'meetingRecordExitMessage': '请选择离开后的操作', // 请选择离开后的操作 + 'meetingRecordEndAndSave': '结束录音并保存', // 结束录音并保存 + 'meetingRecordEndWithoutSave': '结束录音不保存', // 结束录音不保存 + 'meetingRecordContinueInBackground': '后台继续录音', // 后台继续录音 'transcribing': '正在转写...', 'summarizing': '正在总结...', 'analyzing': '正在分析...', diff --git a/lib/modules/meeting/bindings/meeting_record_binding.dart b/lib/modules/meeting/bindings/meeting_record_binding.dart index f4880d510..e6f60e6ca 100644 --- a/lib/modules/meeting/bindings/meeting_record_binding.dart +++ b/lib/modules/meeting/bindings/meeting_record_binding.dart @@ -5,7 +5,7 @@ import '../controllers/meeting_record_controller.dart'; class MeetingRecordBinding extends Bindings { @override void dependencies() { - // 延迟注册 MeetingRecordController 控制器 - Get.lazyPut(() => MeetingRecordController()); + Get.put(MeetingRecordController(), + permanent: true); } } diff --git a/lib/modules/meeting/controllers/meeting_record_controller.dart b/lib/modules/meeting/controllers/meeting_record_controller.dart index e3f867cac..ffcf6740c 100644 --- a/lib/modules/meeting/controllers/meeting_record_controller.dart +++ b/lib/modules/meeting/controllers/meeting_record_controller.dart @@ -84,6 +84,14 @@ class MeetingRecordController extends GetxController super.onClose(); } + void applyRouteArgumentsIfIdle() { + if (isEditingTitle.value) return; + if (isRecording.value) return; + if (timerStatus.value != TimerStatus.stopped) return; + _setupFromArguments(); + titleEditingController.text = newName.value; + } + // 请求录音权限 Future _requestRecordPermission() async { try { @@ -147,7 +155,9 @@ class MeetingRecordController extends GetxController if (audioType.value != 0) { _bleManager.closeCodec(); } - _asrService.stopRecord(true); + if (isRecording.value) { + _asrService.stopRecord(true); + } isRecording.value = false; rms.value = 0.0; _smoothedRms = 0.0; @@ -408,6 +418,23 @@ class MeetingRecordController extends GetxController _resetWaveform(); } + Future discardRecording() async { + resetTimer(); + _stopWaveformTimer(); + if (audioType.value != 0) { + _bleManager.closeCodec(); + } + if (isRecording.value) { + await _asrService.stopRecord(false); + } + _resetToDefaultState(); + isRecording.value = false; + rms.value = 0.0; + _smoothedRms = 0.0; + _latestRms = 0.0; + _resetWaveform(); + } + /// 将控制器重置到初始状态 void _resetToDefaultState() { switch (audioType.value) { diff --git a/lib/modules/meeting/views/bottomSheet/navigation_bar_bottom_sheet.dart b/lib/modules/meeting/views/bottomSheet/navigation_bar_bottom_sheet.dart index 6700edd92..e41e7d0aa 100644 --- a/lib/modules/meeting/views/bottomSheet/navigation_bar_bottom_sheet.dart +++ b/lib/modules/meeting/views/bottomSheet/navigation_bar_bottom_sheet.dart @@ -30,10 +30,12 @@ class NavigationBarBottomSheet extends GetView { Colors.red, onTap: () async { Get.back(); - await Get.toNamed(Routes.meetingRecord, arguments: { + final result = await Get.toNamed(Routes.meetingRecord, arguments: { 'audioTypes': 0, }); - controller.addMeetingAudio(); + if (result != 'meeting_record_background') { + controller.addMeetingAudio(); + } }, ), if (controller.meetingController.type == 1) @@ -45,10 +47,12 @@ class NavigationBarBottomSheet extends GetView { onTap: controller.bleManager.isConnected ? () async { Get.back(); - await Get.toNamed(Routes.meetingRecord, arguments: { + final result = await Get.toNamed(Routes.meetingRecord, arguments: { 'audioTypes': 1, }); - controller.addMeetingAudio(); + if (result != 'meeting_record_background') { + controller.addMeetingAudio(); + } } : null, ), @@ -61,10 +65,12 @@ class NavigationBarBottomSheet extends GetView { onTap: controller.bleManager.isConnected ? () async { Get.back(); - await Get.toNamed(Routes.meetingRecord, arguments: { + final result = await Get.toNamed(Routes.meetingRecord, arguments: { 'audioTypes': 2, }); - controller.addMeetingAudio(); + if (result != 'meeting_record_background') { + controller.addMeetingAudio(); + } } : null, ), diff --git a/lib/modules/meeting/views/meeting_record_view.dart b/lib/modules/meeting/views/meeting_record_view.dart index 356fb7565..9c2a74634 100644 --- a/lib/modules/meeting/views/meeting_record_view.dart +++ b/lib/modules/meeting/views/meeting_record_view.dart @@ -6,18 +6,231 @@ import 'package:get/get.dart'; import '../controllers/meeting_record_controller.dart'; import '../../../core/theme/app_colors.dart'; +enum _MeetingRecordExitAction { + saveAndEnd, + endWithoutSave, + continueInBackground, + cancel, +} + class MeetingRecordView extends GetView { const MeetingRecordView({super.key}); + Future<_MeetingRecordExitAction?> _showExitConfirmDialog(bool isDarkMode) { + return Get.dialog<_MeetingRecordExitAction>( + Dialog( + insetPadding: EdgeInsets.symmetric(horizontal: 24.w, vertical: 24.h), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16.r), + ), + backgroundColor: isDarkMode ? AppColors.cardBackground : Colors.white, + child: Container( + width: 320.w, + padding: EdgeInsets.all(20.w), + decoration: BoxDecoration( + color: isDarkMode ? AppColors.cardBackground : Colors.white, + borderRadius: BorderRadius.circular(16.r), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 56.w, + height: 56.w, + decoration: BoxDecoration( + color: (isDarkMode ? Colors.white : Colors.blue) + .withValues(alpha: 0.12), + borderRadius: BorderRadius.circular(28.r), + ), + child: Icon( + Icons.mic_rounded, + size: 28.sp, + color: isDarkMode ? Colors.white : Colors.blue, + ), + ), + SizedBox(height: 14.h), + Text( + 'meetingRecordExitTitle'.tr, // 对应中文:退出录音 + style: TextStyle( + fontSize: 18.sp, + fontWeight: FontWeight.bold, + color: isDarkMode ? Colors.white : Colors.black87, + ), + ), + SizedBox(height: 8.h), + Text( + 'meetingRecordExitMessage'.tr, // 对应中文:请选择离开后的操作 + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 14.sp, + height: 1.4, + color: isDarkMode ? Colors.white70 : Colors.black54, + ), + ), + SizedBox(height: 18.h), + SizedBox( + width: double.infinity, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + ElevatedButton( + onPressed: () => Get.back( + result: _MeetingRecordExitAction.saveAndEnd, + ), + style: ElevatedButton.styleFrom( + backgroundColor: + isDarkMode ? AppColors.primary : Colors.blue, + foregroundColor: Colors.white, + padding: EdgeInsets.symmetric(vertical: 12.h), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12.r), + ), + elevation: 0, + ), + child: Text( + 'meetingRecordEndAndSave'.tr, // 对应中文:结束录音并保存 + style: TextStyle( + fontSize: 14.sp, + fontWeight: FontWeight.w600, + ), + ), + ), + SizedBox(height: 10.h), + OutlinedButton( + onPressed: () => Get.back( + result: _MeetingRecordExitAction.continueInBackground, + ), + style: OutlinedButton.styleFrom( + foregroundColor: + isDarkMode ? Colors.white70 : Colors.grey[800], + side: BorderSide( + color: + isDarkMode ? Colors.white30 : Colors.grey[400]!, + ), + padding: EdgeInsets.symmetric(vertical: 12.h), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12.r), + ), + ), + child: Text( + 'meetingRecordContinueInBackground'.tr, // 对应中文:后台继续录音 + style: TextStyle( + fontSize: 14.sp, + fontWeight: FontWeight.w500, + ), + ), + ), + SizedBox(height: 10.h), + OutlinedButton( + onPressed: () => Get.back( + result: _MeetingRecordExitAction.endWithoutSave, + ), + style: OutlinedButton.styleFrom( + foregroundColor: + isDarkMode ? Colors.red[300] : Colors.red[600], + side: BorderSide( + color: + isDarkMode ? Colors.red[300]! : Colors.red[300]!, + ), + padding: EdgeInsets.symmetric(vertical: 12.h), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12.r), + ), + ), + child: Text( + 'meetingRecordEndWithoutSave'.tr, // 对应中文:结束录音不保存 + style: TextStyle( + fontSize: 14.sp, + fontWeight: FontWeight.w500, + ), + ), + ), + SizedBox(height: 10.h), + TextButton( + onPressed: () => + Get.back(result: _MeetingRecordExitAction.cancel), + style: TextButton.styleFrom( + padding: EdgeInsets.symmetric(vertical: 12.h), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12.r), + side: BorderSide( + color: + isDarkMode ? Colors.white24 : Colors.grey[300]!, + ), + ), + ), + child: Text( + 'cancel'.tr, // 对应中文:取消 + style: TextStyle( + fontSize: 14.sp, + fontWeight: FontWeight.w500, + color: isDarkMode ? Colors.white70 : Colors.grey[700], + ), + ), + ), + ], + ), + ), + ], + ), + ), + ), + barrierDismissible: true, + ); + } + @override Widget build(BuildContext context) { final isDarkMode = Theme.of(context).brightness == Brightness.dark; + controller.applyRouteArgumentsIfIdle(); + + return PopScope( + canPop: false, + onPopInvokedWithResult: (didPop, result) async { + if (didPop) return; + + if (!controller.isRecording.value) { + Get.back(); + Future.microtask(() { + if (Get.isRegistered()) { + Get.delete(force: true); + } + }); + return; + } - return WillPopScope( - onWillPop: () async { - await controller.saveTitleChange(); - await controller.saveRecording(); - return true; + final action = await _showExitConfirmDialog(isDarkMode); + if (action == null || action == _MeetingRecordExitAction.cancel) { + return; + } + + switch (action) { + case _MeetingRecordExitAction.saveAndEnd: + await controller.saveTitleChange(); + await controller.saveRecording(); + Get.back(result: 'meeting_record_saved'); + Future.microtask(() { + if (Get.isRegistered()) { + Get.delete(force: true); + } + }); + return; + case _MeetingRecordExitAction.endWithoutSave: + await controller.discardRecording(); + Get.back(result: 'meeting_record_discarded'); + Future.microtask(() { + if (Get.isRegistered()) { + Get.delete(force: true); + } + }); + return; + case _MeetingRecordExitAction.continueInBackground: + await controller.saveTitleChange(); + Get.back(result: 'meeting_record_background'); + return; + case _MeetingRecordExitAction.cancel: + return; + } }, child: Scaffold( appBar: AppBar( diff --git a/lib/routes/app_pages.dart b/lib/routes/app_pages.dart index c9b6ec60c..de6142ab3 100644 --- a/lib/routes/app_pages.dart +++ b/lib/routes/app_pages.dart @@ -237,6 +237,7 @@ abstract class AppPages { name: Routes.meetingRecord, page: () => const MeetingRecordView(), binding: MeetingRecordBinding(), + popGesture: false, transition: Transition.rightToLeft, transitionDuration: Duration(milliseconds: 250), ),