Browse Source

Merge branch 'new_dev' of https://github.com/deepcloud2048/deep_voice into new_dev

newdev_shunjiawei
liwei1dao 1 year ago
parent
commit
7f427f88e8
  1. 1
      lib/data/models/appconfig.dart
  2. 2
      lib/data/models/appconfig_model.dart
  3. 1
      lib/data/models/appconfig_model.g.dart
  4. 95
      lib/modules/home/controllers/home_controller.dart
  5. 58
      lib/modules/home/views/home_view.dart
  6. 52
      lib/modules/login/controllers/login_controller.dart
  7. 18
      lib/modules/ota_upgrade/controllers/ota_upgrade_controller.dart
  8. 43
      lib/modules/ota_upgrade/views/ota_upgrade_view.dart
  9. 4
      lib/modules/translation/bindings/translation_binding.dart
  10. 6
      lib/modules/translation/bindings/translation_detail_binding.dart
  11. 1
      lib/modules/translation/bindings/translation_history_binding.dart
  12. 1
      lib/modules/translation/bindings/translation_list_binding.dart
  13. 3
      lib/modules/translation/bindings/translation_stats_binding.dart
  14. 45
      lib/modules/translation/controllers/translation_controller.dart
  15. 12
      lib/modules/translation/models/translation_models.dart
  16. 161
      lib/modules/translation/views/dialogs/language_selection_dialog.dart
  17. 172
      lib/modules/translation/views/dialogs/translation_mode_selector.dart
  18. 480
      lib/modules/translation/views/translation_view.dart
  19. 103
      lib/modules/translation/views/widgets/draggable_floating_button.dart
  20. 219
      lib/modules/translation/views/widgets/face_to_face_layout.dart

1
lib/data/models/appconfig.dart

@ -47,6 +47,7 @@ class AppConfig {
return uuids; return uuids;
} }
//根据产品名称获取产品配置
static DBProduct? getproduct(String name) { static DBProduct? getproduct(String name) {
final foundProduct = final foundProduct =
config.products.firstWhere((product) => product.devicename == name); config.products.firstWhere((product) => product.devicename == name);

2
lib/data/models/appconfig_model.dart

@ -75,6 +75,7 @@ class DBProduct {
final int factoryid; final int factoryid;
final int devicetype; final int devicetype;
final String devicename; final String devicename;
final String? productimage;
final String scanuuid; final String scanuuid;
final String? version; final String? version;
final String? updatedescription; final String? updatedescription;
@ -85,6 +86,7 @@ class DBProduct {
required this.factoryid, required this.factoryid,
required this.devicetype, required this.devicetype,
required this.devicename, required this.devicename,
required this.productimage,
required this.scanuuid, required this.scanuuid,
required this.version, required this.version,
required this.updatedescription, required this.updatedescription,

1
lib/data/models/appconfig_model.g.dart

@ -76,6 +76,7 @@ DBProduct _$DBProductFromJson(Map<String, dynamic> json) => DBProduct(
factoryid: (json['factoryid'] as num).toInt(), factoryid: (json['factoryid'] as num).toInt(),
devicename: json['devicename'] as String, devicename: json['devicename'] as String,
devicetype: json['devicetype'] as int, devicetype: json['devicetype'] as int,
productimage: json['productimage'] as String?,
scanuuid: json['scanuuid'] as String, scanuuid: json['scanuuid'] as String,
version: json['version'] as String?, version: json['version'] as String?,
updatedescription: json['updatedescription'] as String?, updatedescription: json['updatedescription'] as String?,

95
lib/modules/home/controllers/home_controller.dart

@ -22,8 +22,12 @@ enum ConnectionStatus {
/// 主页控制器,管理设备连接状态和信息展示 /// 主页控制器,管理设备连接状态和信息展示
class HomeController extends GetxController { class HomeController extends GetxController {
static const String _tag = 'HomeCtrl'; static const String _tag = 'HomeCtrl';
static const String _lastDeviceNameKey = 'last_connected_device_name'; // 存储键
static const String _lastDeviceImageKey =
'last_connected_device_image'; // 存储上一次设备图片的键
static HomeController get to => Get.find(); static HomeController get to => Get.find();
final GetStorage _storage = GetStorage(); final GetStorage _storage = GetStorage();
String? _lastConnectedDeviceImage; // 新增:存储上一次连接设备的图片链接
final _isFullyConnected = false.obs; final _isFullyConnected = false.obs;
String? _lastConnectedDeviceName; String? _lastConnectedDeviceName;
@ -98,6 +102,7 @@ class HomeController extends GetxController {
void onInit() { void onInit() {
super.onInit(); super.onInit();
_otaCheckService = Get.find<OtaCheckService>(); _otaCheckService = Get.find<OtaCheckService>();
_loadLastConnectedDeviceName(); // 加载持久化的设备名称
_initWithCachedInfo(); _initWithCachedInfo();
// 移除了电池信息定时刷新的启动 // 移除了电池信息定时刷新的启动
} }
@ -132,7 +137,7 @@ class HomeController extends GetxController {
_classicBluetoothWorker = _classicBluetoothWorker =
ever(_bluetoothManager.currentDeviceRx, (BluetoothDevice? device) { ever(_bluetoothManager.currentDeviceRx, (BluetoothDevice? device) {
if (device != null) { if (device != null) {
_lastConnectedDeviceName = device.name; _saveLastConnectedDeviceName(device.name); // 使用新的保存方法
} }
_checkConnectionStatus(); _checkConnectionStatus();
}); });
@ -142,6 +147,28 @@ class HomeController extends GetxController {
}); });
} }
/// 从本地存储加载最后连接的设备名称
void _loadLastConnectedDeviceName() {
_lastConnectedDeviceName = _storage.read(_lastDeviceNameKey);
Logger.d(_tag, '加载最后连接的设备名称: $_lastConnectedDeviceName');
}
/// 保存最后连接的设备名称到本地存储
void _saveLastConnectedDeviceName(String deviceName) {
_lastConnectedDeviceName = deviceName;
_storage.write(_lastDeviceNameKey, deviceName);
Logger.d(_tag, '保存最后连接的设备名称: $deviceName');
}
/// 保存上一次连接设备的图片链接到本地存储
void _saveLastConnectedDeviceImage(String? imageUrl) {
if (imageUrl != null && imageUrl.isNotEmpty) {
_lastConnectedDeviceImage = imageUrl;
_storage.write(_lastDeviceImageKey, imageUrl);
Logger.d(_tag, '保存上一次连接设备的图片: $imageUrl');
}
}
Future<void> refreshDeviceConnection() async { Future<void> refreshDeviceConnection() async {
await _checkConnectionStatus(); await _checkConnectionStatus();
@ -235,6 +262,7 @@ class HomeController extends GetxController {
} }
final deviceName = currentDevice?.name; final deviceName = currentDevice?.name;
//final deviceName = '102';
if (deviceName != null && deviceName.isNotEmpty) { if (deviceName != null && deviceName.isNotEmpty) {
return deviceName; return deviceName;
} }
@ -404,4 +432,69 @@ class HomeController extends GetxController {
Map<String, dynamic> getOtaCheckStatus() { Map<String, dynamic> getOtaCheckStatus() {
return _otaCheckService.getCheckStatus(); return _otaCheckService.getCheckStatus();
} }
/// 获取当前设备的产品图片路径
/// 如果没有配置产品图片或获取失败,返回null使用默认图片
String? getCurrentDeviceImage() {
try {
final deviceName = getDeviceName();
if (deviceName == 'unknownEquipment'.tr) {
Logger.d(_tag, '设备未知,使用默认图片');
return null;
}
// 通过AppConfig获取产品信息
if (!AppConfig.isInitialized()) {
Logger.w(_tag, 'AppConfig未初始化,使用默认图片');
return null;
}
final product = AppConfig.getproduct(deviceName);
if (product != null &&
product.productimage != null &&
product.productimage!.isNotEmpty) {
Logger.d(_tag, '获取到设备产品图片: ${product.productimage}');
_saveLastConnectedDeviceImage(product.productimage);
return product.productimage;
} else {
Logger.d(_tag, '设备 "$deviceName" 未配置产品图片,使用默认图片');
return null;
}
} catch (e) {
Logger.e(_tag, '获取设备产品图片失败: $e');
return null;
}
}
/// 获取设备图片的完整资源路径
/// 处理网络图片URL和本地资源路径
String getDeviceImagePath(bool isDarkMode) {
final productImage = getCurrentDeviceImage();
if (productImage != null) {
// 如果是网络图片URL
if (productImage.startsWith('http://') ||
productImage.startsWith('https://')) {
return productImage;
}
// 如果是本地资源路径,确保以assets/开头
else if (productImage.startsWith('assets/')) {
return productImage;
}
// 如果只是文件名,添加assets/images/前缀
else {
return 'assets/images/$productImage';
}
}
// 使用默认耳机图片
return isDarkMode
? 'assets/images/headphone_new3.png'
: 'assets/images/headphone_new4.png';
}
/// 判断是否为网络图片
bool isNetworkImage(String imagePath) {
return imagePath.startsWith('http://') || imagePath.startsWith('https://');
}
} }

58
lib/modules/home/views/home_view.dart

@ -220,12 +220,58 @@ class HomeView extends GetView<HomeController> {
child: ClipRRect( child: ClipRRect(
// 添加 ClipRRect // 添加 ClipRRect
borderRadius: BorderRadius.circular(16.r), borderRadius: BorderRadius.circular(16.r),
child: Image.asset( child: GetBuilder<HomeController>(
isDarkMode builder: (controller) {
? 'assets/images/headphone_new3.png' final imagePath = controller.getDeviceImagePath(isDarkMode);
: 'assets/images/headphone_new4.png', final isNetwork = controller.isNetworkImage(imagePath);
height: 220.h,
fit: BoxFit.contain, return isNetwork
? Image.network(
imagePath,
height: 220.h,
fit: BoxFit.contain,
errorBuilder: (context, error, stackTrace) {
// 网络图片加载失败时使用默认图片
return Image.asset(
isDarkMode
? 'assets/images/headphone_new3.png'
: 'assets/images/headphone_new4.png',
height: 220.h,
fit: BoxFit.contain,
);
},
loadingBuilder: (context, child, loadingProgress) {
if (loadingProgress == null) return child;
return SizedBox(
height: 220.h,
child: Center(
child: CircularProgressIndicator(
value: loadingProgress.expectedTotalBytes !=
null
? loadingProgress.cumulativeBytesLoaded /
loadingProgress.expectedTotalBytes!
: null,
),
),
);
},
)
: Image.asset(
imagePath,
height: 220.h,
fit: BoxFit.contain,
errorBuilder: (context, error, stackTrace) {
// 本地图片加载失败时使用默认图片
return Image.asset(
isDarkMode
? 'assets/images/headphone_new3.png'
: 'assets/images/headphone_new4.png',
height: 220.h,
fit: BoxFit.contain,
);
},
);
},
), ),
), ),
), ),

52
lib/modules/login/controllers/login_controller.dart

@ -932,7 +932,59 @@ class LoginController extends GetxController {
Logger().d('getAppConfig:进来了'); Logger().d('getAppConfig:进来了');
// 根据API设计调整 // 根据API设计调整
final resp = UserGetAppConfigResp.fromJson(response); final resp = UserGetAppConfigResp.fromJson(response);
// 详细记录解析后的配置信息
Logger().d('getAppConfig: 配置解析成功');
Logger().d('getAppConfig: 环境变量数量 = ${resp.env.length}');
Logger().d('getAppConfig: 智能体数量 = ${resp.agents.length}');
Logger().d('getAppConfig: MCP服务器数量 = ${resp.mcps.length}');
Logger().d('getAppConfig: 产品设备数量 = ${resp.products.length}');
// 详细记录产品设备信息
if (resp.products.isNotEmpty) {
Logger().d('getAppConfig: ========== 产品设备详细信息 ==========');
for (int i = 0; i < resp.products.length; i++) {
final product = resp.products[i];
Logger().d(
'getAppConfig: 产品[$i] ID=${product.id}, 设备名称="${product.devicename}"');
Logger().d(
'getAppConfig: 产品[$i] 厂商ID=${product.factoryid}, 设备类型=${product.devicetype}');
Logger().d('getAppConfig: 产品[$i] 扫描UUID="${product.scanuuid}"');
Logger().d(
'getAppConfig: 产品[$i] 产品图片="${product.productimage ?? "未设置"}"');
Logger().d('getAppConfig: 产品[$i] 版本="${product.version ?? "未设置"}"');
Logger().d(
'getAppConfig: 产品[$i] 更新描述="${product.updatedescription ?? "未设置"}"');
Logger().d(
'getAppConfig: 产品[$i] 更新包地址="${product.updatepackageaddress ?? "未设置"}"');
Logger().d('getAppConfig: ----------------------------------------');
}
Logger().d('getAppConfig: ========== 产品设备信息结束 ==========');
} else {
Logger().w('getAppConfig: 警告 - 未获取到任何产品设备信息');
}
// 记录环境变量信息
if (resp.env.isNotEmpty) {
Logger().d('getAppConfig: ========== 环境变量信息 ==========');
resp.env.forEach((key, value) {
Logger().d('getAppConfig: 环境变量 $key = "$value"');
});
Logger().d('getAppConfig: ========== 环境变量信息结束 ==========');
}
AppConfig.initialize(resp); AppConfig.initialize(resp);
Logger().d(
'getAppConfig: AppConfig初始化完成,产品数量验证 = ${AppConfig.config.products.length}');
// 验证AppConfig中的产品数据
final productNames = AppConfig.allproductNames();
final productUuids = AppConfig.allproductUuids();
Logger().d('getAppConfig: 可用产品名称列表 = $productNames');
Logger().d('getAppConfig: 可用产品UUID列表 = $productUuids');
Logger().d('getAppConfig: 应用配置获取和初始化成功完成');
return true; return true;
} catch (e) { } catch (e) {
Logger().d('getAppConfig 取失败: $e'); Logger().d('getAppConfig 取失败: $e');

18
lib/modules/ota_upgrade/controllers/ota_upgrade_controller.dart

@ -1,12 +1,14 @@
import 'package:classic_bluetooth/classic_bluetooth.dart'; import 'package:classic_bluetooth/classic_bluetooth.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:logger/web.dart'; // import 'package:logger/web.dart';
import 'package:get_storage/get_storage.dart';
import 'package:ota/ota.dart'; import 'package:ota/ota.dart';
import '../../../data/services/ble_manager.dart'; import '../../../data/services/ble_manager.dart';
import 'ota_core_contropller.dart'; import 'ota_core_contropller.dart';
import 'ota_download_contropller.dart'; import 'ota_download_contropller.dart';
import '../views/ota_upgrade_popup_view.dart'; import '../views/ota_upgrade_popup_view.dart';
import '../../../core/utils/logger.dart';
/// OTA升级主控制器 - 重构后的简化版本 /// OTA升级主控制器 - 重构后的简化版本
/// 负责协调各个服务,管理升级流程的整体状态 /// 负责协调各个服务,管理升级流程的整体状态
@ -33,13 +35,18 @@ class OtaUpgradeController extends GetxController {
final RxBool isFetchingDeviceInfo = false.obs; final RxBool isFetchingDeviceInfo = false.obs;
final RxBool showDetailedStatus = false.obs; final RxBool showDetailedStatus = false.obs;
final RxBool isBleConnected = true.obs; final RxBool isBleConnected = true.obs;
final GetStorage _storage = GetStorage();
String _address = ''; String _address = '';
String _updatePackageAddress = ''; String _updatePackageAddress = '';
String? _lastConnectedDeviceImage; // 新增:存储上一次连接设备的图片链接
get lastConnectedDeviceImage => _lastConnectedDeviceImage;
@override @override
void onInit() { void onInit() {
super.onInit(); super.onInit();
_loadLastConnectedDeviceImage(); // 加载持久化的设备图片
_initializeController(); _initializeController();
} }
@ -51,6 +58,13 @@ class OtaUpgradeController extends GetxController {
super.onClose(); super.onClose();
} }
/// 从本地存储加载上一次连接设备的图片链接
void _loadLastConnectedDeviceImage() {
_lastConnectedDeviceImage = _storage.read('last_connected_device_name');
Logger.d(
'OtaUpgradeController', '加载上一次连接设备的图片: $_lastConnectedDeviceImage');
}
/// 初始化控制器 /// 初始化控制器
/// 设置事件监听并加载版本信息 /// 设置事件监听并加载版本信息
Future<void> _initializeController() async { Future<void> _initializeController() async {
@ -169,7 +183,7 @@ class OtaUpgradeController extends GetxController {
detailedStatus.value = '下载失败,请检查网络连接'; detailedStatus.value = '下载失败,请检查网络连接';
showDetailedStatus.value = false; showDetailedStatus.value = false;
_handleError('下载失败', e.toString()); _handleError('下载失败', e.toString());
Logger().d('下载失败: $e'); Logger.d('OtaUpgradeController', '下载失败: $e');
} }
} }

43
lib/modules/ota_upgrade/views/ota_upgrade_view.dart

@ -185,12 +185,43 @@ class OtaUpgradeView extends GetView<OtaUpgradeController> {
width: 1, width: 1,
), ),
), ),
child: Image.asset( child: GetBuilder<OtaUpgradeController>(
isDarkMode builder: (controller) {
? 'assets/images/headphone_new3.png' final imagePath = controller.lastConnectedDeviceImage;
: 'assets/images/headphone_new4.png',
height: 100.h, // 网络图片
fit: BoxFit.contain, return Image.network(
imagePath,
height: 100.h,
fit: BoxFit.contain,
loadingBuilder: (context, child, loadingProgress) {
if (loadingProgress == null) return child;
return SizedBox(
height: 100.h,
child: Center(
child: CircularProgressIndicator(
value: loadingProgress.expectedTotalBytes != null
? loadingProgress.cumulativeBytesLoaded /
loadingProgress.expectedTotalBytes!
: null,
strokeWidth: 2,
color: isDarkMode ? Colors.white : Colors.blue,
),
),
);
},
errorBuilder: (context, error, stackTrace) {
// 网络图片加载失败时使用默认图片
return Image.asset(
isDarkMode
? 'assets/images/headphone_new3.png'
: 'assets/images/headphone_new4.png',
height: 100.h,
fit: BoxFit.contain,
);
},
);
},
), ),
); );
} }

4
lib/modules/translation/bindings/translation_binding.dart

@ -1,10 +1,10 @@
import 'package:get/get.dart'; import 'package:get/get.dart';
import '../controllers/translation_controller.dart'; import '../controllers/translation_controller.dart';
//主翻译页面依赖绑定
class TranslationBinding implements Bindings { class TranslationBinding implements Bindings {
@override @override
void dependencies() { void dependencies() {
Get.put(TranslationController()); Get.put(TranslationController());
} }
} }

6
lib/modules/translation/bindings/translation_detail_binding.dart

@ -1,9 +1,11 @@
import 'package:get/get.dart'; import 'package:get/get.dart';
import '../controllers/translation_detail_controller.dart'; import '../controllers/translation_detail_controller.dart';
//翻译详情页面依赖绑定
class TranslationDetailBinding extends Bindings { class TranslationDetailBinding extends Bindings {
@override @override
void dependencies() { void dependencies() {
Get.lazyPut<TranslationDetailController>(() => TranslationDetailController()); Get.lazyPut<TranslationDetailController>(
() => TranslationDetailController());
} }
} }

1
lib/modules/translation/bindings/translation_history_binding.dart

@ -1,6 +1,7 @@
import 'package:get/get.dart'; import 'package:get/get.dart';
import '../controllers/translation_audiohistory_controller.dart'; import '../controllers/translation_audiohistory_controller.dart';
//翻译历史记录页面依赖绑定
class TranslationHistoryBinding extends Bindings { class TranslationHistoryBinding extends Bindings {
@override @override
void dependencies() { void dependencies() {

1
lib/modules/translation/bindings/translation_list_binding.dart

@ -1,6 +1,7 @@
import 'package:get/get.dart'; import 'package:get/get.dart';
import '../controllers/translation_list_controller.dart'; import '../controllers/translation_list_controller.dart';
//翻译列表页面依赖绑定
class TranslationListBinding extends Bindings { class TranslationListBinding extends Bindings {
@override @override
void dependencies() { void dependencies() {

3
lib/modules/translation/bindings/translation_stats_binding.dart

@ -2,6 +2,7 @@ import 'package:get/get.dart';
import '../controllers/translation_stats_controller.dart'; import '../controllers/translation_stats_controller.dart';
import '../../../data/services/translation_usage_service.dart'; import '../../../data/services/translation_usage_service.dart';
//翻译统计页面依赖绑定
class TranslationStatsBinding implements Bindings { class TranslationStatsBinding implements Bindings {
@override @override
void dependencies() { void dependencies() {
@ -9,7 +10,7 @@ class TranslationStatsBinding implements Bindings {
if (!Get.isRegistered<TranslationUsageService>()) { if (!Get.isRegistered<TranslationUsageService>()) {
Get.put(TranslationUsageService(), permanent: true); Get.put(TranslationUsageService(), permanent: true);
} }
// 注册控制器 // 注册控制器
Get.lazyPut<TranslationStatsController>( Get.lazyPut<TranslationStatsController>(
() => TranslationStatsController(), () => TranslationStatsController(),

45
lib/modules/translation/controllers/translation_controller.dart

@ -31,7 +31,7 @@ enum AudioOutputDevice {
speaker, // 强制扬声器 speaker, // 强制扬声器
} }
/// 翻译控制器 - 负责管理语音识别、翻译和TTS功能(主控制器) /// 翻译主控制器 - 负责管理语音识别、翻译和TTS功能(主控制器)
class TranslationController extends GetxController with WidgetsBindingObserver { class TranslationController extends GetxController with WidgetsBindingObserver {
static const String TAG = 'TranslationController'; static const String TAG = 'TranslationController';
@ -62,6 +62,8 @@ class TranslationController extends GetxController with WidgetsBindingObserver {
// 存储相关配置 // 存储相关配置
static const String _sourceLanguageKey = 'translation_source_language'; static const String _sourceLanguageKey = 'translation_source_language';
static const String _targetLanguageKey = 'translation_target_language'; static const String _targetLanguageKey = 'translation_target_language';
// 添加悬浮按钮位置存储键
static const String _floatingButtonPositionKey = 'floating_button_position';
// 分页加载相关状态 // 分页加载相关状态
final pageSize = 20; // 每页记录数量 final pageSize = 20; // 每页记录数量
@ -77,6 +79,9 @@ class TranslationController extends GetxController with WidgetsBindingObserver {
final isCreateRecord = false.obs; final isCreateRecord = false.obs;
final hasRecordPermission = false.obs; final hasRecordPermission = false.obs;
// 添加悬浮按钮位置状态
final floatingButtonPosition = Rx<Offset>(Offset(2.0, 100.0)); // 默认位置
// ==================== 语言相关 ==================== // ==================== 语言相关 ====================
final sourceLanguage = '中文(简体)'.obs; final sourceLanguage = '中文(简体)'.obs;
final targetLanguage = '英语'.obs; final targetLanguage = '英语'.obs;
@ -319,6 +324,28 @@ class TranslationController extends GetxController with WidgetsBindingObserver {
supportedLanguages.containsKey(savedTargetLanguage)) { supportedLanguages.containsKey(savedTargetLanguage)) {
targetLanguage.value = savedTargetLanguage; targetLanguage.value = savedTargetLanguage;
} }
// 加载悬浮按钮位置
_loadFloatingButtonPosition();
}
/// 加载悬浮按钮位置
void _loadFloatingButtonPosition() {
final savedPosition = _storage.read(_floatingButtonPositionKey);
if (savedPosition != null && savedPosition is Map) {
final dx = savedPosition['dx']?.toDouble() ?? 2.0;
final dy = savedPosition['dy']?.toDouble() ?? 100.0;
floatingButtonPosition.value = Offset(dx, dy);
}
}
/// 保存悬浮按钮位置
void saveFloatingButtonPosition(Offset position) {
floatingButtonPosition.value = position;
_storage.write(_floatingButtonPositionKey, {
'dx': position.dx,
'dy': position.dy,
});
} }
/// 保存当前语言设置 /// 保存当前语言设置
@ -686,6 +713,8 @@ class TranslationController extends GetxController with WidgetsBindingObserver {
handleFinalResult(event.text); handleFinalResult(event.text);
} else { } else {
faceToFaceRecognitionText.value += event.text; faceToFaceRecognitionText.value += event.text;
// 添加日志打印
Logger.info('lxm-面对面翻译识别文本更新: ${faceToFaceRecognitionText.value}');
} }
if (currentMode.value == 'faceToFace' && activeSpeaker.value == 0) { if (currentMode.value == 'faceToFace' && activeSpeaker.value == 0) {
handleFinalResult(faceToFaceRecognitionText.value); handleFinalResult(faceToFaceRecognitionText.value);
@ -955,6 +984,20 @@ class TranslationController extends GetxController with WidgetsBindingObserver {
translationHistory[index].targetLanguageCode = languageCodes['target']!; translationHistory[index].targetLanguageCode = languageCodes['target']!;
translationHistory.refresh(); translationHistory.refresh();
// 添加TranslationItem日志打印
if (isFinal) {
final item = translationHistory[index];
Logger.info('lxm-翻译完成 - TranslationItem详情: '
'源文本: ${item.sourceText}, '
'翻译文本: ${item.translatedText}, '
'源语言: ${item.sourceLanguageCode}, '
'目标语言: ${item.targetLanguageCode}, '
'时间戳: ${item.timestamp.toIso8601String()}, '
'会话ID: ${item.sessionId}, '
'是否会话首项: ${item.isFirstInSession}, '
'是否中间结果: ${item.isIntermediate}');
}
if (isFinal) { if (isFinal) {
saveTranslationHistory(); saveTranslationHistory();
await _recordTranslationStats( await _recordTranslationStats(

12
lib/modules/translation/models/translation_models.dart

@ -12,7 +12,7 @@ class TranslationItem {
String sessionId; String sessionId;
final bool isFirstInSession; final bool isFirstInSession;
bool isIntermediate; // 是否为中间结果 bool isIntermediate; // 是否为中间结果
TranslationItem({ TranslationItem({
required this.sourceText, required this.sourceText,
required this.translatedText, required this.translatedText,
@ -23,7 +23,7 @@ class TranslationItem {
this.isFirstInSession = false, this.isFirstInSession = false,
this.isIntermediate = false, this.isIntermediate = false,
}); });
/// 从JSON创建TranslationItem实例 /// 从JSON创建TranslationItem实例
factory TranslationItem.fromJson(Map<String, dynamic> json) { factory TranslationItem.fromJson(Map<String, dynamic> json) {
DateTime timestamp; DateTime timestamp;
@ -32,7 +32,7 @@ class TranslationItem {
} catch (e) { } catch (e) {
timestamp = DateTime.now(); timestamp = DateTime.now();
} }
return TranslationItem( return TranslationItem(
sourceText: json['sourceText'] ?? '', sourceText: json['sourceText'] ?? '',
translatedText: json['translatedText'] ?? '', translatedText: json['translatedText'] ?? '',
@ -45,7 +45,7 @@ class TranslationItem {
isIntermediate: false, // 从存储加载的都是最终结果 isIntermediate: false, // 从存储加载的都是最终结果
); );
} }
/// 转换为JSON格式 /// 转换为JSON格式
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
return { return {
@ -59,7 +59,7 @@ class TranslationItem {
// 不保存isIntermediate,因为存储的都是最终结果 // 不保存isIntermediate,因为存储的都是最终结果
}; };
} }
/// 获取源语言的中文名称 /// 获取源语言的中文名称
String get sourceLanguageName { String get sourceLanguageName {
final languageManager = Get.find<LanguageManager>(); final languageManager = Get.find<LanguageManager>();
@ -73,4 +73,4 @@ class TranslationItem {
return languageManager.getChineseNameByAsrCode(targetLanguageCode) ?? return languageManager.getChineseNameByAsrCode(targetLanguageCode) ??
'meetingUnknownLanguage'.tr; // 对应中文:未知语言 'meetingUnknownLanguage'.tr; // 对应中文:未知语言
} }
} }

161
lib/modules/translation/views/dialogs/language_selection_dialog.dart

@ -0,0 +1,161 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import '../../controllers/translation_controller.dart';
/// 语言选择对话框
class LanguageSelectionDialog {
/// 显示语言选择对话框
static void show(bool isSource, bool isDarkMode) {
final controller = Get.find<TranslationController>();
Get.dialog(Dialog(
backgroundColor: isDarkMode ? Colors.grey[900] : Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16.r), // 统一圆角尺寸为16
),
alignment: isSource ? Alignment.centerLeft : Alignment.centerRight,
insetPadding: EdgeInsets.symmetric(horizontal: 10.w),
child: SizedBox(
width: 0.4.sw,
child: Padding(
// 使用Padding替代Container的padding
padding: EdgeInsets.all(12.w), // 统一内边距
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 标题区域
Padding(
padding: EdgeInsets.symmetric(horizontal: 8.w),
child: Text(
isSource
? 'selectSourceLanguage'.tr
: 'selectTargetLanguage'.tr, // 保留源语言/目标语言区分
style: TextStyle(
fontSize: 18.sp, // 调整字号
fontWeight: FontWeight.w600, // 使用数字字重
color: isDarkMode ? Colors.white : Colors.black87, // 优化文本颜色
),
),
),
SizedBox(height: 16.h),
// 语言列表区域
Container(
constraints: BoxConstraints(maxHeight: 0.5.sh),
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children:
controller.supportedLanguages.keys.map((language) {
return Obx(() {
final currentLanguage = isSource
? controller.sourceLanguage.value
: controller.targetLanguage.value;
final isSelected = currentLanguage == language;
return GestureDetector(
// 改用手势检测器实现点击
onTap: () {
if (isSource) {
controller.setSourceLanguage(language);
} else {
controller.setTargetLanguage(language);
}
Get.back();
},
child: Container(
margin: EdgeInsets.only(bottom: 10.h), // 添加项间距
padding: EdgeInsets.all(10.w), // 统一内边距
decoration: BoxDecoration(
color: isSelected // 选中状态背景色
? (isDarkMode
? const Color(0xFF6200EE).withOpacity(0.2)
: Colors.blue.withOpacity(0.1))
: (isDarkMode // 未选中状态背景色
? Colors.white.withOpacity(0.05)
: Colors.grey[200]!),
borderRadius: BorderRadius.circular(12.r), // 项圆角
border: Border.all(
// 选中状态边框
color: isSelected
? (isDarkMode
? const Color(0xFF6200EE)
: Colors.blue)
: (isDarkMode // 未选中状态背景色
? Colors.white.withOpacity(0.05)
: Colors.grey[200]!),
width: 1,
),
),
child: Row(
children: [
// 语言名称
Expanded(
child: Text(
language.tr,
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w500,
color: isDarkMode
? Colors.white
: Colors.black87,
overflow: TextOverflow.ellipsis,
),
),
),
// 选中指示器
Container(
width: 24.w,
height: 24.w,
decoration: BoxDecoration(
color: isSelected // 动态颜色
? (isDarkMode
? const Color(0xFF6200EE)
: Colors.blue)
: (isDarkMode
? Colors.white.withOpacity(0.1)
: Colors.grey[300]),
shape: BoxShape.circle, // 圆形样式
),
child: isSelected
? Icon(
// 选中时显示对勾
Icons.check,
size: 16.w,
color: Colors.white,
)
: null,
),
],
),
),
);
});
}).toList(),
),
),
),
SizedBox(height: 16.h),
// 取消按钮
Align(
alignment: Alignment.centerRight,
child: TextButton(
onPressed: () => Get.back(),
child: Text(
'cancel'.tr,
style: TextStyle(
color: isDarkMode ? const Color(0xFF6200EE) : Colors.blue,
),
),
),
),
],
),
),
),
));
}
}

172
lib/modules/translation/views/dialogs/translation_mode_selector.dart

@ -0,0 +1,172 @@
import 'dart:ui';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import '../../controllers/translation_controller.dart';
/// 翻译模式选择对话框
class TranslationModeSelector {
/// 显示翻译模式选择器
static void show(bool isDarkMode) {
final controller = Get.find<TranslationController>();
Get.bottomSheet(
BackdropFilter(
filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0),
child: _buildTranslationModeSelector(controller),
),
isScrollControlled: true,
backgroundColor: Colors.transparent,
);
}
// 构建翻译模式选择器 - 参考会议模块样式
static Widget _buildTranslationModeSelector(TranslationController controller) {
return Builder(
builder: (BuildContext context) {
return SafeArea(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
_buildModeItem(
context,
'simultaneousTranslation'.tr,
Icons.translate,
Colors.blue,
controller,
onTap: () async {
Get.back();
await controller.stopAll();
await controller.changeTranslationMode('simultaneous');
await controller.startRecognition();
},
),
_buildModeItem(
context,
'faceToFaceTranslation'.tr,
Icons.people,
Colors.green,
controller,
onTap: () async {
Get.back();
await controller.stopAll();
await controller.changeTranslationMode('faceToFace');
await controller.startRecognition();
},
),
_buildModeItem(
context,
'audioVideoTranslation'.tr,
Icons.videocam,
Colors.orange,
controller,
onTap: controller.bleManager.isConnected
? () async {
Get.back();
await controller.stopAll();
await controller.changeTranslationMode('audioVideo');
await controller.startRecognition();
}
: null,
),
_buildModeItem(
context,
'callTranslation'.tr,
Icons.phone,
Colors.purple,
controller,
onTap: controller.bleManager.isConnected
? () async {
Get.back();
await controller.stopAll();
await controller.changeTranslationMode('call');
await controller.startRecognition();
}
: null,
),
SizedBox(height: 24.w),
GestureDetector(
onTap: () => Get.back(),
child: Container(
width: 42,
height: 42,
decoration: BoxDecoration(
color: Colors.black.withValues(alpha: 0.8),
borderRadius: BorderRadius.circular(50),
),
child: const Icon(Icons.close, color: Colors.white),
),
),
if (Platform.isAndroid) SizedBox(height: 20.h),
],
),
);
},
);
}
// 构建单个模式选项 - 完全参考会议模块的 _buildItem 方法
static Widget _buildModeItem(
BuildContext context,
String title,
IconData icon,
Color color,
TranslationController controller, {
Function()? onTap,
}) {
final isDarkMode = Theme.of(context).brightness == Brightness.dark;
return GestureDetector(
onTap: onTap,
child: Container(
width: 220.w,
height: 60.w,
padding: EdgeInsets.all(5.w),
margin: EdgeInsets.only(top: 15.w),
decoration: BoxDecoration(
color: isDarkMode
? Colors.grey[800]!.withValues(alpha: onTap != null ? 1 : 0.5)
: Colors.white.withValues(alpha: onTap != null ? 1 : 0.5),
borderRadius: BorderRadius.circular(60.w),
),
child: Row(
children: [
Container(
width: 46.w,
height: 46.w,
margin: EdgeInsets.only(right: 5.w),
decoration: BoxDecoration(
color: color.withValues(alpha: onTap != null ? 0.3 : 0.2),
borderRadius: BorderRadius.circular(60.w),
),
child: Icon(
icon,
color: color.withValues(alpha: onTap != null ? 1 : 0.5),
),
),
Expanded(
child: Text(
title,
style: TextStyle(
fontSize: 15.sp,
color: isDarkMode
? Colors.white.withValues(alpha: onTap != null ? 1 : 0.5)
: Colors.black87
.withValues(alpha: onTap != null ? 1 : 0.5),
fontWeight: FontWeight.bold,
),
),
),
Icon(
Icons.arrow_forward_ios,
size: 16.w,
color: Colors.grey.withValues(alpha: onTap != null ? 1 : 0.5),
),
SizedBox(width: 5.w),
],
),
),
);
}
}

480
lib/modules/translation/views/translation_view.dart

@ -10,6 +10,13 @@ import '../views/translation_history_view.dart';
import '../bindings/translation_history_binding.dart'; import '../bindings/translation_history_binding.dart';
import '../../../core/theme/app_colors.dart'; import '../../../core/theme/app_colors.dart';
import '../../../routes/app_routes.dart'; import '../../../routes/app_routes.dart';
// 导入拆分后的组件
import 'widgets/face_to_face_layout.dart'; // 面对面翻译布局
import 'widgets/draggable_floating_button.dart'; // 可拖拽的悬浮按钮
import 'dialogs/language_selection_dialog.dart'; // 语言选择对话框
import 'dialogs/translation_mode_selector.dart'; // 翻译模式选择器
// import 'package:intl/intl.dart'; // import 'package:intl/intl.dart';
//主页面 //主页面
@ -20,10 +27,98 @@ class TranslationView extends GetView<TranslationController> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final isDarkMode = Theme.of(context).brightness == Brightness.dark; 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),
// 可拖拽的悬浮按钮
DraggableFloatingButton(
isDarkMode: isDarkMode,
controller: controller,
),
],
),
);
});
}
// 构建面对面翻译模式的专用Scaffold
Widget _buildFaceToFaceScaffold(bool isDarkMode) {
return Scaffold( return Scaffold(
backgroundColor: isDarkMode ? AppColors.darkBackground : Colors.grey[50], backgroundColor: isDarkMode ? AppColors.darkBackground : Colors.grey[50],
appBar: _buildAppBar(isDarkMode), body: Stack(
body: _buildBody(isDarkMode), children: [
// 主要内容区域
FaceToFaceLayout(
isDarkMode: isDarkMode,
controller: controller,
),
// 顶部按钮(倒过来)
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),
),
// 可拖拽的悬浮按钮 - 在所有模式下都显示
DraggableFloatingButton(
isDarkMode: isDarkMode,
controller: controller,
),
],
),
);
}
// 构建面对面翻译顶部按钮(原右边按钮,现在移到顶部并旋转)
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, // 源语言名称
),
); );
} }
@ -46,7 +141,7 @@ class TranslationView extends GetView<TranslationController> {
title: Obx(() => GestureDetector( title: Obx(() => GestureDetector(
onTap: () { onTap: () {
// 点击标题时弹出翻译模式选择器 // 点击标题时弹出翻译模式选择器
_showTranslationModeSelector(isDarkMode); TranslationModeSelector.show(isDarkMode);
}, },
child: Text( child: Text(
controller.currentModeTitle.value.tr, controller.currentModeTitle.value.tr,
@ -149,7 +244,7 @@ class TranslationView extends GetView<TranslationController> {
// 源语言选择 // 源语言选择
Expanded( Expanded(
child: GestureDetector( child: GestureDetector(
onTap: () => _showLanguageSelectionDialog(true, isDarkMode), onTap: () => LanguageSelectionDialog.show(true, isDarkMode),
child: Container( child: Container(
padding: padding:
EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h), EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h),
@ -197,7 +292,7 @@ class TranslationView extends GetView<TranslationController> {
// 目标语言选择 // 目标语言选择
Expanded( Expanded(
child: GestureDetector( child: GestureDetector(
onTap: () => _showLanguageSelectionDialog(false, isDarkMode), onTap: () => LanguageSelectionDialog.show(false, isDarkMode),
child: Container( child: Container(
padding: padding:
EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h), EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h),
@ -260,6 +355,7 @@ class TranslationView extends GetView<TranslationController> {
Widget _buildTranslationContent(bool isDarkMode) { Widget _buildTranslationContent(bool isDarkMode) {
return Obx(() { return Obx(() {
// 其他模式使用原有的列表布局
final itemCount = controller.translationHistory.length + final itemCount = controller.translationHistory.length +
(controller.hasMoreHistory.value ? 1 : 0); (controller.hasMoreHistory.value ? 1 : 0);
@ -474,7 +570,7 @@ class TranslationView extends GetView<TranslationController> {
controller.stopAll(); controller.stopAll();
} else { } else {
// 弹出翻译模式选择器 // 弹出翻译模式选择器
_showTranslationModeSelector(isDarkMode); TranslationModeSelector.show(isDarkMode);
} }
}, },
child: Container( child: Container(
@ -654,158 +750,6 @@ class TranslationView extends GetView<TranslationController> {
); );
} }
// 显示语言选择对话框
void _showLanguageSelectionDialog(bool isSource, bool isDarkMode) {
Get.dialog(Dialog(
backgroundColor: isDarkMode ? Colors.grey[900] : Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16.r), // 统一圆角尺寸为16
),
alignment: isSource ? Alignment.centerLeft : Alignment.centerRight,
insetPadding: EdgeInsets.symmetric(horizontal: 10.w),
child: SizedBox(
width: 0.4.sw,
child: Padding(
// 使用Padding替代Container的padding
padding: EdgeInsets.all(12.w), // 统一内边距
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 标题区域
Padding(
padding: EdgeInsets.symmetric(horizontal: 8.w),
child: Text(
isSource
? 'selectSourceLanguage'.tr
: 'selectTargetLanguage'.tr, // 保留源语言/目标语言区分
style: TextStyle(
fontSize: 18.sp, // 调整字号
fontWeight: FontWeight.w600, // 使用数字字重
color: isDarkMode ? Colors.white : Colors.black87, // 优化文本颜色
),
),
),
SizedBox(height: 16.h),
// 语言列表区域
Container(
constraints: BoxConstraints(maxHeight: 0.5.sh),
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children:
controller.supportedLanguages.keys.map((language) {
return Obx(() {
final currentLanguage = isSource
? controller.sourceLanguage.value
: controller.targetLanguage.value;
final isSelected = currentLanguage == language;
return GestureDetector(
// 改用手势检测器实现点击
onTap: () {
if (isSource) {
controller.setSourceLanguage(language);
} else {
controller.setTargetLanguage(language);
}
Get.back();
},
child: Container(
margin: EdgeInsets.only(bottom: 10.h), // 添加项间距
padding: EdgeInsets.all(10.w), // 统一内边距
decoration: BoxDecoration(
color: isSelected // 选中状态背景色
? (isDarkMode
? const Color(0xFF6200EE).withOpacity(0.2)
: Colors.blue.withOpacity(0.1))
: (isDarkMode // 未选中状态背景色
? Colors.white.withOpacity(0.05)
: Colors.grey[200]!),
borderRadius: BorderRadius.circular(12.r), // 项圆角
border: Border.all(
// 选中状态边框
color: isSelected
? (isDarkMode
? const Color(0xFF6200EE)
: Colors.blue)
: (isDarkMode // 未选中状态背景色
? Colors.white.withOpacity(0.05)
: Colors.grey[200]!),
width: 1,
),
),
child: Row(
children: [
// 语言名称
Expanded(
child: Text(
language.tr,
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w500,
color: isDarkMode
? Colors.white
: Colors.black87,
overflow: TextOverflow.ellipsis,
),
),
),
// 选中指示器
Container(
width: 24.w,
height: 24.w,
decoration: BoxDecoration(
color: isSelected // 动态颜色
? (isDarkMode
? const Color(0xFF6200EE)
: Colors.blue)
: (isDarkMode
? Colors.white.withOpacity(0.1)
: Colors.grey[300]),
shape: BoxShape.circle, // 圆形样式
),
child: isSelected
? Icon(
// 选中时显示对勾
Icons.check,
size: 16.w,
color: Colors.white,
)
: null,
),
],
),
),
);
});
}).toList(),
),
),
),
SizedBox(height: 16.h),
// 取消按钮
Align(
alignment: Alignment.centerRight,
child: TextButton(
onPressed: () => Get.back(),
child: Text(
'cancel'.tr,
style: TextStyle(
color: isDarkMode ? const Color(0xFF6200EE) : Colors.blue,
),
),
),
),
],
),
),
),
));
}
// 构建加载更多指示器 // 构建加载更多指示器
Widget _buildLoadMoreIndicator() { Widget _buildLoadMoreIndicator() {
return Obx(() { return Obx(() {
@ -1046,158 +990,104 @@ class TranslationView extends GetView<TranslationController> {
} }
} }
// 显示翻译模式选择器 // 构建语言选择栏
void _showTranslationModeSelector(bool isDarkMode) { Widget _buildLanguageSelectionBar(bool isDarkMode) {
Get.bottomSheet( return Container(
BackdropFilter( height: 44.h,
filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0), padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 6.h),
child: _buildTranslationModeSelector(), 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),
),
],
), ),
isScrollControlled: true, child: Row(
backgroundColor: Colors.transparent, mainAxisAlignment: MainAxisAlignment.center,
); children: [
} // 源语言选择
Expanded(
// 构建翻译模式选择器 - 参考会议模块样式 child: GestureDetector(
Widget _buildTranslationModeSelector() { onTap: () => LanguageSelectionDialog.show(true, isDarkMode),
return Builder( child: Container(
builder: (BuildContext context) { padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h),
return SafeArea( decoration: BoxDecoration(
child: Column( color: isDarkMode
mainAxisSize: MainAxisSize.min, ? Colors.white.withOpacity(0.1)
children: [ : Colors.grey[100],
_buildModeItem( borderRadius: BorderRadius.circular(16.r),
context, ),
'simultaneousTranslation'.tr, child: Text(
Icons.translate, '${controller.sourceLanguage.value.split(' ')[0]}'.tr,
Colors.blue, style: TextStyle(
onTap: () async { fontSize: 15.sp,
Get.back(); fontWeight: FontWeight.w600,
await controller.stopAll(); color: isDarkMode ? Colors.white : Colors.black,
await controller.changeTranslationMode('simultaneous');
await controller.startRecognition();
},
),
_buildModeItem(
context,
'faceToFaceTranslation'.tr,
Icons.people,
Colors.green,
onTap: () async {
Get.back();
await controller.stopAll();
await controller.changeTranslationMode('faceToFace');
await controller.startRecognition();
},
),
_buildModeItem(
context,
'audioVideoTranslation'.tr,
Icons.videocam,
Colors.orange,
onTap: controller.bleManager.isConnected
? () async {
Get.back();
await controller.stopAll();
await controller.changeTranslationMode('audioVideo');
await controller.startRecognition();
}
: null,
),
_buildModeItem(
context,
'callTranslation'.tr,
Icons.phone,
Colors.purple,
onTap: controller.bleManager.isConnected
? () async {
Get.back();
await controller.stopAll();
await controller.changeTranslationMode('call');
await controller.startRecognition();
}
: null,
),
SizedBox(height: 24.w),
GestureDetector(
onTap: () => Get.back(),
child: Container(
width: 42,
height: 42,
decoration: BoxDecoration(
color: Colors.black.withValues(alpha: 0.8),
borderRadius: BorderRadius.circular(50),
), ),
child: const Icon(Icons.close, color: Colors.white), textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
), ),
), ),
if (Platform.isAndroid) SizedBox(height: 20.h), ),
],
), ),
);
},
);
}
// 构建单个模式选项 - 完全参考会议模块的 _buildItem 方法 // 语言互换按钮
Widget _buildModeItem( GestureDetector(
BuildContext context, onTap: controller.swapLanguages,
String title, child: Container(
IconData icon, margin: EdgeInsets.symmetric(horizontal: 8.w),
Color color, { padding: EdgeInsets.all(4.r),
Function()? onTap,
}) {
final isDarkMode = Theme.of(context).brightness == Brightness.dark;
return GestureDetector(
onTap: onTap,
child: Container(
width: 220.w,
height: 60.w,
padding: EdgeInsets.all(5.w),
margin: EdgeInsets.only(top: 15.w),
decoration: BoxDecoration(
color: isDarkMode
? Colors.grey[800]!.withValues(alpha: onTap != null ? 1 : 0.5)
: Colors.white.withValues(alpha: onTap != null ? 1 : 0.5),
borderRadius: BorderRadius.circular(60.w),
),
child: Row(
children: [
Container(
width: 46.w,
height: 46.w,
margin: EdgeInsets.only(right: 5.w),
decoration: BoxDecoration( decoration: BoxDecoration(
color: color.withValues(alpha: onTap != null ? 0.3 : 0.2), color: isDarkMode
borderRadius: BorderRadius.circular(60.w), ? AppColors.primary.withOpacity(0.2)
: Colors.blue[50],
shape: BoxShape.circle,
), ),
child: Icon( child: Icon(
icon, Icons.swap_horiz,
color: color.withValues(alpha: onTap != null ? 1 : 0.5), color: isDarkMode ? AppColors.primary : Colors.blue,
size: 20.sp,
), ),
), ),
Expanded( ),
child: Text(
title, // 目标语言选择
style: TextStyle( Expanded(
fontSize: 15.sp, child: GestureDetector(
onTap: () => LanguageSelectionDialog.show(false, isDarkMode),
child: Container(
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h),
decoration: BoxDecoration(
color: isDarkMode color: isDarkMode
? Colors.white.withValues(alpha: onTap != null ? 1 : 0.5) ? Colors.white.withOpacity(0.1)
: Colors.black87 : Colors.grey[100],
.withValues(alpha: onTap != null ? 1 : 0.5), borderRadius: BorderRadius.circular(16.r),
fontWeight: FontWeight.bold, ),
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,
),
), ),
), ),
), ),
Icon( ),
Icons.arrow_forward_ios, ],
size: 16.w,
color: Colors.grey.withValues(alpha: onTap != null ? 1 : 0.5),
),
SizedBox(width: 5.w),
],
),
), ),
); );
} }

103
lib/modules/translation/views/widgets/draggable_floating_button.dart

@ -0,0 +1,103 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import '../../controllers/translation_controller.dart';
import '../dialogs/translation_mode_selector.dart';
/// 可拖拽悬浮按钮组件
class DraggableFloatingButton extends StatelessWidget {
final bool isDarkMode;
final TranslationController controller;
const DraggableFloatingButton({
Key? key,
required this.isDarkMode,
required this.controller,
}) : super(key: key);
@override
Widget build(BuildContext context) {
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,
),
),
),
),
));
}
/// 显示翻译模式选择器
void _showTranslationModeSelector(bool isDarkMode) {
TranslationModeSelector.show(isDarkMode);
}
}

219
lib/modules/translation/views/widgets/face_to_face_layout.dart

@ -0,0 +1,219 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import '../../controllers/translation_controller.dart';
import '../../models/translation_models.dart';
import '../../../../core/theme/app_colors.dart';
import '../dialogs/language_selection_dialog.dart';
/// 面对面翻译布局组件
class FaceToFaceLayout extends StatelessWidget {
final bool isDarkMode;
final TranslationController controller;
const FaceToFaceLayout({
Key? key,
required this.isDarkMode,
required this.controller,
}) : super(key: key);
@override
Widget build(BuildContext context) {
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 _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,
),
),
),
),
),
],
),
);
}
/// 显示语言选择对话框
void _showLanguageSelectionDialog(bool isSource, bool isDarkMode) {
LanguageSelectionDialog.show(isSource, isDarkMode);
}
}
Loading…
Cancel
Save