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