|
|
|
@ -20,13 +20,173 @@ class TranslationView extends GetView<TranslationController> { |
|
|
|
Widget build(BuildContext context) { |
|
|
|
final isDarkMode = Theme.of(context).brightness == Brightness.dark; |
|
|
|
|
|
|
|
return Obx(() { |
|
|
|
// 面对面翻译模式使用特殊布局 |
|
|
|
if (controller.currentMode.value == 'faceToFace') { |
|
|
|
return _buildFaceToFaceScaffold(isDarkMode); |
|
|
|
} |
|
|
|
|
|
|
|
// 其他模式使用原有布局 |
|
|
|
return Scaffold( |
|
|
|
backgroundColor: |
|
|
|
isDarkMode ? AppColors.darkBackground : Colors.grey[50], |
|
|
|
appBar: _buildAppBar(isDarkMode), |
|
|
|
body: Stack( |
|
|
|
children: [ |
|
|
|
_buildBody(isDarkMode), |
|
|
|
// 可拖拽的悬浮按钮 |
|
|
|
_buildDraggableFloatingButton(isDarkMode), |
|
|
|
], |
|
|
|
), |
|
|
|
); |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
// 构建面对面翻译模式的专用Scaffold |
|
|
|
Widget _buildFaceToFaceScaffold(bool isDarkMode) { |
|
|
|
return Scaffold( |
|
|
|
backgroundColor: isDarkMode ? AppColors.darkBackground : Colors.grey[50], |
|
|
|
appBar: _buildAppBar(isDarkMode), |
|
|
|
body: _buildBody(isDarkMode), |
|
|
|
body: Stack( |
|
|
|
children: [ |
|
|
|
// 主要内容区域 |
|
|
|
_buildFaceToFaceLayout(isDarkMode), |
|
|
|
|
|
|
|
// 顶部按钮(倒过来) |
|
|
|
Positioned( |
|
|
|
top: 50.h, |
|
|
|
left: 0, |
|
|
|
right: 0, |
|
|
|
child: Transform.rotate( |
|
|
|
angle: 3.14159, // 180度旋转 |
|
|
|
child: _buildFaceToFaceTopButton(isDarkMode), |
|
|
|
), |
|
|
|
), |
|
|
|
|
|
|
|
// 底部按钮 |
|
|
|
Positioned( |
|
|
|
bottom: 40.h, |
|
|
|
left: 0, |
|
|
|
right: 0, |
|
|
|
child: _buildFaceToFaceBottomButton(isDarkMode), |
|
|
|
), |
|
|
|
|
|
|
|
// 可拖拽的悬浮按钮 - 在所有模式下都显示 |
|
|
|
_buildDraggableFloatingButton(isDarkMode), |
|
|
|
], |
|
|
|
), |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
// 构建面对面翻译顶部按钮(原右边按钮,现在移到顶部并旋转) |
|
|
|
Widget _buildFaceToFaceTopButton(bool isDarkMode) { |
|
|
|
return Center( |
|
|
|
child: Transform.rotate( |
|
|
|
angle: 0, // 180度旋转 |
|
|
|
child: _buildHoldToTalkButton( |
|
|
|
isDarkMode: isDarkMode, |
|
|
|
speakerId: 2, // 说话者2(原右边按钮) |
|
|
|
activeSpeaker: controller.activeSpeaker.value, |
|
|
|
icon: Icons.mic_none, |
|
|
|
label: controller.targetLanguage.value.split(' ')[0].tr, // 目标语言名称 |
|
|
|
), |
|
|
|
), |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
// 构建面对面翻译底部按钮(原左边按钮,保留在底部) |
|
|
|
Widget _buildFaceToFaceBottomButton(bool isDarkMode) { |
|
|
|
return Center( |
|
|
|
child: _buildHoldToTalkButton( |
|
|
|
isDarkMode: isDarkMode, |
|
|
|
speakerId: 1, // 说话者1(原左边按钮) |
|
|
|
activeSpeaker: controller.activeSpeaker.value, |
|
|
|
icon: Icons.mic_none, |
|
|
|
label: controller.sourceLanguage.value.split(' ')[0].tr, // 源语言名称 |
|
|
|
), |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
// 构建可拖拽的悬浮按钮 |
|
|
|
Widget _buildDraggableFloatingButton(bool isDarkMode) { |
|
|
|
return Obx(() => Positioned( |
|
|
|
right: controller.floatingButtonPosition.value.dx, // 使用保存的位置 |
|
|
|
bottom: controller.floatingButtonPosition.value.dy, // 使用保存的位置 |
|
|
|
child: Draggable( |
|
|
|
feedback: Material( |
|
|
|
color: Colors.transparent, |
|
|
|
child: Container( |
|
|
|
width: 56.w, |
|
|
|
height: 56.w, |
|
|
|
decoration: BoxDecoration( |
|
|
|
color: (isDarkMode ? Colors.grey[700] : Colors.blue) |
|
|
|
?.withOpacity(0.8), |
|
|
|
shape: BoxShape.circle, |
|
|
|
boxShadow: [ |
|
|
|
BoxShadow( |
|
|
|
color: Colors.black.withOpacity(0.3), |
|
|
|
blurRadius: 8, |
|
|
|
offset: const Offset(0, 4), |
|
|
|
), |
|
|
|
], |
|
|
|
), |
|
|
|
child: Icon( |
|
|
|
Icons.translate, |
|
|
|
color: Colors.white, |
|
|
|
size: 24.sp, |
|
|
|
), |
|
|
|
), |
|
|
|
), |
|
|
|
childWhenDragging: Container( |
|
|
|
width: 56.w, |
|
|
|
height: 56.w, |
|
|
|
decoration: BoxDecoration( |
|
|
|
color: (isDarkMode ? Colors.grey[700] : Colors.blue) |
|
|
|
?.withOpacity(0.3), |
|
|
|
shape: BoxShape.circle, |
|
|
|
), |
|
|
|
), |
|
|
|
onDragEnd: (details) { |
|
|
|
// 计算新位置(相对于屏幕右下角) |
|
|
|
final screenSize = MediaQuery.of(Get.context!).size; |
|
|
|
final newRight = screenSize.width - details.offset.dx - 56.w; |
|
|
|
final newBottom = screenSize.height - details.offset.dy - 56.w; |
|
|
|
|
|
|
|
// 边界检查,确保按钮不会超出屏幕 |
|
|
|
final clampedRight = newRight.clamp(0.0, screenSize.width - 56.w); |
|
|
|
final clampedBottom = |
|
|
|
newBottom.clamp(0.0, screenSize.height - 56.w); |
|
|
|
|
|
|
|
// 保存新位置 |
|
|
|
controller.saveFloatingButtonPosition( |
|
|
|
Offset(clampedRight, clampedBottom)); |
|
|
|
}, |
|
|
|
child: GestureDetector( |
|
|
|
onTap: () => _showTranslationModeSelector(isDarkMode), |
|
|
|
child: Container( |
|
|
|
width: 56.w, |
|
|
|
height: 56.w, |
|
|
|
decoration: BoxDecoration( |
|
|
|
color: isDarkMode ? Colors.grey[700] : Colors.blue, |
|
|
|
shape: BoxShape.circle, |
|
|
|
boxShadow: [ |
|
|
|
BoxShadow( |
|
|
|
color: Colors.black.withOpacity(0.2), |
|
|
|
blurRadius: 6, |
|
|
|
offset: const Offset(0, 3), |
|
|
|
), |
|
|
|
], |
|
|
|
), |
|
|
|
child: Icon( |
|
|
|
Icons.translate, |
|
|
|
color: Colors.white, |
|
|
|
size: 24.sp, |
|
|
|
), |
|
|
|
), |
|
|
|
), |
|
|
|
), |
|
|
|
)); |
|
|
|
} |
|
|
|
|
|
|
|
PreferredSizeWidget _buildAppBar(bool isDarkMode) { |
|
|
|
return AppBar( |
|
|
|
backgroundColor: isDarkMode ? AppColors.cardBackground : Colors.white, |
|
|
|
@ -260,6 +420,7 @@ class TranslationView extends GetView<TranslationController> { |
|
|
|
|
|
|
|
Widget _buildTranslationContent(bool isDarkMode) { |
|
|
|
return Obx(() { |
|
|
|
// 其他模式使用原有的列表布局 |
|
|
|
final itemCount = controller.translationHistory.length + |
|
|
|
(controller.hasMoreHistory.value ? 1 : 0); |
|
|
|
|
|
|
|
@ -299,6 +460,99 @@ class TranslationView extends GetView<TranslationController> { |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
/// 构建面对面翻译的对称布局 |
|
|
|
Widget _buildFaceToFaceLayout(bool isDarkMode) { |
|
|
|
return Obx(() { |
|
|
|
// 获取最新的翻译记录 |
|
|
|
final latestItem = controller.translationHistory.isNotEmpty |
|
|
|
? controller.translationHistory.last |
|
|
|
: null; |
|
|
|
|
|
|
|
return Column( |
|
|
|
children: [ |
|
|
|
// 上半部分 - (旋转180度) |
|
|
|
Expanded( |
|
|
|
child: Transform.rotate( |
|
|
|
angle: 3.14159, // 180度 = π弧度 |
|
|
|
child: Container( |
|
|
|
width: double.infinity, |
|
|
|
padding: EdgeInsets.all(24.w), |
|
|
|
decoration: BoxDecoration( |
|
|
|
color: isDarkMode |
|
|
|
? AppColors.cardBackground.withValues(alpha: 0.5) |
|
|
|
: Colors.blue[50]?.withValues(alpha: 0.3), |
|
|
|
border: Border( |
|
|
|
bottom: BorderSide( |
|
|
|
color: isDarkMode ? Colors.grey[700]! : Colors.grey[300]!, |
|
|
|
width: 1, |
|
|
|
), |
|
|
|
), |
|
|
|
), |
|
|
|
child: Column( |
|
|
|
mainAxisAlignment: MainAxisAlignment.center, |
|
|
|
children: [ |
|
|
|
// 上半部的文本 |
|
|
|
Text( |
|
|
|
latestItem?.targetLanguageCode == |
|
|
|
controller.targetLanguageCode.value |
|
|
|
? latestItem?.translatedText ?? '' //如果为null则显示空字符 |
|
|
|
: latestItem?.sourceText ?? '', |
|
|
|
style: TextStyle( |
|
|
|
fontSize: 20.sp, |
|
|
|
fontWeight: FontWeight.w500, |
|
|
|
color: isDarkMode ? Colors.white : Colors.black87, |
|
|
|
height: 1.4, |
|
|
|
), |
|
|
|
textAlign: TextAlign.center, |
|
|
|
), |
|
|
|
], |
|
|
|
), |
|
|
|
), |
|
|
|
), |
|
|
|
), |
|
|
|
|
|
|
|
// 语言选择栏 |
|
|
|
_buildLanguageSelectionBar(isDarkMode), |
|
|
|
|
|
|
|
// 下半部分 - (正常方向) |
|
|
|
Expanded( |
|
|
|
child: Container( |
|
|
|
width: double.infinity, |
|
|
|
padding: EdgeInsets.all(24.w), |
|
|
|
decoration: BoxDecoration( |
|
|
|
color: isDarkMode |
|
|
|
? AppColors.cardBackground.withValues(alpha: 0.5) |
|
|
|
: Colors.green[50]?.withValues(alpha: 0.3), |
|
|
|
), |
|
|
|
child: Column( |
|
|
|
mainAxisAlignment: MainAxisAlignment.center, |
|
|
|
children: [ |
|
|
|
// 下半部的文本 |
|
|
|
Text( |
|
|
|
latestItem?.sourceLanguageCode == |
|
|
|
controller.sourceLanguageCode.value |
|
|
|
? latestItem?.sourceText ?? |
|
|
|
(controller.faceToFaceRecognitionText.value.isEmpty |
|
|
|
? '' |
|
|
|
: controller.faceToFaceRecognitionText.value) |
|
|
|
: latestItem?.translatedText ?? '', |
|
|
|
style: TextStyle( |
|
|
|
fontSize: 20.sp, |
|
|
|
fontWeight: FontWeight.w500, |
|
|
|
color: isDarkMode ? Colors.white : Colors.black87, |
|
|
|
height: 1.4, |
|
|
|
), |
|
|
|
textAlign: TextAlign.center, |
|
|
|
), |
|
|
|
], |
|
|
|
), |
|
|
|
), |
|
|
|
), |
|
|
|
], |
|
|
|
); |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
Widget _buildTranslationItemWidget( |
|
|
|
BuildContext context, TranslationItem item) { |
|
|
|
return Container( |
|
|
|
@ -1201,4 +1455,106 @@ class TranslationView extends GetView<TranslationController> { |
|
|
|
), |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
// 构建语言选择栏 |
|
|
|
Widget _buildLanguageSelectionBar(bool isDarkMode) { |
|
|
|
return Container( |
|
|
|
height: 44.h, |
|
|
|
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 6.h), |
|
|
|
decoration: BoxDecoration( |
|
|
|
color: isDarkMode ? AppColors.cardBackground : Colors.white, |
|
|
|
boxShadow: [ |
|
|
|
BoxShadow( |
|
|
|
color: Colors.black.withOpacity(0.1), |
|
|
|
blurRadius: 4, |
|
|
|
offset: const Offset(0, 2), |
|
|
|
), |
|
|
|
BoxShadow( |
|
|
|
color: Colors.black.withOpacity(0.1), |
|
|
|
blurRadius: 4, |
|
|
|
offset: const Offset(0, -2), |
|
|
|
), |
|
|
|
], |
|
|
|
), |
|
|
|
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: Text( |
|
|
|
'${controller.sourceLanguage.value.split(' ')[0]}'.tr, |
|
|
|
style: TextStyle( |
|
|
|
fontSize: 15.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: Transform.rotate( |
|
|
|
angle: 3.14159, // 旋转180度 |
|
|
|
child: Text( |
|
|
|
'${controller.targetLanguage.value.split(' ')[0]}'.tr, |
|
|
|
style: TextStyle( |
|
|
|
fontSize: 15.sp, |
|
|
|
fontWeight: FontWeight.w600, |
|
|
|
color: isDarkMode ? Colors.white : Colors.black, |
|
|
|
), |
|
|
|
textAlign: TextAlign.center, |
|
|
|
overflow: TextOverflow.ellipsis, |
|
|
|
), |
|
|
|
), |
|
|
|
), |
|
|
|
), |
|
|
|
), |
|
|
|
], |
|
|
|
), |
|
|
|
); |
|
|
|
} |
|
|
|
} |
|
|
|
|