diff --git a/android/app/src/main/kotlin/com/example/deep_voice/AzureAsrHelper.kt b/android/app/src/main/kotlin/com/example/deep_voice/AzureAsrHelper.kt index da501829b..1eff8c8be 100644 --- a/android/app/src/main/kotlin/com/example/deep_voice/AzureAsrHelper.kt +++ b/android/app/src/main/kotlin/com/example/deep_voice/AzureAsrHelper.kt @@ -269,9 +269,9 @@ class AzureAsrHelper { if (event.result.reason == ResultReason.RecognizingSpeech) { val autoDetectSourceLanguageResult = AutoDetectSourceLanguageResult.fromResult(event.result) val detectedLanguage = autoDetectSourceLanguageResult?.language ?: "" - if (isAutoDetectLanguage) { - Log.d(TAG, "正在识别: ${event.result.text}, 检测到的语言: $detectedLanguage") - } + // if (isAutoDetectLanguage) { + // Log.d(TAG, "正在识别: ${event.result.text}, 检测到的语言: $detectedLanguage") + // } callback.onRecognizing(event.result.text, detectedLanguage) } } diff --git a/lib/modules/chat/views/chat_view.dart b/lib/modules/chat/views/chat_view.dart index 94cd42aba..e575c5c56 100644 --- a/lib/modules/chat/views/chat_view.dart +++ b/lib/modules/chat/views/chat_view.dart @@ -13,20 +13,26 @@ class ChatView extends GetView { @override Widget build(BuildContext context) { + final isDarkMode = Theme.of(context).brightness == Brightness.dark; + return Scaffold( - backgroundColor: Colors.grey[50], // 使用浅灰背景,类似HTML模板 - appBar: _buildAppBar(), - body: _buildBody(), + backgroundColor: isDarkMode ? AppColors.darkBackground : Colors.grey[50], + appBar: _buildAppBar(isDarkMode), + body: _buildBody(isDarkMode), ); } // 构建AppBar - PreferredSizeWidget _buildAppBar() { + PreferredSizeWidget _buildAppBar(bool isDarkMode) { return AppBar( elevation: 1, - backgroundColor: Colors.white, + backgroundColor: isDarkMode ? AppColors.cardBackground : Colors.white, leading: IconButton( - icon: Icon(Icons.arrow_back_ios, color: Colors.black87, size: 20.r), + icon: Icon( + Icons.arrow_back_ios, + color: isDarkMode ? Colors.white : Colors.black87, + size: 20.r + ), onPressed: () => Get.back(), ), titleSpacing: 0, @@ -80,7 +86,7 @@ class ChatView extends GetView { style: TextStyle( fontSize: 16.sp, fontWeight: FontWeight.bold, - color: Colors.black87, + color: isDarkMode ? Colors.white : Colors.black87, ), ), Row( @@ -117,7 +123,7 @@ class ChatView extends GetView { : Icons.volume_off, color: controller.isTtsEnabled.value ? AppColors.primary - : Colors.grey, + : isDarkMode ? Colors.grey[400] : Colors.grey, ), onPressed: controller.toggleTts, )), @@ -127,21 +133,27 @@ class ChatView extends GetView { height: 34.r, margin: EdgeInsets.only(right: 8.w), decoration: BoxDecoration( - color: Colors.grey[100], + color: isDarkMode ? Colors.white.withOpacity(0.1) : Colors.grey[100], shape: BoxShape.circle, boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.05), - blurRadius: 2, - spreadRadius: 0.5, - ), + if (!isDarkMode) + BoxShadow( + color: Colors.black.withOpacity(0.05), + blurRadius: 2, + spreadRadius: 0.5, + ), ], ), child: PopupMenuButton( - icon: Icon(Icons.more_vert, color: Colors.black87, size: 20.r), + icon: Icon( + Icons.more_vert, + color: isDarkMode ? Colors.white : Colors.black87, + size: 20.r + ), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12.r), ), + color: isDarkMode ? AppColors.cardBackground : Colors.white, itemBuilder: (context) => [ PopupMenuItem( value: 'clear', @@ -149,16 +161,23 @@ class ChatView extends GetView { children: [ Icon(Icons.delete_outline, color: Colors.red), SizedBox(width: 8.w), - Text('清空对话'), + Text( + '清空聊天记录', + style: TextStyle( + color: isDarkMode ? Colors.white : Colors.black87, + ), + ), ], ), + onTap: () { + // 延迟执行,避免在弹出菜单关闭前执行 + Future.delayed( + const Duration(milliseconds: 100), + () => controller.clearChat(), + ); + }, ), ], - onSelected: (value) { - if (value == 'clear') { - controller.clearChat(); - } - }, ), ), ], @@ -166,59 +185,43 @@ class ChatView extends GetView { } // 构建主体内容 - Widget _buildBody() { + Widget _buildBody(bool isDarkMode) { return Column( children: [ - // 消息列表 + // 聊天内容区域 Expanded( - child: Container( - color: Colors.grey[50], // 使用浅灰背景 - child: Obx(() => controller.messages.isEmpty - ? _buildEmptyChat() - : _buildChatList()), - ), + child: Obx(() { + if (controller.messages.isEmpty) { + return _buildEmptyState(isDarkMode); + } else { + return _buildChatList(isDarkMode); + } + }), ), - // 输入区域 - 根据当前模式显示不同的输入面板 - Obx(() => controller.isVoiceRecognitionActive.value - ? _buildVoiceInputPanel() - : _buildTextInputPanel()), + // 底部输入区域 + _buildInputArea(isDarkMode), ], ); } - // 构建空聊天界面 - Widget _buildEmptyChat() { + // 构建空状态 + Widget _buildEmptyState(bool isDarkMode) { return Center( child: Column( - mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, children: [ Container( - width: 80.r, - height: 80.r, + width: 80.w, + height: 80.w, decoration: BoxDecoration( - color: AppColors.primary.withOpacity(0.1), + color: isDarkMode ? Colors.white.withOpacity(0.1) : Colors.grey[100], shape: BoxShape.circle, ), - child: ClipRRect( - borderRadius: BorderRadius.circular(40.r), - child: controller.agent.avatarUrl.isNotEmpty - ? Image.asset( - controller.agent.avatarUrl, - fit: BoxFit.cover, - errorBuilder: (context, error, stackTrace) { - return Icon( - Icons.smart_toy_rounded, - size: 40.r, - color: AppColors.primary, - ); - }, - ) - : Icon( - Icons.smart_toy_rounded, - size: 40.r, - color: AppColors.primary, - ), + child: Icon( + Icons.chat_bubble_outline, + size: 40.w, + color: isDarkMode ? Colors.white.withOpacity(0.5) : Colors.grey[400], ), ), SizedBox(height: 24.h), @@ -227,7 +230,7 @@ class ChatView extends GetView { style: TextStyle( fontSize: 18.sp, fontWeight: FontWeight.bold, - color: Colors.black87, + color: isDarkMode ? Colors.white : Colors.black87, ), ), SizedBox(height: 12.h), @@ -238,7 +241,7 @@ class ChatView extends GetView { textAlign: TextAlign.center, style: TextStyle( fontSize: 14.sp, - color: Colors.grey[600], + color: isDarkMode ? Colors.grey[400] : Colors.grey[600], height: 1.5, ), ), @@ -249,7 +252,7 @@ class ChatView extends GetView { } // 构建聊天列表 - Widget _buildChatList() { + Widget _buildChatList(bool isDarkMode) { return ListView.builder( controller: controller.scrollController, padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 10.h), @@ -259,14 +262,14 @@ class ChatView extends GetView { cacheExtent: 1000, // 增加缓存范围,减少重建 itemBuilder: (context, index) { final message = controller.messages[index]; - return _buildMessageItem(message); + return _buildMessageItem(message, isDarkMode); }, ); } // 构建消息项 - Widget _buildMessageItem(Message message) { - final bool isUser = message.role == 'user'; + Widget _buildMessageItem(Message message, bool isDarkMode) { + final isUser = message.role == 'user'; return Padding( padding: EdgeInsets.only(bottom: 16.h), @@ -274,164 +277,102 @@ class ChatView extends GetView { mainAxisAlignment: isUser ? MainAxisAlignment.end : MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ - // AI头像(非用户消息时显示) - if (!isUser) - Container( - width: 36.r, - height: 36.r, - margin: EdgeInsets.only(right: 8.w, top: 2.h), - decoration: BoxDecoration( - color: AppColors.primary, - borderRadius: BorderRadius.circular(18.r), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.1), - blurRadius: 2, - offset: Offset(0, 1), - ), - ], - ), - child: ClipRRect( - borderRadius: BorderRadius.circular(18.r), - child: controller.agent.avatarUrl.isNotEmpty - ? Image.asset( - controller.agent.avatarUrl, - fit: BoxFit.cover, - errorBuilder: (context, error, stackTrace) { - return Icon( - Icons.smart_toy_rounded, - color: Colors.white, - size: 20.r, - ); - }, - ) - : Icon( - Icons.smart_toy_rounded, - color: Colors.white, - size: 20.r, - ), - ), - ), - - // 消息气泡 + if (!isUser) _buildAvatar(false, isDarkMode), + + SizedBox(width: 8.w), + Flexible( - child: Container( - padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.h), - decoration: BoxDecoration( - color: isUser ? AppColors.primary : Colors.white, - borderRadius: BorderRadius.only( - topLeft: Radius.circular(isUser ? 16.r : 0), - topRight: Radius.circular(isUser ? 0 : 16.r), - bottomLeft: Radius.circular(16.r), - bottomRight: Radius.circular(16.r), - ), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.05), - blurRadius: 3, - spreadRadius: 0.5, - offset: Offset(0, 1), + child: Column( + crossAxisAlignment: isUser ? CrossAxisAlignment.end : CrossAxisAlignment.start, + children: [ + // 消息气泡 + Container( + padding: EdgeInsets.all(12.r), + decoration: BoxDecoration( + color: isUser + ? AppColors.primary + : isDarkMode ? AppColors.cardBackground : Colors.grey[200], + borderRadius: BorderRadius.circular(16.r), ), - ], - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // 消息内容 - if (message.content.isNotEmpty) - SelectableText( - message.content, - style: TextStyle( - color: isUser ? Colors.white : Colors.black87, - fontSize: 15.sp, - height: 1.5, - letterSpacing: 0.2, - ), - textAlign: TextAlign.left, - maxLines: null, - enableInteractiveSelection: true, - textWidthBasis: TextWidthBasis.longestLine, - ), - - // 加载指示器 - if (message.isLoading) - Padding( - padding: EdgeInsets.only(top: message.content.isNotEmpty ? 8.h : 0), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - SizedBox( - width: 12.w, - height: 12.h, - child: CircularProgressIndicator( - strokeWidth: 2, - valueColor: AlwaysStoppedAnimation( - isUser ? Colors.white : AppColors.primary, - ), - ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // 消息内容 + if (message.content.isNotEmpty) + SelectableText( + message.content, + style: TextStyle( + color: isUser + ? Colors.white + : isDarkMode ? Colors.white : Colors.black, + fontSize: 16.sp, + height: 1.5, ), - SizedBox(width: 8.w), - Text( - '思考中...', - style: TextStyle( - color: isUser ? Colors.white : Colors.black54, - fontSize: 12.sp, + ), + + // 加载中动画 + if (message.isLoading) + Padding( + padding: EdgeInsets.only(top: 8.h), + child: Row( + mainAxisSize: MainAxisSize.min, + children: List.generate( + 3, + (index) => _buildLoadingDot(index, isDarkMode), ), ), - ], - ), + ), + ], + ), + ), + + // 时间戳 + Padding( + padding: EdgeInsets.only(top: 4.h, left: 4.w, right: 4.w), + child: Text( + _formatTime(message.timestamp), + style: TextStyle( + fontSize: 10.sp, + color: isDarkMode ? Colors.grey[500] : Colors.grey[500], ), - ], - ), - ), - ), - - // 用户头像(用户消息时显示) - if (isUser) - Container( - width: 36.r, - height: 36.r, - margin: EdgeInsets.only(left: 8.w, top: 2.h), - decoration: BoxDecoration( - color: Colors.grey[200], - borderRadius: BorderRadius.circular(18.r), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.05), - blurRadius: 2, - offset: Offset(0, 1), ), - ], - ), - child: Center( - child: Icon( - Icons.person, - color: Colors.grey[600], - size: 20.r, ), - ), + ], ), + ), + + SizedBox(width: 8.w), + + if (isUser) _buildAvatar(true, isDarkMode), ], ), ); } - // 构建文本输入面板 - Widget _buildTextInputPanel() { + // 构建输入区域 + Widget _buildInputArea(bool isDarkMode) { final textController = TextEditingController(text: controller.inputText.value); - textController.addListener(() { - controller.inputText.value = textController.text; + // 检查是否处于语音识别模式 + return Obx(() { + if (controller.isVoiceRecognitionActive.value) { + return _buildVoiceInputPanel(isDarkMode); + } else { + return _buildTextInputPanel(isDarkMode, textController); + } }); - + } + + // 构建文本输入面板 + Widget _buildTextInputPanel(bool isDarkMode, TextEditingController textController) { return Container( - padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 10.h), + padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.h), decoration: BoxDecoration( - color: Colors.white, + color: isDarkMode ? AppColors.cardBackground : Colors.white, boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.06), - blurRadius: 10, + color: Colors.black.withOpacity(0.05), + blurRadius: 3, offset: Offset(0, -1), ), ], @@ -439,25 +380,18 @@ class ChatView extends GetView { child: SafeArea( child: Row( children: [ - // 语音输入按钮 + // 语音识别按钮 Container( - width: 40.r, - height: 40.r, + width: 40.w, + height: 40.w, decoration: BoxDecoration( - color: Colors.grey[100], + color: isDarkMode ? Colors.white.withOpacity(0.1) : Colors.grey[100], shape: BoxShape.circle, - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.05), - blurRadius: 3, - spreadRadius: 0.5, - ), - ], ), child: IconButton( icon: Icon( Icons.mic_none, - color: Colors.grey[600], + color: isDarkMode ? Colors.white : Colors.grey[700], size: 20.r, ), padding: EdgeInsets.zero, @@ -465,115 +399,99 @@ class ChatView extends GetView { ), ), - // 文本输入框 + SizedBox(width: 8.w), + + // 输入框 Expanded( - child: Container( - margin: EdgeInsets.symmetric(horizontal: 8.w), - decoration: BoxDecoration( - color: Colors.grey[100], - borderRadius: BorderRadius.circular(20.r), - ), - child: TextField( - controller: textController, - decoration: InputDecoration( - hintText: '输入消息...', - hintStyle: TextStyle(color: Colors.grey[500]), - border: InputBorder.none, - contentPadding: EdgeInsets.symmetric( - horizontal: 16.w, - vertical: 10.h, - ), + child: TextField( + controller: textController, + decoration: InputDecoration( + hintText: '输入消息...', + hintStyle: TextStyle( + color: isDarkMode ? Colors.grey[500] : Colors.grey[500] ), - maxLines: null, - textInputAction: TextInputAction.send, - style: TextStyle( - fontSize: 15.sp, - color: Colors.black87, + filled: true, + fillColor: isDarkMode ? Colors.white.withOpacity(0.1) : Colors.grey[100], + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(20.r), + borderSide: BorderSide.none, ), - onSubmitted: (value) { - if (value.trim().isNotEmpty) { - controller.sendMessage(value); - textController.clear(); - } - }, + contentPadding: EdgeInsets.symmetric( + horizontal: 16.w, + vertical: 10.h, + ), + ), + maxLines: null, + textInputAction: TextInputAction.send, + style: TextStyle( + fontSize: 15.sp, + color: isDarkMode ? Colors.white : Colors.black87, ), + onSubmitted: (value) { + if (value.trim().isNotEmpty) { + controller.sendMessage(value); + textController.clear(); + } + }, ), ), + SizedBox(width: 8.w), + // 发送按钮 - Obx(() => Container( - width: 40.r, - height: 40.r, - decoration: BoxDecoration( - color: controller.inputText.value.trim().isEmpty - ? Colors.grey[100] - : AppColors.primary, - shape: BoxShape.circle, - boxShadow: controller.inputText.value.trim().isEmpty - ? [ - BoxShadow( - color: Colors.black.withOpacity(0.05), - blurRadius: 3, - spreadRadius: 0.5, - ), - ] - : [ - BoxShadow( - color: AppColors.primary.withOpacity(0.3), - blurRadius: 5, - spreadRadius: 0.5, - ), - ], - ), - child: IconButton( - icon: Icon( - Icons.send, - size: 18.r, - color: controller.inputText.value.trim().isEmpty - ? Colors.grey[500] - : Colors.white, + Obx(() { + final hasText = controller.inputText.value.isNotEmpty; + return Container( + width: 40.w, + height: 40.w, + decoration: BoxDecoration( + color: hasText + ? AppColors.primary + : isDarkMode ? Colors.white.withOpacity(0.1) : Colors.grey[200], + shape: BoxShape.circle, ), - padding: EdgeInsets.zero, - onPressed: controller.inputText.value.trim().isEmpty - ? null - : () { - controller.sendMessage(controller.inputText.value); - textController.clear(); - }, - ), - )), + child: IconButton( + icon: Icon( + Icons.send, + color: hasText + ? Colors.white + : isDarkMode ? Colors.white.withOpacity(0.5) : Colors.grey[400], + size: 18.r, + ), + padding: EdgeInsets.zero, + onPressed: hasText + ? () { + controller.sendMessage(controller.inputText.value); + textController.clear(); + } + : null, + ), + ); + }), ], ), ), ); } - + // 构建语音输入面板 - Widget _buildVoiceInputPanel() { + Widget _buildVoiceInputPanel(bool isDarkMode) { return Container( decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [ - Colors.white, - Color(0xFFE8D5F5), - ], - stops: [0.3, 1.0], - ), - // boxShadow: [ - // BoxShadow( - // color: Colors.black.withOpacity(0.06), - // blurRadius: 10, - // offset: Offset(0, -1), - // ), - // ], + color: isDarkMode ? AppColors.cardBackground : Colors.white, + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.05), + blurRadius: 3, + offset: Offset(0, -1), + ), + ], ), child: SafeArea( child: SizedBox( - height: 80.h, + height: 120.h, child: Padding( - padding: EdgeInsets.symmetric(horizontal: 30.w), + padding: EdgeInsets.symmetric(horizontal: 20.w), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.center, @@ -583,7 +501,7 @@ class ChatView extends GetView { width: 60.r, height: 60.r, decoration: BoxDecoration( - color: Colors.white, + color: isDarkMode ? Colors.white.withOpacity(0.1) : Colors.white, shape: BoxShape.circle, boxShadow: [ BoxShadow( @@ -639,7 +557,7 @@ class ChatView extends GetView { decoration: BoxDecoration( color: isSpeaking ? AppColors.primary - : Colors.grey.shade400, + : isDarkMode ? Colors.grey.shade600 : Colors.grey.shade400, borderRadius: BorderRadius.circular(2.r), ), ), @@ -659,7 +577,7 @@ class ChatView extends GetView { controller.isSpeechDetected.value ? '正在聆听...' : '你可以开始说话', style: TextStyle( fontSize: 14.sp, - color: Colors.grey.shade700, + color: isDarkMode ? Colors.grey[400] : Colors.grey[700], fontWeight: FontWeight.w500, ), )), @@ -672,7 +590,7 @@ class ChatView extends GetView { width: 60.r, height: 60.r, decoration: BoxDecoration( - color: Colors.white, + color: isDarkMode ? Colors.white.withOpacity(0.1) : Colors.white, shape: BoxShape.circle, boxShadow: [ BoxShadow( @@ -686,7 +604,7 @@ class ChatView extends GetView { child: IconButton( icon: Icon( Icons.close, - color: Colors.grey.shade600, + color: isDarkMode ? Colors.white : Colors.grey.shade600, size: 28.r, ), padding: EdgeInsets.zero, @@ -701,4 +619,87 @@ class ChatView extends GetView { ), ); } + + // 构建头像 + Widget _buildAvatar(bool isUser, bool isDarkMode) { + return Container( + width: 36.r, + height: 36.r, + decoration: BoxDecoration( + color: isUser + ? (isDarkMode ? Colors.white.withOpacity(0.1) : Colors.grey[200]) + : AppColors.primary, + shape: BoxShape.circle, + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.05), + blurRadius: 2, + offset: Offset(0, 1), + ), + ], + ), + child: isUser + ? Center( + child: Icon( + Icons.person, + color: isDarkMode ? Colors.white : Colors.grey[700], + size: 20.r, + ), + ) + : ClipRRect( + borderRadius: BorderRadius.circular(18.r), + child: controller.agent.avatarUrl.isNotEmpty + ? Image.asset( + controller.agent.avatarUrl, + fit: BoxFit.cover, + errorBuilder: (context, error, stackTrace) { + return Icon( + Icons.smart_toy_rounded, + color: Colors.white, + size: 20.r, + ); + }, + ) + : Icon( + Icons.smart_toy_rounded, + color: Colors.white, + size: 20.r, + ), + ), + ); + } + + // 构建加载动画点 + Widget _buildLoadingDot(int index, bool isDarkMode) { + return AnimatedBuilder( + animation: AlwaysStoppedAnimation(0), + builder: (context, child) { + return Container( + margin: EdgeInsets.symmetric(horizontal: 2.w), + width: 8.r, + height: 8.r, + decoration: BoxDecoration( + color: isDarkMode ? Colors.white.withOpacity(0.5) : Colors.grey[400], + shape: BoxShape.circle, + ), + ); + }, + ); + } + + // 格式化时间 + String _formatTime(DateTime time) { + final now = DateTime.now(); + final today = DateTime(now.year, now.month, now.day); + final yesterday = today.subtract(const Duration(days: 1)); + final messageDate = DateTime(time.year, time.month, time.day); + + if (messageDate == today) { + return '今天 ${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}'; + } else if (messageDate == yesterday) { + return '昨天 ${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}'; + } else { + return '${time.month}月${time.day}日 ${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}'; + } + } } \ No newline at end of file diff --git a/lib/modules/settings/views/settings_view.dart b/lib/modules/settings/views/settings_view.dart index 36136f5a6..2d478c0e9 100644 --- a/lib/modules/settings/views/settings_view.dart +++ b/lib/modules/settings/views/settings_view.dart @@ -27,13 +27,7 @@ class SettingsView extends GetView { ), ), centerTitle: true, - leading: IconButton( - icon: Icon( - Icons.arrow_back_ios, - color: isDarkMode ? Colors.white70 : Colors.black54 - ), - onPressed: () => Get.back(), - ), + ), body: ListView( padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 16.h), diff --git a/lib/modules/translation/controllers/translation_controller.dart b/lib/modules/translation/controllers/translation_controller.dart index 25dd534f3..ca3730351 100644 --- a/lib/modules/translation/controllers/translation_controller.dart +++ b/lib/modules/translation/controllers/translation_controller.dart @@ -8,7 +8,7 @@ import '../../../data/services/azure_asr_service.dart'; import '../../../data/services/azure_tts_service.dart'; import '../../../data/services/language_manager.dart'; import '../../../core/utils/logger.dart'; - +import '../../../data/services/voice_interaction_service.dart'; class TranslationController extends GetxController { // 服务 final AzureAsrService _asrService = Get.find(); @@ -16,9 +16,12 @@ class TranslationController extends GetxController { final AzureTtsService _ttsService = Get.find(); final LanguageManager _languageManager = Get.find(); final GetStorage _storage = GetStorage(); + final VoiceInteractionService _voiceInteractionService = Get.find(); // 存储相关 static const String _historyKey = 'translation_history'; + static const String _sourceLanguageKey = 'translation_source_language'; + static const String _targetLanguageKey = 'translation_target_language'; static const int _maxHistoryItems = 100; // 状态变量 @@ -43,9 +46,8 @@ class TranslationController extends GetxController { // 其他 StreamSubscription? _recognitionSubscription; + StreamSubscription? _voiceInteractionSubscription; Timer? _translationDebounceTimer; - Timer? _scrollDebounceTimer; // 滚动防抖定时器 - bool _isNearBottom = true; // 标记是否在底部附近 // 获取支持的语言列表 Map get supportedLanguages => _languageManager.getChineseNameToAsrCodeMap(); @@ -53,75 +55,73 @@ class TranslationController extends GetxController { @override void onInit() { super.onInit(); - _initController(); - } + + // 加载上次保存的语言设置 + _loadSavedLanguages(); + + // 初始化语言代码 + final sourceAsrCode = _languageManager.getAsrCodeByChineseName(sourceLanguage.value); + if (sourceAsrCode != null) { + sourceLanguageCode.value = sourceAsrCode; + } + + final targetAsrCode = _languageManager.getAsrCodeByChineseName(targetLanguage.value); + if (targetAsrCode != null) { + targetLanguageCode.value = targetAsrCode; + } + + // 订阅VoiceInteractionService事件 + _subscribeToVoiceInteractionEvents(); - // 初始化控制器 - void _initController() { - _initLanguageCodes(); + // 加载历史记录 loadTranslationHistory(); + + // 初始化服务 _initServices(); - _setupListeners(); - // 初始化完成后滚动到底部 + // 使用微任务在布局完成后自动滚动到底部 WidgetsBinding.instance.addPostFrameCallback((_) { - // 延迟 300ms 再次尝试滚动,确保列表已完全渲染 - Future.delayed(const Duration(milliseconds: 300), () { - scrollToBottom(animate: false); - }); - + _scrollToBottom(); }); } - // 重新初始化控制器(公共方法) - void reinitialize() { - _setupListeners(); + /// 订阅VoiceInteractionService事件 + void _subscribeToVoiceInteractionEvents() { + if (_voiceInteractionService.eventStream != null) { + _voiceInteractionSubscription = _voiceInteractionService.eventStream!.listen((event) { + if (event is RecognitionStartedEvent) { + // 收到recognitionStarted事件时,停止当前的AI交互、录音和TTS + stopAll(); + } + }); + } + } + + // 加载保存的语言设置 + void _loadSavedLanguages() { + final savedSourceLanguage = _storage.read(_sourceLanguageKey); + final savedTargetLanguage = _storage.read(_targetLanguageKey); - // 初始化完成后滚动到底部 - WidgetsBinding.instance.addPostFrameCallback((_) { - scrollToBottom(animate: false); - }); + if (savedSourceLanguage != null && supportedLanguages.containsKey(savedSourceLanguage)) { + sourceLanguage.value = savedSourceLanguage; + } + + if (savedTargetLanguage != null && supportedLanguages.containsKey(savedTargetLanguage)) { + targetLanguage.value = savedTargetLanguage; + } } - // 仅设置滚动监听器(不启动翻译) - void setupScrollListener() { - // 移除之前的监听器,避免重复添加 - scrollController.removeListener(_onScroll); - // 重新添加滚动监听器 - scrollController.addListener(_onScroll); + // 保存当前语言设置 + void _saveLanguageSettings() { + _storage.write(_sourceLanguageKey, sourceLanguage.value); + _storage.write(_targetLanguageKey, targetLanguage.value); } // 初始化服务 Future _initServices() async { - await _initTranslationService(); - await _initAzureAsrService(); - } - - // 初始化语言代码 - void _initLanguageCodes() { - final sourceAsrCode = _languageManager.getAsrCodeByChineseName(sourceLanguage.value); - if (sourceAsrCode != null) { - sourceLanguageCode.value = sourceAsrCode; - } - - final targetAsrCode = _languageManager.getAsrCodeByChineseName(targetLanguage.value); - if (targetAsrCode != null) { - targetLanguageCode.value = targetAsrCode; - } - } - - // 初始化翻译服务 - Future _initTranslationService() async { try { await _translationService.initialize(); - } catch (e) { - Logger.error('初始化翻译服务失败: $e'); - } - } - - // 初始化语音识别服务 - Future _initAzureAsrService() async { - try { + final List asrSupportedLanguages = [sourceLanguageCode.value, targetLanguageCode.value]; await _ttsService.initialize(); await _asrService.initialize( @@ -129,61 +129,19 @@ class TranslationController extends GetxController { supportedLanguages: asrSupportedLanguages, ); } catch (e) { - Logger.error('初始化语音识别服务失败: $e'); + Logger.error('初始化服务失败: $e'); } } - // 设置监听器 - void _setupListeners() { - // 监听历史记录变化,当有新项添加时滚动到底部 - ever(translationHistory, (_) { - if (_isNearBottom) { - scrollToBottom(); - } - }); - - // 监听当前项变化,当有新项添加时滚动到底部 - ever(currentItem, (item) { - if (item != null && _isNearBottom) { - scrollToBottom(); - } - }); - - // 监听滚动位置,判断是否在底部附近 - scrollController.addListener(_onScroll); - } - - // 滚动监听器 - void _onScroll() { - if (!scrollController.hasClients) return; - - // 判断是否在底部附近(距离底部20像素内) - final maxScroll = scrollController.position.maxScrollExtent; - final currentScroll = scrollController.position.pixels; - _isNearBottom = (maxScroll - currentScroll) < 20; - } - @override void onClose() { stopRecognition(); _recognitionSubscription?.cancel(); _translationDebounceTimer?.cancel(); - _scrollDebounceTimer?.cancel(); - scrollController.removeListener(_onScroll); scrollController.dispose(); super.onClose(); } - // 更新ASR支持的语言 - Future _updateAsrSupportedLanguages() async { - try { - final List asrSupportedLanguages = [sourceLanguageCode.value, targetLanguageCode.value]; - await _asrService.setSupportedLanguages(asrSupportedLanguages); - } catch (e) { - Logger.error('更新ASR支持的语言失败: $e'); - } - } - // 切换TTS功能 void toggleTts() => isTtsEnabled.toggle(); @@ -191,8 +149,13 @@ class TranslationController extends GetxController { Future startRecognition() async { if (isRecognizing.value) return; + _voiceInteractionService.pauseVoiceInteraction(); + try { - await _updateAsrSupportedLanguages(); + // 更新ASR支持的语言 + final List asrSupportedLanguages = [sourceLanguageCode.value, targetLanguageCode.value]; + await _asrService.setSupportedLanguages(asrSupportedLanguages); + currentSessionId = DateTime.now().millisecondsSinceEpoch.toString(); // 初始化新的翻译项 @@ -283,48 +246,48 @@ class TranslationController extends GetxController { timestamp: DateTime.now(), sessionId: currentSessionId!, isFirstInSession: translationHistory.isEmpty || translationHistory.last.sessionId != currentSessionId, - isIntermediate: true, // 保留标记,用于内部逻辑 + isIntermediate: true, ); + // 添加新项后滚动到底部 + _scrollToBottom(); } else { // 更新当前项 currentItem.value!.sourceText = text; - currentItem.value!.isIntermediate = true; // 保留标记,用于内部逻辑 + currentItem.value!.isIntermediate = true; currentItem.refresh(); + // 更新后滚动到底部 + _scrollToBottom(); } // 防抖处理翻译请求 _translationDebounceTimer?.cancel(); _translationDebounceTimer = Timer(const Duration(milliseconds: 300), () { - translateCurrentItem(isIntermediate: true); + if (currentItem.value != null && currentItem.value!.sourceText.isNotEmpty) { + translateText(currentItem.value!.sourceText); + } }); } - // 翻译当前项 - Future translateCurrentItem({bool isIntermediate = false}) async { - if (currentItem.value == null || currentItem.value!.sourceText.isEmpty) return; - await translateText(currentItem.value!.sourceText); - } - // 处理最终识别结果 Future handleFinalResult(String text) async { if (text.isEmpty || currentItem.value == null) return; // 更新当前项,但不立即添加到历史记录 currentItem.value!.sourceText = text; - currentItem.value!.isIntermediate = false; // 标记为最终结果 + currentItem.value!.isIntermediate = false; currentItem.refresh(); + // 更新后滚动到底部 + _scrollToBottom(); // 先翻译,保持UI稳定 final translationResult = await translateText(text); - // 给UI一点时间更新 - await Future.delayed(const Duration(milliseconds: 100)); - // 翻译完成后再添加到历史记录 if (currentItem.value != null) { - // 确保添加到历史记录的是最终结果 translationHistory.add(currentItem.value!); saveTranslationHistory(); + // 添加到历史记录后滚动到底部 + _scrollToBottom(); // 播放TTS if (translationResult != null && isTtsEnabled.value) { @@ -357,7 +320,28 @@ class TranslationController extends GetxController { ); if (translationResult != null) { - _updateTranslationResult(sourceText, translationResult, detectedSourceLanguageCode, detectedTargetLanguageCode); + // 更新翻译结果 + if (currentItem.value != null && currentItem.value!.sourceText.contains(sourceText)) { + // 更新当前项 + currentItem.value!.translatedText = translationResult; + currentItem.value!.sourceLanguageCode = detectedSourceLanguageCode; + currentItem.value!.targetLanguageCode = detectedTargetLanguageCode; + currentItem.refresh(); + // 更新后滚动到底部 + _scrollToBottom(); + } else { + // 更新历史记录 + final index = translationHistory.indexWhere((item) => item.sourceText == sourceText); + if (index != -1) { + translationHistory[index].translatedText = translationResult; + translationHistory[index].sourceLanguageCode = detectedSourceLanguageCode; + translationHistory[index].targetLanguageCode = detectedTargetLanguageCode; + translationHistory.refresh(); + saveTranslationHistory(); + // 更新后滚动到底部 + _scrollToBottom(); + } + } } } catch (e) { Logger.error('翻译失败: $e'); @@ -368,27 +352,6 @@ class TranslationController extends GetxController { return translationResult; } - // 更新翻译结果 - void _updateTranslationResult(String sourceText, String result, String sourceCode, String targetCode) { - if (currentItem.value != null && currentItem.value!.sourceText.contains(sourceText)) { - // 更新当前项 - currentItem.value!.translatedText = result; - currentItem.value!.sourceLanguageCode = sourceCode; - currentItem.value!.targetLanguageCode = targetCode; - currentItem.refresh(); - } else { - // 更新历史记录 - final index = translationHistory.indexWhere((item) => item.sourceText == sourceText); - if (index != -1) { - translationHistory[index].translatedText = result; - translationHistory[index].sourceLanguageCode = sourceCode; - translationHistory[index].targetLanguageCode = targetCode; - translationHistory.refresh(); - saveTranslationHistory(); - } - } - } - // 播放翻译文本 Future playTranslatedText(String text) async { if (text.isEmpty) return; @@ -412,10 +375,7 @@ class TranslationController extends GetxController { await stopRecognition(); } - // 取消所有定时器 _translationDebounceTimer?.cancel(); - _scrollDebounceTimer?.cancel(); - await _ttsService.stop(); } catch (e) { Logger.error('停止操作失败: $e'); @@ -451,30 +411,26 @@ class TranslationController extends GetxController { } } - // 简洁高效的滚动到底部方法 - void scrollToBottom({bool animate = true}) { - if (!scrollController.hasClients) return; - - // 取消之前的定时器 - _scrollDebounceTimer?.cancel(); - - // 创建新的定时器,防抖处理 - _scrollDebounceTimer = Timer(const Duration(milliseconds: 100), () { - if (!scrollController.hasClients) return; - - - if (animate) { + // 滚动到底部 + void _scrollToBottom() { + if (scrollController.hasClients && (translationHistory.isNotEmpty || currentItem.value != null)) { + try { scrollController.animateTo( scrollController.position.maxScrollExtent, - duration: const Duration(milliseconds: 200), + duration: const Duration(milliseconds: 300), curve: Curves.easeOut, ); - } else { - scrollController.jumpTo(scrollController.position.maxScrollExtent); + } catch (e) { + Logger.error('滚动到底部失败: $e'); } - }); + } } + // 设置滚动监听器(简化版,不再需要实际监听) + void setupScrollListener() { + // 简化后的空方法,保留接口兼容性 + } + // 底部控制栏高度 double _bottomBarHeight = 0.0; @@ -488,7 +444,6 @@ class TranslationController extends GetxController { // 切换源语言和目标语言 void swapLanguages() { - // 先停止当前的翻译操作 stopAll(); final tempName = sourceLanguage.value; @@ -498,12 +453,14 @@ class TranslationController extends GetxController { final tempCode = sourceLanguageCode.value; sourceLanguageCode.value = targetLanguageCode.value; targetLanguageCode.value = tempCode; + + // 保存语言设置 + _saveLanguageSettings(); } // 设置源语言 void setSourceLanguage(String language) { if (sourceLanguage.value != language) { - // 先停止当前的翻译操作 stopAll(); sourceLanguage.value = language; @@ -512,20 +469,15 @@ class TranslationController extends GetxController { if (asrCode != null) { sourceLanguageCode.value = asrCode; } - - _updateAsrSupportedLanguages(); - - if (isRecognizing.value) { - stopRecognition(); - startRecognition(); - } + + // 保存语言设置 + _saveLanguageSettings(); } } // 设置目标语言 void setTargetLanguage(String language) { if (targetLanguage.value != language) { - // 先停止当前的翻译操作 stopAll(); targetLanguage.value = language; @@ -535,7 +487,8 @@ class TranslationController extends GetxController { targetLanguageCode.value = asrCode; } - _updateAsrSupportedLanguages(); + // 保存语言设置 + _saveLanguageSettings(); } } @@ -546,6 +499,11 @@ class TranslationController extends GetxController { try { final List historyList = jsonDecode(historyJson); translationHistory.value = historyList.map((item) => TranslationItem.fromJson(item)).toList(); + + // 使用微任务确保在视图完全渲染后滚动到底部 + WidgetsBinding.instance.addPostFrameCallback((_) { + _scrollToBottom(); + }); } catch (e) { _storage.remove(_historyKey); Logger.error('加载翻译历史失败: $e'); @@ -572,6 +530,11 @@ class TranslationController extends GetxController { translationHistory.clear(); _storage.remove(_historyKey); } + + // 滚动到底部 (公共方法,用于向后兼容) + void scrollToBottom({bool animate = true}) { + _scrollToBottom(); + } } // 翻译项模型 diff --git a/lib/modules/translation/views/translation_detail_view.dart b/lib/modules/translation/views/translation_detail_view.dart index 797e5cf87..9e94ecbdf 100644 --- a/lib/modules/translation/views/translation_detail_view.dart +++ b/lib/modules/translation/views/translation_detail_view.dart @@ -7,6 +7,7 @@ import '../controllers/translation_detail_controller.dart'; import 'package:intl/intl.dart'; import 'package:flutter/services.dart'; import 'package:share_plus/share_plus.dart'; +import '../../../core/theme/app_colors.dart'; class TranslationDetailView extends GetView { TranslationDetailView({Key? key}) : super(key: key); @@ -33,19 +34,25 @@ class TranslationDetailView extends GetView { @override Widget build(BuildContext context) { + final isDarkMode = Theme.of(context).brightness == Brightness.dark; + return Scaffold( - backgroundColor: Color(0xFFF5F5F8), - appBar: _buildAppBar(), + backgroundColor: isDarkMode ? AppColors.darkBackground : Color(0xFFF5F5F8), + appBar: _buildAppBar(isDarkMode), body: Obx(() { // 显示加载状态 if (controller.isLoading.value) { - return const Center(child: CircularProgressIndicator()); + return Center( + child: CircularProgressIndicator( + color: isDarkMode ? AppColors.primary : Colors.blue, + ), + ); } return Column( children: [ - _buildTabButtons(), - _buildContentArea(), + _buildTabButtons(isDarkMode), + _buildContentArea(isDarkMode), ], ); }), @@ -53,14 +60,14 @@ class TranslationDetailView extends GetView { } // 构建应用栏 - PreferredSizeWidget _buildAppBar() { + PreferredSizeWidget _buildAppBar(bool isDarkMode) { return AppBar( - backgroundColor: Color(0xFFF5F5F8), + backgroundColor: isDarkMode ? AppColors.cardBackground : Color(0xFFF5F5F8), elevation: 0, leading: IconButton( icon: Icon( Icons.arrow_back_ios, - color: Colors.black, + color: isDarkMode ? Colors.white : Colors.black, size: 20.sp, ), onPressed: () => Get.back(), @@ -68,7 +75,7 @@ class TranslationDetailView extends GetView { title: Text( 'Detail', style: TextStyle( - color: Colors.black, + color: isDarkMode ? Colors.white : Colors.black, fontSize: 24.sp, fontWeight: FontWeight.bold, ), @@ -78,7 +85,7 @@ class TranslationDetailView extends GetView { IconButton( icon: Icon( Icons.share, - color: Colors.black, + color: isDarkMode ? Colors.white : Colors.black, size: 22.sp, ), onPressed: _shareContent, @@ -88,7 +95,7 @@ class TranslationDetailView extends GetView { } // 构建选项卡按钮 - Widget _buildTabButtons() { + Widget _buildTabButtons(bool isDarkMode) { return Padding( padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.h), child: Row( @@ -98,6 +105,7 @@ class TranslationDetailView extends GetView { title: '原始记录', isSelected: controller.selectedTabIndex.value == 0, onTap: () => controller.switchTab(0), + isDarkMode: isDarkMode, )), ), SizedBox(width: 12.w), @@ -106,6 +114,7 @@ class TranslationDetailView extends GetView { title: '记录摘要', isSelected: controller.selectedTabIndex.value == 1, onTap: () => controller.switchTab(1), + isDarkMode: isDarkMode, )), ), ], @@ -114,7 +123,7 @@ class TranslationDetailView extends GetView { } // 构建内容区域 - Widget _buildContentArea() { + Widget _buildContentArea(bool isDarkMode) { return Expanded( child: SingleChildScrollView( padding: EdgeInsets.symmetric(horizontal: 16.w), @@ -122,14 +131,14 @@ class TranslationDetailView extends GetView { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - _buildTimestamp(), + _buildTimestamp(isDarkMode), SizedBox(height: 8.h), // 根据选中的选项卡显示不同内容 if (controller.selectedTabIndex.value == 0) - ..._buildOriginalRecords() + ..._buildOriginalRecords(isDarkMode) else - _buildSummaryWidget(), + _buildSummaryWidget(isDarkMode), SizedBox(height: 24.h), ], @@ -140,7 +149,7 @@ class TranslationDetailView extends GetView { } // 构建时间戳 - Widget _buildTimestamp() { + Widget _buildTimestamp(bool isDarkMode) { return Padding( padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 8.h), child: Text( @@ -148,7 +157,7 @@ class TranslationDetailView extends GetView { style: TextStyle( fontSize: 14.sp, fontWeight: FontWeight.w600, - color: Colors.grey[600], + color: isDarkMode ? Colors.grey[400] : Colors.grey[600], letterSpacing: 1.0, ), ), @@ -156,8 +165,8 @@ class TranslationDetailView extends GetView { } // 构建原始记录列表 - List _buildOriginalRecords() { - return controller.sessionItems.map((sessionItem) => _buildSessionItemWidget(sessionItem)).toList(); + List _buildOriginalRecords(bool isDarkMode) { + return controller.sessionItems.map((sessionItem) => _buildSessionItemWidget(sessionItem, isDarkMode)).toList(); } // 构建选项卡按钮 @@ -165,17 +174,20 @@ class TranslationDetailView extends GetView { required String title, required bool isSelected, required VoidCallback onTap, + required bool isDarkMode, }) { return GestureDetector( onTap: onTap, child: Container( height: 40.h, decoration: BoxDecoration( - color: isSelected ? Colors.blue : Colors.white, + color: isSelected + ? (isDarkMode ? AppColors.primary : Colors.blue) + : (isDarkMode ? AppColors.cardBackground : Colors.white), borderRadius: BorderRadius.circular(20.r), boxShadow: [ BoxShadow( - color: Colors.black.withAlpha(8), + color: Colors.black.withAlpha(isDarkMode ? 30 : 8), blurRadius: 4, offset: Offset(0, 1), ), @@ -187,7 +199,9 @@ class TranslationDetailView extends GetView { style: TextStyle( fontSize: 15.sp, fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, - color: isSelected ? Colors.white : Colors.black87, + color: isSelected + ? Colors.white + : (isDarkMode ? Colors.white : Colors.black87), ), ), ), @@ -195,16 +209,16 @@ class TranslationDetailView extends GetView { } // 构建会话详情中的翻译项 - Widget _buildSessionItemWidget(TranslationItem item) { + Widget _buildSessionItemWidget(TranslationItem item, bool isDarkMode) { return Container( width: double.infinity, margin: EdgeInsets.only(bottom: 16.h), decoration: BoxDecoration( - color: Colors.white, + color: isDarkMode ? AppColors.cardBackground : Colors.white, borderRadius: BorderRadius.circular(16.r), boxShadow: [ BoxShadow( - color: Colors.black.withAlpha(8), + color: Colors.black.withAlpha(isDarkMode ? 30 : 8), blurRadius: 8, offset: Offset(0, 2), ), @@ -220,7 +234,7 @@ class TranslationDetailView extends GetView { item.sourceText, style: TextStyle( fontSize: 15.sp, - color: Colors.black, + color: isDarkMode ? Colors.white : Colors.black, fontWeight: FontWeight.w500, ), ), @@ -236,13 +250,13 @@ class TranslationDetailView extends GetView { item.translatedText, style: TextStyle( fontSize: 14.sp, - color: Colors.grey[700], + color: isDarkMode ? Colors.grey[400] : Colors.grey[700], fontWeight: FontWeight.normal, ), ), ), SizedBox(width: 12.w), - _buildPlayButton(() => controller.playTranslationItem(item)), + _buildPlayButton(() => controller.playTranslationItem(item), isDarkMode), ], ), ), @@ -252,23 +266,23 @@ class TranslationDetailView extends GetView { } // 构建播放按钮 - Widget _buildPlayButton(VoidCallback onTap) { + Widget _buildPlayButton(VoidCallback onTap, bool isDarkMode) { return Material( color: Colors.transparent, child: InkWell( onTap: onTap, borderRadius: BorderRadius.circular(20.r), - splashColor: Colors.blue.withOpacity(0.2), + splashColor: isDarkMode ? AppColors.primary.withOpacity(0.2) : Colors.blue.withOpacity(0.2), child: Container( width: 28.w, height: 28.h, decoration: BoxDecoration( - color: Colors.blue.withAlpha(10), + color: isDarkMode ? AppColors.primary.withAlpha(10) : Colors.blue.withAlpha(10), shape: BoxShape.circle, ), child: Icon( Icons.volume_up, - color: Colors.blue, + color: Colors.white, size: 16.sp, ), ), @@ -277,34 +291,34 @@ class TranslationDetailView extends GetView { } // 构建总结视图 - Widget _buildSummaryWidget() { + Widget _buildSummaryWidget(bool isDarkMode) { final String combinedSourceText = _getCombinedSourceText(); return Column( children: [ // 摘要内容容器 - _buildSummaryContainer(), + _buildSummaryContainer(isDarkMode), // 提取摘要按钮 - Obx(() => _buildExtractSummaryButton(combinedSourceText)), + Obx(() => _buildExtractSummaryButton(combinedSourceText, isDarkMode)), // 重新生成按钮 - Obx(() => _buildRegenerateSummaryButton(combinedSourceText)), + Obx(() => _buildRegenerateSummaryButton(combinedSourceText, isDarkMode)), ], ); } // 构建摘要容器 - Widget _buildSummaryContainer() { + Widget _buildSummaryContainer(bool isDarkMode) { return Container( width: double.infinity, margin: EdgeInsets.only(bottom: 16.h), decoration: BoxDecoration( - color: Colors.white, + color: isDarkMode ? AppColors.cardBackground : Colors.white, borderRadius: BorderRadius.circular(16.r), boxShadow: [ BoxShadow( - color: Colors.black.withAlpha(8), + color: Colors.black.withAlpha(isDarkMode ? 30 : 8), blurRadius: 8, offset: Offset(0, 2), ), @@ -312,16 +326,16 @@ class TranslationDetailView extends GetView { ), child: Obx(() { if (controller.summaryResult.value.isEmpty) { - return _buildEmptySummaryPrompt(); + return _buildEmptySummaryPrompt(isDarkMode); } else { - return _buildSummaryContent(); + return _buildSummaryContent(isDarkMode); } }), ); } // 构建空摘要提示 - Widget _buildEmptySummaryPrompt() { + Widget _buildEmptySummaryPrompt(bool isDarkMode) { return Padding( padding: EdgeInsets.all(24.r), child: Center( @@ -329,7 +343,7 @@ class TranslationDetailView extends GetView { '点击下方按钮生成会话摘要', style: TextStyle( fontSize: 15.sp, - color: Colors.grey[600], + color: isDarkMode ? Colors.grey[400] : Colors.grey[600], fontStyle: FontStyle.italic, ), textAlign: TextAlign.center, @@ -339,7 +353,7 @@ class TranslationDetailView extends GetView { } // 构建摘要内容 - Widget _buildSummaryContent() { + Widget _buildSummaryContent(bool isDarkMode) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -353,7 +367,7 @@ class TranslationDetailView extends GetView { style: TextStyle( fontSize: 16.sp, fontWeight: FontWeight.bold, - color: Colors.black, + color: isDarkMode ? Colors.white : Colors.black, ), ), SizedBox(height: 12.h), @@ -361,7 +375,7 @@ class TranslationDetailView extends GetView { controller.summaryResult.value, style: TextStyle( fontSize: 15.sp, - color: Colors.black87, + color: isDarkMode ? Colors.grey[400] : Colors.grey[800], height: 1.5, ), ), @@ -371,7 +385,7 @@ class TranslationDetailView extends GetView { Divider( height: 1, thickness: 0.5, - color: Colors.grey[200], + color: isDarkMode ? Colors.grey[700] : Colors.grey[200], ), Padding( padding: EdgeInsets.symmetric(horizontal: 12.r, vertical: 8.r), @@ -384,12 +398,12 @@ class TranslationDetailView extends GetView { child: InkWell( onTap: () => controller.playText(controller.summaryResult.value), borderRadius: BorderRadius.circular(20.r), - splashColor: Colors.blue.withOpacity(0.2), + splashColor: isDarkMode ? AppColors.primary.withOpacity(0.2) : Colors.blue.withOpacity(0.2), child: Padding( padding: EdgeInsets.all(8.r), child: Icon( Icons.volume_up, - color: Colors.blue, + color: isDarkMode ? Colors.white : Colors.blue, size: 18.sp, ), ), @@ -402,12 +416,12 @@ class TranslationDetailView extends GetView { child: InkWell( onTap: _copySummaryToClipboard, borderRadius: BorderRadius.circular(20.r), - splashColor: Colors.blue.withOpacity(0.2), + splashColor: isDarkMode ? AppColors.primary.withOpacity(0.2) : Colors.blue.withOpacity(0.2), child: Padding( padding: EdgeInsets.all(8.r), child: Icon( Icons.copy, - color: Colors.blue, + color: isDarkMode ? Colors.white : Colors.blue, size: 18.sp, ), ), @@ -432,7 +446,7 @@ class TranslationDetailView extends GetView { } // 构建提取摘要按钮 - Widget _buildExtractSummaryButton(String combinedSourceText) { + Widget _buildExtractSummaryButton(String combinedSourceText, bool isDarkMode) { final bool isDisabled = controller.isGeneratingSummary.value || controller.summaryResult.value.isNotEmpty; return Container( @@ -441,7 +455,7 @@ class TranslationDetailView extends GetView { child: ElevatedButton( onPressed: isDisabled ? null : () => _extractSummary(combinedSourceText), style: ElevatedButton.styleFrom( - backgroundColor: Colors.blue, + backgroundColor: isDarkMode ? AppColors.primary : Colors.blue, foregroundColor: Colors.white, padding: EdgeInsets.symmetric(vertical: 12.h), shape: RoundedRectangleBorder( @@ -449,11 +463,11 @@ class TranslationDetailView extends GetView { ), elevation: 0, disabledBackgroundColor: controller.isGeneratingSummary.value - ? Colors.blue.withAlpha(128) + ? isDarkMode ? AppColors.primary.withAlpha(128) : Colors.blue.withAlpha(128) : Colors.grey.withAlpha(128), ), child: controller.isGeneratingSummary.value - ? _buildGeneratingIndicator() + ? _buildGeneratingIndicator(isDarkMode) : Text( controller.summaryResult.value.isEmpty ? '提取摘要' : '已生成摘要', style: TextStyle( @@ -466,7 +480,7 @@ class TranslationDetailView extends GetView { } // 构建生成中指示器 - Widget _buildGeneratingIndicator() { + Widget _buildGeneratingIndicator(bool isDarkMode) { return Row( mainAxisAlignment: MainAxisAlignment.center, children: [ @@ -491,7 +505,7 @@ class TranslationDetailView extends GetView { } // 构建重新生成按钮 - Widget _buildRegenerateSummaryButton(String combinedSourceText) { + Widget _buildRegenerateSummaryButton(String combinedSourceText, bool isDarkMode) { if (controller.summaryResult.value.isEmpty || controller.isGeneratingSummary.value) { return SizedBox.shrink(); } @@ -502,8 +516,8 @@ class TranslationDetailView extends GetView { child: OutlinedButton( onPressed: () => _extractSummary(combinedSourceText), style: OutlinedButton.styleFrom( - foregroundColor: Colors.blue, - side: BorderSide(color: Colors.blue), + foregroundColor: isDarkMode ? AppColors.primary : Colors.blue, + side: BorderSide(color: isDarkMode ? AppColors.primary : Colors.blue), padding: EdgeInsets.symmetric(vertical: 12.h), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16.r), diff --git a/lib/modules/translation/views/translation_history_view.dart b/lib/modules/translation/views/translation_history_view.dart index b3f10ad8f..dd6063a6f 100644 --- a/lib/modules/translation/views/translation_history_view.dart +++ b/lib/modules/translation/views/translation_history_view.dart @@ -5,47 +5,18 @@ import '../controllers/translation_controller.dart'; import '../controllers/translation_history_controller.dart'; import '../bindings/translation_detail_binding.dart'; import 'translation_detail_view.dart'; +import '../../../core/theme/app_colors.dart'; class TranslationHistoryView extends GetView { const TranslationHistoryView({Key? key}) : super(key: key); @override Widget build(BuildContext context) { + final isDarkMode = Theme.of(context).brightness == Brightness.dark; + return Scaffold( - backgroundColor: Color(0xFFF5F5F8), - appBar: AppBar( - backgroundColor: Color(0xFFF5F5F8), - elevation: 0, - leading: IconButton( - icon: Icon( - Icons.arrow_back_ios, - color: Colors.black, - size: 20.sp, - ), - onPressed: () => Get.back(), - ), - title: Text( - 'History', - style: TextStyle( - color: Colors.black, - fontSize: 24.sp, - fontWeight: FontWeight.bold, - ), - ), - centerTitle: true, - actions: [ - IconButton( - icon: Icon( - Icons.delete_outline, - color: Colors.black, - size: 24.sp, - ), - onPressed: () { - _showClearHistoryConfirmDialog(); - }, - ), - ], - ), + backgroundColor: isDarkMode ? AppColors.darkBackground : Color(0xFFF5F5F8), + appBar: _buildAppBar(isDarkMode), body: Obx(() { if (!controller.hasHistory) { return Center( @@ -53,7 +24,7 @@ class TranslationHistoryView extends GetView { '暂无历史记录', style: TextStyle( fontSize: 16.sp, - color: Colors.grey, + color: isDarkMode ? Colors.grey[400] : Colors.grey, ), ), ); @@ -61,7 +32,9 @@ class TranslationHistoryView extends GetView { if (controller.isLoading.value) { return Center( - child: CircularProgressIndicator(), + child: CircularProgressIndicator( + color: isDarkMode ? AppColors.primary : Colors.blue, + ), ); } @@ -82,12 +55,12 @@ class TranslationHistoryView extends GetView { style: TextStyle( fontSize: 14.sp, fontWeight: FontWeight.w600, - color: Colors.grey[600], + color: isDarkMode ? Colors.grey[400] : Colors.grey[600], letterSpacing: 1.0, ), ), ), - _buildDateGroupContainer(items), + _buildDateGroupContainer(items, isDarkMode), ], ); }, @@ -97,15 +70,15 @@ class TranslationHistoryView extends GetView { } // 构建日期组容器 - Widget _buildDateGroupContainer(List items) { + Widget _buildDateGroupContainer(List items, bool isDarkMode) { return Container( margin: EdgeInsets.only(bottom: 16.h), decoration: BoxDecoration( - color: Colors.white, + color: isDarkMode ? AppColors.cardBackground : Colors.white, borderRadius: BorderRadius.circular(16.r), boxShadow: [ BoxShadow( - color: Colors.black.withAlpha(8), + color: Colors.black.withAlpha(isDarkMode ? 30 : 8), blurRadius: 8, offset: Offset(0, 2), ), @@ -119,19 +92,19 @@ class TranslationHistoryView extends GetView { separatorBuilder: (context, index) => Divider( height: 1, thickness: 0.5, - color: Colors.grey[200], + color: isDarkMode ? Colors.grey[800] : Colors.grey[200], indent: 0, endIndent: 0, ), itemBuilder: (context, index) { - return _buildHistoryItemInGroup(items[index]); + return _buildHistoryItemInGroup(items[index], isDarkMode); }, ), ); } // 构建组内的历史记录项 - Widget _buildHistoryItemInGroup(TranslationItem item) { + Widget _buildHistoryItemInGroup(TranslationItem item, bool isDarkMode) { return InkWell( onTap: () { // 点击查看详情 @@ -155,7 +128,7 @@ class TranslationHistoryView extends GetView { style: TextStyle( fontSize: 15.sp, fontWeight: FontWeight.w500, - color: Colors.black, + color: isDarkMode ? Colors.white : Colors.black, ), maxLines: 2, overflow: TextOverflow.ellipsis, @@ -166,7 +139,7 @@ class TranslationHistoryView extends GetView { item.translatedText, style: TextStyle( fontSize: 14.sp, - color: Colors.grey[700], + color: isDarkMode ? Colors.grey[400] : Colors.grey[700], ), maxLines: 2, overflow: TextOverflow.ellipsis, @@ -176,7 +149,7 @@ class TranslationHistoryView extends GetView { ), Icon( Icons.chevron_right, - color: Colors.grey[350], + color: isDarkMode ? Colors.grey[600] : Colors.grey[350], size: 20.sp, ), ], @@ -201,19 +174,24 @@ class TranslationHistoryView extends GetView { } void _showClearHistoryConfirmDialog() { + final isDarkMode = Get.isDarkMode; + Get.dialog( AlertDialog( + backgroundColor: isDarkMode ? AppColors.cardBackground : Colors.white, title: Text( '清除历史记录', style: TextStyle( fontSize: 18.sp, fontWeight: FontWeight.bold, + color: isDarkMode ? Colors.white : Colors.black, ), ), content: Text( '确定要清除所有翻译历史记录吗?此操作无法撤销。', style: TextStyle( fontSize: 16.sp, + color: isDarkMode ? Colors.grey[300] : Colors.black87, ), ), actions: [ @@ -223,7 +201,7 @@ class TranslationHistoryView extends GetView { '取消', style: TextStyle( fontSize: 16.sp, - color: Colors.grey[700], + color: isDarkMode ? Colors.grey[400] : Colors.grey[700], ), ), ), @@ -249,4 +227,41 @@ class TranslationHistoryView extends GetView { ), ); } + + // 构建应用栏 + PreferredSizeWidget _buildAppBar(bool isDarkMode) { + return AppBar( + backgroundColor: isDarkMode ? AppColors.cardBackground : Color(0xFFF5F5F8), + elevation: 0, + leading: IconButton( + icon: Icon( + Icons.arrow_back_ios, + color: isDarkMode ? Colors.white : Colors.black, + size: 20.sp, + ), + onPressed: () => Get.back(), + ), + title: Text( + 'History', + style: TextStyle( + color: isDarkMode ? Colors.white : Colors.black, + fontSize: 24.sp, + fontWeight: FontWeight.bold, + ), + ), + centerTitle: true, + actions: [ + IconButton( + icon: Icon( + Icons.delete_outline, + color: isDarkMode ? Colors.white : Colors.black, + size: 24.sp, + ), + onPressed: () { + _showClearHistoryConfirmDialog(); + }, + ), + ], + ); + } } \ No newline at end of file diff --git a/lib/modules/translation/views/translation_view.dart b/lib/modules/translation/views/translation_view.dart index eecce7440..6b57f13c3 100644 --- a/lib/modules/translation/views/translation_view.dart +++ b/lib/modules/translation/views/translation_view.dart @@ -4,6 +4,7 @@ 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 { @@ -11,20 +12,23 @@ class TranslationView extends GetView { @override Widget build(BuildContext context) { + final isDarkMode = Theme.of(context).brightness == Brightness.dark; + return Scaffold( - appBar: _buildAppBar(), - body: _buildBody(), + backgroundColor: isDarkMode ? AppColors.darkBackground : Colors.grey[50], + appBar: _buildAppBar(isDarkMode), + body: _buildBody(isDarkMode), ); } - PreferredSizeWidget _buildAppBar() { + PreferredSizeWidget _buildAppBar(bool isDarkMode) { return AppBar( - backgroundColor: Colors.white, + backgroundColor: isDarkMode ? AppColors.cardBackground : Colors.white, elevation: 0, leading: IconButton( icon: Icon( Icons.arrow_back_ios, - color: Colors.black, + color: isDarkMode ? Colors.white : Colors.black, size: 20.sp, ), onPressed: () { @@ -38,11 +42,11 @@ class TranslationView extends GetView { children: [ // 源语言选择 GestureDetector( - onTap: () => _showLanguageSelectionDialog(true), + onTap: () => _showLanguageSelectionDialog(true, isDarkMode), child: Container( padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h), decoration: BoxDecoration( - color: Colors.grey[100], + color: isDarkMode ? Colors.white.withOpacity(0.1) : Colors.grey[100], borderRadius: BorderRadius.circular(16.r), ), child: Obx(() => Text( @@ -50,7 +54,7 @@ class TranslationView extends GetView { style: TextStyle( fontSize: 16.sp, fontWeight: FontWeight.w600, - color: Colors.black, + color: isDarkMode ? Colors.white : Colors.black, ), )), ), @@ -63,12 +67,12 @@ class TranslationView extends GetView { margin: EdgeInsets.symmetric(horizontal: 8.w), padding: EdgeInsets.all(4.r), decoration: BoxDecoration( - color: Colors.blue[50], + color: isDarkMode ? AppColors.primary.withOpacity(0.2) : Colors.blue[50], shape: BoxShape.circle, ), child: Icon( Icons.swap_horiz, - color: Colors.blue, + color: isDarkMode ? AppColors.primary : Colors.blue, size: 20.sp, ), ), @@ -76,11 +80,11 @@ class TranslationView extends GetView { // 目标语言选择 GestureDetector( - onTap: () => _showLanguageSelectionDialog(false), + onTap: () => _showLanguageSelectionDialog(false, isDarkMode), child: Container( padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h), decoration: BoxDecoration( - color: Colors.grey[100], + color: isDarkMode ? Colors.white.withOpacity(0.1) : Colors.grey[100], borderRadius: BorderRadius.circular(16.r), ), child: Obx(() => Text( @@ -88,7 +92,7 @@ class TranslationView extends GetView { style: TextStyle( fontSize: 16.sp, fontWeight: FontWeight.w600, - color: Colors.black, + color: isDarkMode ? Colors.white : Colors.black, ), )), ), @@ -101,7 +105,7 @@ class TranslationView extends GetView { Obx(() => IconButton( icon: Icon( controller.isTtsEnabled.value ? Icons.volume_up : Icons.volume_off, - color: Colors.black, + color: isDarkMode ? Colors.white : Colors.black, size: 24.sp, ), onPressed: controller.toggleTts, @@ -112,7 +116,7 @@ class TranslationView extends GetView { IconButton( icon: Icon( Icons.history, - color: Colors.black, + color: isDarkMode ? Colors.white : Colors.black, size: 24.sp, ), onPressed: () async { @@ -132,18 +136,18 @@ class TranslationView extends GetView { ); } - Widget _buildBody() { + Widget _buildBody(bool isDarkMode) { return Column( children: [ Expanded( - child: _buildTranslationContent(), + child: _buildTranslationContent(isDarkMode), ), - _buildControlBar(), + _buildControlBar(isDarkMode), ], ); } - Widget _buildTranslationContent() { + Widget _buildTranslationContent(bool isDarkMode) { return Obx(() { final itemCount = controller.translationHistory.length; @@ -171,6 +175,7 @@ class TranslationView extends GetView { controller.currentItem.value!.translatedText, true, isIntermediate: controller.currentItem.value!.isIntermediate, + isDarkMode: isDarkMode, ); } @@ -182,6 +187,7 @@ class TranslationView extends GetView { false, item: historyItem, isIntermediate: false, // 历史项总是显示为最终结果 + isDarkMode: isDarkMode, ); }, ); @@ -195,6 +201,7 @@ class TranslationView extends GetView { { TranslationItem? item, bool isIntermediate = false, + required bool isDarkMode, } ) { // 使用AnimatedContainer实现平滑过渡,但恢复原来的视觉样式 @@ -203,7 +210,7 @@ class TranslationView extends GetView { curve: Curves.easeInOut, margin: EdgeInsets.only(bottom: 24.h), decoration: BoxDecoration( - color: Colors.grey[100], // 恢复原来的背景色 + color: isDarkMode ? AppColors.cardBackground : Colors.grey[100], borderRadius: BorderRadius.circular(12.r), ), child: Column( @@ -218,14 +225,16 @@ class TranslationView extends GetView { sourceText.isEmpty && isCurrent ? '等待语音输入...' : sourceText, style: TextStyle( fontSize: 16.sp, - color: sourceText.isEmpty && isCurrent ? Colors.grey : Colors.black, // 恢复原来的文字颜色 - fontWeight: FontWeight.normal, // 恢复原来的字重 + 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: Colors.grey[200]), + Divider(height: 1, thickness: 1, color: isDarkMode ? Colors.grey[800] : Colors.grey[200]), // Translated text AnimatedContainer( @@ -240,8 +249,10 @@ class TranslationView extends GetView { translatedText.isEmpty && isCurrent ? '翻译结果将显示在这里...' : translatedText, style: TextStyle( fontSize: 16.sp, - color: translatedText.isEmpty && isCurrent ? Colors.grey : Colors.black, // 恢复原来的文字颜色 - fontStyle: FontStyle.normal, // 恢复原来的字体样式 + color: translatedText.isEmpty && isCurrent + ? (isDarkMode ? Colors.grey[400] : Colors.grey) + : (isDarkMode ? Colors.white : Colors.black), + fontStyle: FontStyle.normal, ), ), ), @@ -249,7 +260,7 @@ class TranslationView extends GetView { IconButton( icon: Icon( Icons.volume_up, - color: Colors.blue, // 恢复原来的图标颜色 + color: isDarkMode ? AppColors.primary : Colors.blue, size: 20.sp, ), onPressed: () { @@ -268,7 +279,7 @@ class TranslationView extends GetView { ); } - Widget _buildControlBar() { + Widget _buildControlBar(bool isDarkMode) { // 使用GlobalKey来获取控制栏的高度 final controlBarKey = GlobalKey(); @@ -285,10 +296,10 @@ class TranslationView extends GetView { key: controlBarKey, padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 16.h), decoration: BoxDecoration( - color: Colors.white, + color: isDarkMode ? AppColors.cardBackground : Colors.white, boxShadow: [ BoxShadow( - color: Colors.black.withAlpha(13), // 0.05 * 255 ≈ 13 + color: Colors.black.withAlpha(isDarkMode ? 30 : 13), blurRadius: 4, offset: const Offset(0, -2), ), @@ -314,7 +325,9 @@ class TranslationView extends GetView { width: 60.w, height: 60.w, decoration: BoxDecoration( - color: isRecognizing ? Colors.red : Colors.blue, + color: isRecognizing + ? (isDarkMode ? Colors.red[700] : Colors.red) + : (isDarkMode ? AppColors.primary : Colors.blue), shape: BoxShape.circle, ), child: Icon( @@ -331,14 +344,14 @@ class TranslationView extends GetView { } // 显示语言选择对话框 - void _showLanguageSelectionDialog(bool isSource) { + void _showLanguageSelectionDialog(bool isSource, bool isDarkMode) { Get.dialog( Dialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(24.r), ), insetPadding: EdgeInsets.symmetric(horizontal: 20.w), - backgroundColor: Colors.white, + backgroundColor: isDarkMode ? AppColors.cardBackground : Colors.white, child: Container( width: 0.9.sw, padding: EdgeInsets.only(top: 32.h, bottom: 16.h), @@ -349,31 +362,39 @@ class TranslationView extends GetView { Padding( padding: EdgeInsets.symmetric(horizontal: 24.w), child: Text( - 'Select Language', + isSource ? '选择源语言' : '选择目标语言', style: TextStyle( - fontSize: 32.sp, + fontSize: 20.sp, fontWeight: FontWeight.bold, - color: Colors.black, + color: isDarkMode ? Colors.white : Colors.black, ), ), ), - SizedBox(height: 24.h), + SizedBox(height: 16.h), Container( - constraints: BoxConstraints( - maxHeight: 0.6.sh, - ), + constraints: BoxConstraints(maxHeight: 0.5.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, + 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); @@ -387,24 +408,19 @@ class TranslationView extends GetView { ), ), ), - 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, + SizedBox(height: 16.h), + Align( + alignment: Alignment.centerRight, + child: Padding( + padding: EdgeInsets.only(right: 24.w), + child: TextButton( + onPressed: () => Get.back(), + child: Text( + '取消', + style: TextStyle( + color: isDarkMode ? AppColors.primary : Colors.blue, + fontSize: 16.sp, + ), ), ), ), @@ -413,53 +429,6 @@ class TranslationView extends GetView { ), ), ), - 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, - ), - ], - ), - ), ); } } \ No newline at end of file