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.

401 lines
12 KiB

import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart';
// import 'package:google_mlkit_text_recognition/google_mlkit_text_recognition.dart';
import 'package:image_picker/image_picker.dart';
import 'package:image/image.dart' as img;
import 'package:path/path.dart' as path;
import 'package:permission_handler/permission_handler.dart';
import '../../../data/services/volcano_translation_service.dart';
import '../../../data/services/language_manager.dart';
import '../../../core/utils/logger.dart';
import '../models/picture_translation_models.dart';
class PictureTranslationController extends GetxController {
final ImagePicker _imagePicker = ImagePicker();
// final TextRecognizer _textRecognizer =
// TextRecognizer(script: TextRecognitionScript.chinese);
final GetStorage _storage = GetStorage();
final VolcanoTranslationService _translationService =
Get.find<VolcanoTranslationService>();
final LanguageManager languageManager = Get.find<LanguageManager>();
// UI状态
final sourceLanguageChineseName = '中文(简体)'.obs;
final targetLanguageChineseName = '英语'.obs;
final sourceText = ''.obs;
final translatedText = ''.obs;
final isProcessing = false.obs;
final pickedImageFile = Rx<File?>(null);
final currentStep = ProcessingStep(
title: '等待操作',
description: '请选择图片开始翻译',
status: ProcessingStatus.idle,
).obs;
// 历史记录
final historyList = <PictureTranslationHistory>[].obs;
final isLoadingHistory = false.obs;
// 图片处理相关
final imageQuality = 85.obs;
final maxImageSize = 1920.obs;
static const String _historyKey = 'picture_translation_history';
static const String _settingsKey = 'picture_translation_settings';
@override
void onInit() {
super.onInit();
_loadSettings();
_loadHistory();
}
@override
void onClose() {
// _textRecognizer.close();
super.onClose();
}
/// 加载设置
void _loadSettings() {
try {
final settings = _storage.read(_settingsKey) as Map<String, dynamic>?;
if (settings != null) {
sourceLanguageChineseName.value =
settings['sourceLanguage'] ?? '中文(简体)';
targetLanguageChineseName.value = settings['targetLanguage'] ?? '英语';
imageQuality.value = settings['imageQuality'] ?? 85;
maxImageSize.value = settings['maxImageSize'] ?? 1920;
}
} catch (e) {
Logger.error('PictureTranslation', '加载设置失败: $e');
}
}
/// 保存设置
void _saveSettings() {
try {
_storage.write(_settingsKey, {
'sourceLanguage': sourceLanguageChineseName.value,
'targetLanguage': targetLanguageChineseName.value,
'imageQuality': imageQuality.value,
'maxImageSize': maxImageSize.value,
});
} catch (e) {
Logger.error('PictureTranslation', '保存设置失败: $e');
}
}
/// 加载历史记录
void _loadHistory() {
try {
isLoadingHistory.value = true;
final historyData = _storage.read(_historyKey) as List<dynamic>?;
if (historyData != null) {
historyList.value = historyData
.map((item) => PictureTranslationHistory.fromJson(
item as Map<String, dynamic>))
.toList();
// 按时间倒序排列
historyList.sort((a, b) => b.timestamp.compareTo(a.timestamp));
}
} catch (e) {
Logger.error('PictureTranslation', '加载历史记录失败: $e');
} finally {
isLoadingHistory.value = false;
}
}
/// 保存历史记录
void _saveHistory() {
try {
final historyData = historyList.map((item) => item.toJson()).toList();
_storage.write(_historyKey, historyData);
} catch (e) {
Logger.error('PictureTranslation', '保存历史记录失败: $e');
}
}
/// 添加历史记录
void _addToHistory(PictureTranslationHistory history) {
historyList.insert(0, history);
// 限制历史记录数量,最多保存100条
if (historyList.length > 100) {
historyList.removeRange(100, historyList.length);
}
_saveHistory();
}
/// 删除历史记录
void deleteHistory(String id) {
historyList.removeWhere((item) => item.id == id);
_saveHistory();
Get.snackbar('成功', '已删除历史记录');
}
/// 清空历史记录
void clearHistory() {
historyList.clear();
_storage.remove(_historyKey);
Get.snackbar('成功', '已清空所有历史记录');
}
/// 检查权限
Future<bool> _checkPermissions(ImageSource source) async {
if (source == ImageSource.camera) {
final status = await Permission.camera.request();
if (!status.isGranted) {
Get.snackbar('权限需求', '需要相机权限才能拍照');
return false;
}
} else {
final status = await Permission.photos.request();
if (!status.isGranted) {
Get.snackbar('权限需求', '需要相册权限才能选择图片');
return false;
}
}
return true;
}
/// 更新处理步骤
void _updateStep(String title, String description, ProcessingStatus status,
{double progress = 0.0, String? error}) {
currentStep.value = ProcessingStep(
title: title,
description: description,
status: status,
progress: progress,
error: error,
);
}
/// 从相册选择
Future<void> pickFromGallery() async {
//if (!(await _checkPermissions(ImageSource.gallery))) return;
await _pickAndProcess(ImageSource.gallery);
}
/// 从相机拍照
Future<void> pickFromCamera() async {
//if (!(await _checkPermissions(ImageSource.camera))) return;
await _pickAndProcess(ImageSource.camera);
}
/// 选择并处理图片
Future<void> _pickAndProcess(ImageSource source) async {
try {
isProcessing.value = true;
_updateStep(
'选择图片',
'正在打开${source == ImageSource.camera ? '相机' : '相册'}...',
ProcessingStatus.picking);
final XFile? image = await _imagePicker.pickImage(
source: source,
imageQuality: imageQuality.value,
maxWidth: maxImageSize.value.toDouble(),
maxHeight: maxImageSize.value.toDouble(),
);
if (image == null) {
_updateStep('已取消', '用户取消了选择', ProcessingStatus.idle);
return;
}
File file = File(image.path);
// 图片预处理
file = await _preprocessImage(file);
pickedImageFile.value = file;
_updateStep('识别文本', '正在分析图片中的文本...', ProcessingStatus.recognizing,
progress: 0.3);
// 文本识别
// final inputImage = InputImage.fromFile(file);
// final RecognizedText recognizedText =
// await _textRecognizer.processImage(inputImage);
// sourceText.value = recognizedText.text.trim();
if (sourceText.value.isEmpty) {
_updateStep('识别完成', '未识别到任何文本', ProcessingStatus.idle);
translatedText.value = '';
return;
}
_updateStep('翻译文本', '正在翻译识别的文本...', ProcessingStatus.translating,
progress: 0.7);
// 执行翻译
await _performTranslation();
// 保存历史记录
if (sourceText.value.isNotEmpty && translatedText.value.isNotEmpty) {
_updateStep('保存记录', '正在保存翻译结果...', ProcessingStatus.saving,
progress: 0.9);
await _saveTranslationResult(file);
}
_updateStep('翻译完成', '所有步骤已完成', ProcessingStatus.idle, progress: 1.0);
} catch (e) {
Logger.error('PictureTranslation', '处理失败: $e');
_updateStep('处理失败', e.toString(), ProcessingStatus.error);
_showErrorDialog('处理失败', '图片处理或翻译失败,请重试。\n错误信息: $e');
} finally {
isProcessing.value = false;
}
}
/// 图片预处理
Future<File> _preprocessImage(File file) async {
try {
final bytes = await file.readAsBytes();
final image = img.decodeImage(bytes);
if (image == null) return file;
// 自动旋转图片
final orientedImage = img.bakeOrientation(image);
// 压缩图片大小(如果需要)
img.Image resizedImage = orientedImage;
if (orientedImage.width > maxImageSize.value ||
orientedImage.height > maxImageSize.value) {
resizedImage = img.copyResize(
orientedImage,
width: orientedImage.width > orientedImage.height
? maxImageSize.value
: null,
height: orientedImage.height > orientedImage.width
? maxImageSize.value
: null,
);
}
// 保存处理后的图片
final processedBytes =
img.encodeJpg(resizedImage, quality: imageQuality.value);
final processedFile = File('${file.path}_processed.jpg');
await processedFile.writeAsBytes(processedBytes);
return processedFile;
} catch (e) {
Logger.error('PictureTranslation', '图片预处理失败: $e');
return file; // 返回原图片
}
}
/// 执行翻译
Future<void> _performTranslation() async {
final sourceCode = languageManager
.getAsrCodeByChineseName(sourceLanguageChineseName.value) ??
'zh-CN';
final targetCode = languageManager
.getAsrCodeByChineseName(targetLanguageChineseName.value) ??
'en-US';
final result = await _translationService.translateText(
text: sourceText.value,
sourceLanguageCode: sourceCode,
targetLanguageCode: targetCode,
);
translatedText.value = result ?? '';
}
/// 保存翻译结果
Future<void> _saveTranslationResult(File imageFile) async {
try {
final sourceCode = languageManager
.getAsrCodeByChineseName(sourceLanguageChineseName.value) ??
'zh-CN';
final targetCode = languageManager
.getAsrCodeByChineseName(targetLanguageChineseName.value) ??
'en-US';
final history = PictureTranslationHistory(
id: DateTime.now().millisecondsSinceEpoch.toString(),
imagePath: imageFile.path,
sourceText: sourceText.value,
translatedText: translatedText.value,
sourceLanguageCode: sourceCode,
targetLanguageCode: targetCode,
timestamp: DateTime.now(),
);
_addToHistory(history);
} catch (e) {
Logger.error('PictureTranslation', '保存翻译结果失败: $e');
}
}
/// 显示错误对话框
void _showErrorDialog(String title, String message) {
Get.dialog(
AlertDialog(
title: Text(title),
content: Text(message),
actions: [
TextButton(
onPressed: () => Get.back(),
child: const Text('确定'),
),
],
),
);
}
/// 设置源语言
void setSourceLanguage(String language) {
if (sourceLanguageChineseName.value != language) {
sourceLanguageChineseName.value = language;
_saveSettings();
}
}
/// 设置目标语言
void setTargetLanguage(String language) {
if (targetLanguageChineseName.value != language) {
targetLanguageChineseName.value = language;
_saveSettings();
}
}
/// 交换语言
void swapLanguages() {
final temp = sourceLanguageChineseName.value;
sourceLanguageChineseName.value = targetLanguageChineseName.value;
targetLanguageChineseName.value = temp;
// 交换文本
final tempText = sourceText.value;
sourceText.value = translatedText.value;
translatedText.value = tempText;
_saveSettings();
}
/// 复制文本
void copyText(String text) {
if (text.isNotEmpty) {
// 这里可以添加复制到剪切板的逻辑
Get.snackbar('成功', '文本已复制到剪切板');
}
}
/// 重置状态
void reset() {
sourceText.value = '';
translatedText.value = '';
pickedImageFile.value = null;
_updateStep('等待操作', '请选择图片开始翻译', ProcessingStatus.idle);
}
}