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 '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) { return AppBar( backgroundColor: isDarkMode ? AppColors.cardBackground : Colors.white, elevation: 0, 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: Row( mainAxisSize: MainAxisSize.min, children: [ // 源语言选择 Flexible( 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], // Just show the Chinese part style: TextStyle( fontSize: 16.sp, fontWeight: FontWeight.w600, color: isDarkMode ? Colors.white : Colors.black, ), 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, ), ), ), // 目标语言选择 Flexible( 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], // Just show the English part style: TextStyle( fontSize: 16.sp, fontWeight: FontWeight.w600, color: isDarkMode ? Colors.white : Colors.black, ), overflow: TextOverflow.ellipsis, )), ), ), ), ], ), centerTitle: true, actions: [ // TTS toggle button Obx(() => IconButton( icon: Icon( controller.isTtsEnabled.value ? Icons.volume_up : Icons.volume_off, color: isDarkMode ? Colors.white : Colors.black, size: 24.sp, ), onPressed: controller.toggleTts, tooltip: '自动播放翻译结果', )), // 历史记录按钮 IconButton( icon: Icon( Icons.history, color: isDarkMode ? Colors.white : Colors.black, size: 24.sp, ), onPressed: () async { // 停止语音识别和TTS,释放资源 await controller.stopAll(); await Get.to( () => const TranslationHistoryView(), binding: TranslationHistoryBinding(), ); // 返回时不自动开始翻译,只需重新设置滚动监听器 controller.setupScrollListener(); }, tooltip: '查看历史记录', ), ], ); } Widget _buildBody(bool isDarkMode) { return Column( children: [ Expanded( child: _buildTranslationContent(isDarkMode), ), _buildControlBar(isDarkMode), ], ); } Widget _buildTranslationContent(bool isDarkMode) { return Obx(() { final itemCount = controller.translationHistory.length; // 使用 currentItem 来判断是否显示当前项 final hasCurrentItem = controller.currentItem.value != null && controller .currentItem.value!.sourceText.isNotEmpty; // 只有当有实际内容时才显示当前项 // 计算底部内边距,确保内容不会被底部框遮住 final bottomPadding = controller.bottomBarHeight + 20.h; // 使用控制栏高度加上额外间距 return ListView.builder( controller: controller.scrollController, padding: EdgeInsets.only( left: 16.w, right: 16.w, top: 16.h, bottom: bottomPadding), itemCount: itemCount + (hasCurrentItem ? 1 : 0), itemBuilder: (context, index) { // Current item (being translated) if (hasCurrentItem && index == itemCount) { return _buildTranslationItemWidget( controller.currentItem.value!.sourceText, controller.currentItem.value!.translatedText, true, isIntermediate: controller.currentItem.value!.isIntermediate, isDarkMode: isDarkMode, ); } // History items final historyItem = controller.translationHistory[index]; return _buildTranslationItemWidget( historyItem.sourceText, historyItem.translatedText, false, item: historyItem, isIntermediate: false, // 历史项总是显示为最终结果 isDarkMode: isDarkMode, ); }, ); }); } Widget _buildTranslationItemWidget( String sourceText, String translatedText, bool isCurrent, { TranslationItem? item, bool isIntermediate = false, required bool isDarkMode, }) { // 使用AnimatedContainer实现平滑过渡,但恢复原来的视觉样式 return AnimatedContainer( duration: const Duration(milliseconds: 300), curve: Curves.easeInOut, margin: EdgeInsets.only(bottom: 24.h), decoration: BoxDecoration( color: isDarkMode ? AppColors.cardBackground : Colors.grey[100], borderRadius: BorderRadius.circular(12.r), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Source text AnimatedContainer( duration: const Duration(milliseconds: 300), width: double.infinity, padding: EdgeInsets.all(16.r), child: Text( sourceText.isEmpty && isCurrent ? '等待语音输入...' : sourceText, style: TextStyle( fontSize: 16.sp, color: sourceText.isEmpty && isCurrent ? (isDarkMode ? Colors.grey[400] : Colors.grey) : (isDarkMode ? Colors.white : Colors.black), fontWeight: FontWeight.normal, ), ), ), // Divider Divider( height: 1, thickness: 1, color: isDarkMode ? Colors.grey[800] : Colors.grey[200]), // Translated text AnimatedContainer( duration: const Duration(milliseconds: 300), width: double.infinity, padding: EdgeInsets.all(16.r), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( child: Text( translatedText.isEmpty && isCurrent ? '翻译结果将显示在这里...' : translatedText, style: TextStyle( fontSize: 16.sp, color: translatedText.isEmpty && isCurrent ? (isDarkMode ? Colors.grey[400] : Colors.grey) : (isDarkMode ? Colors.white : Colors.black), fontStyle: FontStyle.normal, ), ), ), if (translatedText.isNotEmpty) IconButton( icon: Icon( Icons.volume_up, color: isDarkMode ? AppColors.primary : Colors.blue, size: 20.sp, ), onPressed: () { if (isCurrent) { controller.playTranslatedText(translatedText); } else if (item != null) { controller.playTranslationItem(item); } }, ), ], ), ), ], ), ); } 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: 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, ), ), ); }), ], ), ); } // 显示语言选择对话框 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, 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, ), ), ), ), ), ], ), ), ), ); } }