9 changed files with 412 additions and 610 deletions
@ -0,0 +1,172 @@ |
|||
import 'package:flutter/material.dart'; |
|||
import 'package:get/get.dart'; |
|||
import 'package:flutter_screenutil/flutter_screenutil.dart'; |
|||
import '../../../core/theme/app_colors.dart'; |
|||
|
|||
/// 通用语言选择栏组件 |
|||
/// - 统一源/目标语言胶囊样式与交换按钮样式 |
|||
/// - 通过函数读取语言显示文本,以便模块内保持最小改动 |
|||
/// - 文案默认走 .tr,本地化显示语言名称(翻译键代表的中文文本内容) |
|||
class LanguageSelectorBar extends StatelessWidget { |
|||
const LanguageSelectorBar({ |
|||
Key? key, |
|||
required this.sourceText, |
|||
required this.targetText, |
|||
required this.onTapSource, |
|||
required this.onTapTarget, |
|||
required this.onSwap, |
|||
this.translateLanguageNames = true, |
|||
this.fontSizeSp = 15, |
|||
this.sourceSuffix, |
|||
this.targetSuffix, |
|||
}) : super(key: key); |
|||
|
|||
/// 返回源语言显示文本(建议返回语言中文名或翻译键) |
|||
final String Function() sourceText; |
|||
|
|||
/// 返回目标语言显示文本(建议返回语言中文名或翻译键) |
|||
final String Function() targetText; |
|||
|
|||
/// 源语言点击事件(打开语言选择弹窗) |
|||
final VoidCallback onTapSource; |
|||
|
|||
/// 目标语言点击事件(打开语言选择弹窗) |
|||
final VoidCallback onTapTarget; |
|||
|
|||
/// 语言交换事件 |
|||
final VoidCallback onSwap; |
|||
|
|||
/// 是否将语言名称作为翻译键进行翻译(翻译键代表的中文文本内容) |
|||
final bool translateLanguageNames; |
|||
|
|||
/// 字号(sp单位),默认 15,与语音翻译一致 |
|||
final double fontSizeSp; |
|||
|
|||
/// 源语言后缀(可选),用于追加图标等 |
|||
final Widget? sourceSuffix; |
|||
|
|||
/// 目标语言后缀(可选),用于追加图标等 |
|||
final Widget? targetSuffix; |
|||
|
|||
@override |
|||
Widget build(BuildContext context) { |
|||
final isDarkMode = Get.isDarkMode; |
|||
final capsuleBg = |
|||
isDarkMode ? Colors.white.withOpacity(0.1) : Colors.grey[100]; |
|||
final textColor = isDarkMode ? Colors.white : Colors.black; |
|||
final swapBg = |
|||
isDarkMode ? AppColors.primary.withOpacity(0.2) : Colors.blue[50]; |
|||
final swapIconColor = isDarkMode ? AppColors.primary : Colors.blue; |
|||
|
|||
// 规范化特殊项的翻译键,例如:自动检测 -> autoDetect(翻译键代表的中文文本内容) |
|||
String _normalizeKey(String text) { |
|||
switch (text) { |
|||
case '自动检测': |
|||
return 'autoDetect'; |
|||
default: |
|||
return text; // 其他保持原值,由 .tr 决定是否命中 |
|||
} |
|||
} |
|||
|
|||
return Row( |
|||
mainAxisAlignment: MainAxisAlignment.center, |
|||
children: [ |
|||
// 源语言胶囊 |
|||
Expanded( |
|||
child: GestureDetector( |
|||
onTap: onTapSource, |
|||
child: Container( |
|||
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h), |
|||
decoration: BoxDecoration( |
|||
color: capsuleBg, |
|||
borderRadius: BorderRadius.circular(16.r), |
|||
), |
|||
child: Obx(() { |
|||
final raw = sourceText(); |
|||
final text = translateLanguageNames ? _normalizeKey(raw) : raw; |
|||
return Row( |
|||
mainAxisAlignment: MainAxisAlignment.center, |
|||
children: [ |
|||
Flexible( |
|||
child: Text( |
|||
translateLanguageNames ? text.tr : text, |
|||
style: TextStyle( |
|||
fontSize: fontSizeSp.sp, |
|||
fontWeight: FontWeight.w600, |
|||
color: textColor, |
|||
), |
|||
textAlign: TextAlign.center, |
|||
overflow: TextOverflow.ellipsis, |
|||
), |
|||
), |
|||
if (sourceSuffix != null) ...[ |
|||
SizedBox(width: 4.w), |
|||
sourceSuffix!, |
|||
], |
|||
], |
|||
); |
|||
}), |
|||
), |
|||
), |
|||
), |
|||
|
|||
// 语言互换按钮 |
|||
GestureDetector( |
|||
onTap: onSwap, |
|||
child: Container( |
|||
margin: EdgeInsets.symmetric(horizontal: 8.w), |
|||
padding: EdgeInsets.all(4.r), |
|||
decoration: BoxDecoration( |
|||
color: swapBg, |
|||
shape: BoxShape.circle, |
|||
), |
|||
child: Icon( |
|||
Icons.swap_horiz, |
|||
color: swapIconColor, |
|||
size: 20.sp, |
|||
), |
|||
), |
|||
), |
|||
|
|||
// 目标语言胶囊 |
|||
Expanded( |
|||
child: GestureDetector( |
|||
onTap: onTapTarget, |
|||
child: Container( |
|||
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h), |
|||
decoration: BoxDecoration( |
|||
color: capsuleBg, |
|||
borderRadius: BorderRadius.circular(16.r), |
|||
), |
|||
child: Obx(() { |
|||
final raw = targetText(); |
|||
final text = translateLanguageNames ? _normalizeKey(raw) : raw; |
|||
return Row( |
|||
mainAxisAlignment: MainAxisAlignment.center, |
|||
children: [ |
|||
Flexible( |
|||
child: Text( |
|||
translateLanguageNames ? text.tr : text, |
|||
style: TextStyle( |
|||
fontSize: fontSizeSp.sp, |
|||
fontWeight: FontWeight.w600, |
|||
color: textColor, |
|||
), |
|||
textAlign: TextAlign.center, |
|||
overflow: TextOverflow.ellipsis, |
|||
), |
|||
), |
|||
if (targetSuffix != null) ...[ |
|||
SizedBox(width: 4.w), |
|||
targetSuffix!, |
|||
], |
|||
], |
|||
); |
|||
}), |
|||
), |
|||
), |
|||
), |
|||
], |
|||
); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue