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 'package:intl/intl.dart'; class TranslationView extends GetView { const TranslationView({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: _buildAppBar(), body: _buildBody(), ); } PreferredSizeWidget _buildAppBar() { return AppBar( backgroundColor: Colors.white, elevation: 0, leading: IconButton( icon: Icon( Icons.arrow_back_ios, color: Colors.black, size: 20.sp, ), onPressed: () { // Stop recognition and TTS before navigating back controller.stopAll(); Get.back(); }, ), title: Row( mainAxisSize: MainAxisSize.min, children: [ // 源语言选择 GestureDetector( onTap: () => _showLanguageSelectionDialog(true), child: Container( padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h), decoration: BoxDecoration( color: 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: Colors.black, ), )), ), ), // 语言互换按钮 GestureDetector( onTap: controller.swapLanguages, child: Container( margin: EdgeInsets.symmetric(horizontal: 8.w), padding: EdgeInsets.all(4.r), decoration: BoxDecoration( color: Colors.blue[50], shape: BoxShape.circle, ), child: Icon( Icons.swap_horiz, color: Colors.blue, size: 20.sp, ), ), ), // 目标语言选择 GestureDetector( onTap: () => _showLanguageSelectionDialog(false), child: Container( padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h), decoration: BoxDecoration( color: 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: Colors.black, ), )), ), ), ], ), centerTitle: true, actions: [ // TTS toggle button Obx(() => IconButton( icon: Icon( controller.isTtsEnabled.value ? Icons.volume_up : Icons.volume_off, color: Colors.black, size: 24.sp, ), onPressed: controller.toggleTts, tooltip: '自动播放翻译结果', )), // 历史记录按钮 IconButton( icon: Icon( Icons.history, color: Colors.black, size: 24.sp, ), onPressed: () async { // 停止语音识别和TTS,释放资源 await controller.stopAll(); await Get.to( () => const TranslationHistoryView(), binding: TranslationHistoryBinding(), ); }, tooltip: '查看历史记录', ), ], ); } Widget _buildBody() { return Column( children: [ Expanded( child: _buildTranslationContent(), ), _buildControlBar(), ], ); } Widget _buildTranslationContent() { return Obx(() { final itemCount = controller.translationHistory.length; // 使用 currentItem 来判断是否显示当前项 final hasCurrentItem = controller.currentItem.value != null && (controller.currentItem.value!.sourceText.isNotEmpty || controller.currentItem.value!.translatedText.isNotEmpty); return ListView.builder( controller: controller.scrollController, padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 16.h), 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, ); } // History items final historyItem = controller.translationHistory[index]; return _buildTranslationItemWidget( historyItem.sourceText, historyItem.translatedText, false, item: historyItem, ); }, ); }); } Widget _buildTranslationItemWidget(String sourceText, String translatedText, bool isCurrent, {TranslationItem? item}) { return Container( margin: EdgeInsets.only(bottom: 24.h), decoration: BoxDecoration( color: Colors.grey[100], borderRadius: BorderRadius.circular(12.r), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Source text Container( width: double.infinity, padding: EdgeInsets.all(16.r), child: Text( sourceText.isEmpty && isCurrent ? '等待语音输入...' : sourceText, style: TextStyle( fontSize: 16.sp, color: sourceText.isEmpty && isCurrent ? Colors.grey : Colors.black, ), ), ), // Divider Divider(height: 1, thickness: 1, color: Colors.grey[200]), // Translated text Container( 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 ? Colors.grey : Colors.black, ), ), ), if (translatedText.isNotEmpty) IconButton( icon: Icon( Icons.volume_up, color: Colors.blue, size: 20.sp, ), onPressed: () { if (isCurrent) { controller.playTranslatedText(translatedText); } else if (item != null) { controller.playTranslationItem(item); } }, ), ], ), ), ], ), ); } Widget _buildControlBar() { return Container( padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 16.h), decoration: BoxDecoration( color: Colors.white, boxShadow: [ BoxShadow( color: Colors.black.withAlpha(13), // 0.05 * 255 ≈ 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 ? Colors.red : Colors.blue, shape: BoxShape.circle, ), child: Icon( isRecognizing ? Icons.pause : Icons.play_arrow, color: Colors.white, size: 30.sp, ), ), ); }), ], ), ); } // 显示语言选择对话框 void _showLanguageSelectionDialog(bool isSource) { Get.dialog( Dialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(24.r), ), insetPadding: EdgeInsets.symmetric(horizontal: 20.w), backgroundColor: 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( 'Select Language', style: TextStyle( fontSize: 32.sp, fontWeight: FontWeight.bold, color: Colors.black, ), ), ), SizedBox(height: 24.h), Container( constraints: BoxConstraints( maxHeight: 0.6.sh, ), child: SingleChildScrollView( physics: BouncingScrollPhysics(), child: Column( mainAxisSize: MainAxisSize.min, children: controller.supportedLanguages.keys.map((language) { final isSelected = isSource ? controller.sourceLanguage.value == language : controller.targetLanguage.value == language; return _buildLanguageItem( language: language.split(' ')[0], isSelected: isSelected, onTap: () { if (isSource) { controller.setSourceLanguage(language); } else { controller.setTargetLanguage(language); } Get.back(); }, ); }).toList(), ), ), ), SizedBox(height: 24.h), GestureDetector( onTap: () => Get.back(), child: Container( margin: EdgeInsets.symmetric(horizontal: 16.w), width: double.infinity, padding: EdgeInsets.symmetric(vertical: 16.h), alignment: Alignment.center, decoration: BoxDecoration( color: Colors.grey[200], borderRadius: BorderRadius.circular(12.r), ), child: Text( 'Cancel', style: TextStyle( fontSize: 18.sp, fontWeight: FontWeight.w500, color: Colors.black87, ), ), ), ), ], ), ), ), barrierDismissible: true, ); } // 构建语言选择项 Widget _buildLanguageItem({ required String language, required bool isSelected, required VoidCallback onTap, }) { return GestureDetector( onTap: onTap, child: Container( padding: EdgeInsets.symmetric(horizontal: 24.w, vertical: 16.h), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( language, style: TextStyle( fontSize: 20.sp, fontWeight: FontWeight.normal, color: Colors.black, ), ), Container( width: 28.w, height: 28.w, decoration: BoxDecoration( borderRadius: BorderRadius.circular(4.r), border: Border.all( color: isSelected ? Colors.transparent : Colors.grey[300]!, width: 1.5, ), color: isSelected ? Color(0xFF6C5CE7) : Colors.white, ), child: isSelected ? Icon( Icons.check, color: Colors.white, size: 20.sp, ) : null, ), ], ), ), ); } }