import 'dart:async'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import '../controllers/translation_controller.dart'; import '../views/translation_history_view.dart'; import '../bindings/translation_history_binding.dart'; import '../../../core/theme/app_colors.dart'; import '../../../routes/app_routes.dart'; // import 'package:intl/intl.dart'; class TranslationView extends GetView { const TranslationView({Key? key}) : super(key: key); @override Widget build(BuildContext context) { final isDarkMode = Theme.of(context).brightness == Brightness.dark; return Scaffold( backgroundColor: isDarkMode ? AppColors.darkBackground : Colors.grey[50], appBar: _buildAppBar(isDarkMode), body: _buildBody(isDarkMode), ); } PreferredSizeWidget _buildAppBar(bool isDarkMode) { // 在同一个类中添加辅助方法 PopupMenuItem _buildPopupMenuItem(String value, String text, {bool enabled = true // 默认启用 }) { return PopupMenuItem( value: value, enabled: enabled, // 设置启用状态 child: Text( text, style: TextStyle( color: enabled ? null // 使用默认颜色 : Colors.grey, // 禁用时显示灰色 ), ), ); } return AppBar( backgroundColor: isDarkMode ? AppColors.cardBackground : Colors.white, elevation: 1, leading: IconButton( icon: Icon( Icons.arrow_back_ios, color: isDarkMode ? Colors.white : Colors.black, size: 20.sp, ), onPressed: () { // Stop recognition and TTS before navigating back controller.stopAll(); Get.back(); }, ), title: Obx(() => PopupMenuButton( onSelected: (value) { controller.changeTranslationMode(value); }, color: isDarkMode ? AppColors.cardBackground : Colors.white, itemBuilder: (BuildContext context) => [ _buildPopupMenuItem('simultaneous', 'simultaneousTranslation'.tr), _buildPopupMenuItem('faceToFace', 'faceToFaceTranslation'.tr), _buildPopupMenuItem( 'audioVideo', 'audioVideoTranslation'.tr, enabled: controller.bleManager.isConnected, // 根据连接状态启用/禁用 ), _buildPopupMenuItem( 'call', 'callTranslation'.tr, enabled: controller.bleManager.isConnected, // 根据连接状态启用/禁用 ), ], child: Row( mainAxisSize: MainAxisSize.min, // 关键修改:让宽度自适应内容 mainAxisAlignment: MainAxisAlignment.center, children: [ Flexible( // 防止文本溢出 child: FittedBox( // 保持文本缩放比例 fit: BoxFit.scaleDown, child: Text( controller.currentModeTitle.value.tr, style: TextStyle( fontSize: 18.sp, fontWeight: FontWeight.w600, // 保持字体加粗 color: isDarkMode ? Colors.white : Colors.black, ), overflow: TextOverflow.ellipsis, // 长文本显示省略号 maxLines: 1, ), ), ), SizedBox(width: 4), // 添加间距 Icon( Icons.arrow_drop_down, color: isDarkMode ? Colors.white : Colors.black, size: 20.sp, ), ], ), )), centerTitle: true, actions: [ Obx(() { if (controller.isRecording.value) { // 录音中:显示红色计时器样式,点击可停止录音 return Padding( padding: EdgeInsets.only(right: 16.w), child: GestureDetector( onTap: controller.toggleisRecord, // 点击结束录音 child: Container( padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h), decoration: BoxDecoration( color: Colors.red, borderRadius: BorderRadius.circular(20.r), ), child: Row( children: [ Icon( Icons.fiber_manual_record, color: Colors.white, size: 18.sp, ), SizedBox(width: 4.w), Text( controller.recordDurationText.value, style: TextStyle( color: Colors.white, fontSize: 14.sp, fontWeight: FontWeight.bold, ), ), ], ), ), ), ); } else { // 未录音:显示红色录音按钮 + 文件夹按钮 return Row( children: [ IconButton( icon: Icon( Icons.radio_button_checked, color: Colors.red, size: 24.sp, ), onPressed: controller.toggleisRecord, tooltip: 'enableRecording'.tr, // 是否开启录音 ), IconButton( icon: Icon( Icons.folder, color: isDarkMode ? Colors.white : Colors.black, size: 24.sp, ), onPressed: () async { await controller.stopAll(); Get.toNamed(Routes.translationList); controller.setupScrollListener(); }, tooltip: 'viewAudioFiles'.tr, // 查看录音文件 ), // 添加历史记录按钮 IconButton( icon: Icon( Icons.history, color: isDarkMode ? Colors.white : Colors.black, size: 24.sp, ), onPressed: () async { await controller.stopAll(); Get.toNamed(Routes.translationHistory); }, tooltip: 'translationHistory'.tr, // 翻译历史 ), ], ); } }), ], bottom: PreferredSize( preferredSize: Size.fromHeight(50.h), child: Padding( padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.h), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ // 源语言选择 Expanded( child: GestureDetector( onTap: () => _showLanguageSelectionDialog(true, isDarkMode), child: Container( padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h), decoration: BoxDecoration( color: isDarkMode ? Colors.white.withOpacity(0.1) : Colors.grey[100], borderRadius: BorderRadius.circular(16.r), ), child: Obx(() => Text( '${controller.sourceLanguage.value.split(' ')[0]}' .tr, // Just show the Chinese part style: TextStyle( fontSize: 16.sp, fontWeight: FontWeight.w600, color: isDarkMode ? Colors.white : Colors.black, ), textAlign: TextAlign.center, overflow: TextOverflow.ellipsis, )), ), ), ), // 语言互换按钮 GestureDetector( onTap: controller.swapLanguages, child: Container( margin: EdgeInsets.symmetric(horizontal: 8.w), padding: EdgeInsets.all(4.r), decoration: BoxDecoration( color: isDarkMode ? AppColors.primary.withOpacity(0.2) : Colors.blue[50], shape: BoxShape.circle, ), child: Icon( Icons.swap_horiz, color: isDarkMode ? AppColors.primary : Colors.blue, size: 20.sp, ), ), ), // 目标语言选择 Expanded( child: GestureDetector( onTap: () => _showLanguageSelectionDialog(false, isDarkMode), child: Container( padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h), decoration: BoxDecoration( color: isDarkMode ? Colors.white.withOpacity(0.1) : Colors.grey[100], borderRadius: BorderRadius.circular(16.r), ), child: Obx(() => Text( '${controller.targetLanguage.value.split(' ')[0]}' .tr, // Just show the English part style: TextStyle( fontSize: 16.sp, fontWeight: FontWeight.w600, color: isDarkMode ? Colors.white : Colors.black, ), textAlign: TextAlign.center, overflow: TextOverflow.ellipsis, )), ), ), ), ], ), ), ), ); } Widget _buildBody(bool isDarkMode) { return Column( children: [ Expanded( child: _buildTranslationContent(isDarkMode), ), _buildControlBar(isDarkMode), ], ); } Widget _buildTranslationContent(bool isDarkMode) { return Obx(() { final itemCount = controller.translationHistory.length; return ListView.builder( controller: controller.scrollController, padding: EdgeInsets.only( left: 16.w, right: 16.w, top: 16.h, bottom: 16.h, ), itemCount: itemCount, itemBuilder: (context, index) { // 获取当前项 final historyItem = controller.translationHistory[index]; // 构建项目widget return _buildTranslationItemWidget(context, historyItem); }, ); }); } Widget _buildTranslationItemWidget( BuildContext context, TranslationItem item) { return Container( margin: const EdgeInsets.only(bottom: 8), padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: item.isIntermediate ? Colors.grey.withOpacity(0.1) // 中间结果使用浅灰色底色 : (item.sourceLanguageCode == controller.sourceLanguageCode.value ? Theme.of(context) .colorScheme .primary .withOpacity(0.08) // 源语言匹配时使用主题色底色 : Theme.of(context) .colorScheme .secondary .withOpacity(0.08)), // 否则使用次要色底色 borderRadius: BorderRadius.circular(12), border: Border.all( color: item.isIntermediate ? Colors.grey.withOpacity(0.2) : Colors.transparent, width: 1, ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 源文本 Text( item.sourceText, style: TextStyle( fontSize: 16, color: Theme.of(context).textTheme.bodyLarge?.color, ), ), if (item.translatedText.isNotEmpty) ...[ const SizedBox(height: 8), // 翻译文本 Text( item.translatedText, style: TextStyle( fontSize: 16, color: Theme.of(context).textTheme.bodyMedium?.color, fontWeight: FontWeight.w500, ), ), ], // 语言信息和时间戳 const SizedBox(height: 4), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( '${item.sourceLanguageName.tr} → ${item.targetLanguageName.tr}', style: TextStyle( fontSize: 12, color: Theme.of(context).textTheme.bodySmall?.color, ), ), if (!item.isIntermediate) GestureDetector( onTap: () => controller.playTranslationItem(item), child: Icon( Icons.volume_up, size: 20, color: Theme.of(context).colorScheme.primary, ), ), ], ), ], ), ); } Widget _buildControlBar(bool isDarkMode) { // 使用GlobalKey来获取控制栏的高度 final controlBarKey = GlobalKey(); // 在构建完成后获取控制栏的高度并设置到控制器中 WidgetsBinding.instance.addPostFrameCallback((_) { if (controlBarKey.currentContext != null) { final RenderBox renderBox = controlBarKey.currentContext!.findRenderObject() as RenderBox; final height = renderBox.size.height; controller.setBottomBarHeight(height); } }); return Container( key: controlBarKey, padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 16.h), decoration: BoxDecoration( color: isDarkMode ? AppColors.cardBackground : Colors.white, boxShadow: [ BoxShadow( color: Colors.black.withAlpha(isDarkMode ? 30 : 13), blurRadius: 4, offset: const Offset(0, -2), ), ], ), child: Obx(() { // 同时模式显示双按钮 if (controller.currentMode.value == 'faceToFace') { controller.startRecognition(); return Row( mainAxisAlignment: MainAxisAlignment.center, children: [ // 耳机图标按钮 (说话者1) _buildHoldToTalkButton( isDarkMode: isDarkMode, speakerId: 1, activeSpeaker: controller.activeSpeaker.value, icon: Icons.headset, // 耳机图标 label: "headsetUser".tr, // 耳机用户 ), SizedBox(width: 40.w), // 手机图标按钮 (说话者2) _buildHoldToTalkButton( isDarkMode: isDarkMode, speakerId: 2, activeSpeaker: controller.activeSpeaker.value, icon: Icons.phone_android, // 手机图标 label: "phoneUser".tr, // 手机用户 ), ], ); } // 普通模式显示单按钮 else { return Row( mainAxisAlignment: MainAxisAlignment.center, children: [ // 主要控制按钮 - 开始/暂停语音识别 Obx(() { final isRecognizing = controller.isRecognizing.value; return GestureDetector( onTap: () { if (isRecognizing) { controller.stopAll(); } else { controller.startRecognition(); } }, child: Container( width: 60.w, height: 60.w, decoration: BoxDecoration( color: isRecognizing ? (isDarkMode ? Colors.red[700] : Colors.red) : (isDarkMode ? AppColors.primary : Colors.blue), shape: BoxShape.circle, ), child: Icon( isRecognizing ? Icons.pause : Icons.play_arrow, color: Colors.white, size: 30.sp, ), ), ); }), ], ); } }), ); } // // 按住说话按钮组件 // Widget _buildHoldToTalkButton({ // required bool isDarkMode, // required int speakerId, // required int activeSpeaker, // required IconData icon, // required String label, // }) { // bool isActive = activeSpeaker == speakerId; // return Listener( // onPointerDown: (_) { // if (activeSpeaker == 0) { // // 没有激活的按钮时才能激活 // if (speakerId == 1) { // //耳机收音 // controller.restoreOriginalAudioState(); // //controller.setActiveSpeaker(speakerId); // } else { // controller.disableBluetoothAudio(); // } // controller.setActiveSpeaker(speakerId); // //controller.startRecognition(); // } // }, // onPointerUp: (_) { // if (isActive) { // //controller.stopRecognition(); // controller.setActiveSpeaker(0); // } // }, // child: Container( // width: 60.w, // height: 60.w, // decoration: BoxDecoration( // color: isActive // ? (isDarkMode ? Colors.green[700] : Colors.green) // 激活状态颜色 // : (isDarkMode ? AppColors.primary : Colors.blue), // shape: BoxShape.circle, // ), // child: Icon( // Icons.mic, // 麦克风图标 // color: isActive ? Colors.white : Colors.white.withOpacity(0.7), // size: 30.sp, // ), // ), // ); // } // 按住说话按钮组件 Widget _buildHoldToTalkButton({ required bool isDarkMode, required int speakerId, required int activeSpeaker, required IconData icon, required String label, }) { bool isActive = activeSpeaker == speakerId; bool isDisabled = activeSpeaker != 0 && activeSpeaker != speakerId; Timer? _holdTimer; // 增加延迟触发的计时器 return Listener( onPointerDown: (_) { // 设置300ms延迟防止误触 _holdTimer = Timer(const Duration(milliseconds: 200), () { if (activeSpeaker == 0) { if (speakerId == 1) { controller.restoreOriginalAudioState(); } else { controller.disableBluetoothAudio(); } controller.setActiveSpeaker(speakerId); } }); }, onPointerUp: (_) { _holdTimer?.cancel(); if (isActive) { controller.setActiveSpeaker(0); } }, onPointerCancel: (_) { _holdTimer?.cancel(); if (isActive) { controller.setActiveSpeaker(0); } }, child: AnimatedContainer( duration: const Duration(milliseconds: 200), width: 80.w, height: 80.w, decoration: BoxDecoration( color: isActive ? (isDarkMode ? Colors.green[700] : Colors.green) : (isDisabled ? (isDarkMode ? Colors.grey[800] : Colors.grey[300]) : (isDarkMode ? AppColors.primary : Colors.blue)), shape: BoxShape.circle, boxShadow: isActive ? [ BoxShadow( color: Colors.green.withOpacity(0.5), blurRadius: 10, spreadRadius: 3, ) ] : null, ), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Stack( alignment: Alignment.center, children: [ // 设备类型图标(叠加在麦克风上) Icon( icon, color: isActive ? Colors.white : Colors.white.withOpacity(isDisabled ? 0.3 : 0.9), size: 24.sp, ), ], ), SizedBox(height: 4.h), Text( label, style: TextStyle( fontSize: 12.sp, color: isActive ? Colors.white : Colors.white.withOpacity(isDisabled ? 0.3 : 0.9), ), ), ], ), ), ); } // 显示语言选择对话框 void _showLanguageSelectionDialog(bool isSource, 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( isSource ? 'selectSourceLanguage'.tr // 选择源语言 : 'selectTargetLanguage'.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.supportedLanguages.keys.map((language) { return ListTile( title: Text( '${language}'.tr, style: TextStyle( color: isDarkMode ? Colors.white : Colors.black, ), ), trailing: Obx(() { final currentLanguage = isSource ? controller.sourceLanguage.value : controller.targetLanguage.value; return currentLanguage == language ? Icon( Icons.check_circle, color: isDarkMode ? AppColors.primary : Colors.blue, ) : const SizedBox.shrink(); }), onTap: () { if (isSource) { controller.setSourceLanguage(language); } else { controller.setTargetLanguage(language); } Get.back(); }, ); }).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, ), ), ), ), ), ], ), ), ), ); } }