You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
172 lines
5.7 KiB
172 lines
5.7 KiB
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!,
|
|
],
|
|
],
|
|
);
|
|
}),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|