import 'dart:convert'; import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_text_detect_area/flutter_text_detect_area.dart'; import 'package:flutter_translate/ads/interstitial_ad_manage.dart'; import 'package:flutter_translate/common/components/speak_alert.dart'; import 'package:flutter_translate/common/hive/history_data.dart'; import 'package:flutter_translate/common/utils/bot_toast.dart'; import 'package:flutter_translate/common/utils/log_utils.dart'; import 'package:flutter_translate/common/utils/number_utils.dart'; import 'package:flutter_translate/common/utils/object_utils.dart'; import 'package:flutter_translate/common/utils/permission_utils.dart'; import 'package:flutter_translate/common/utils/shared_util.dart'; import 'package:flutter_translate/generated/assets.dart'; import 'package:flutter_translate/global/app_lifecycle_reactor.dart'; import 'package:flutter_translate/global/app_tracking_transparency_manager.dart'; import 'package:flutter_translate/manager/speech_convert.dart'; import 'package:flutter_translate/manager/translate.dart'; import 'package:flutter_translate/manager/tts.dart'; import 'package:flutter_translate/model/history_model.dart'; import 'package:flutter_translate/model/language_model.dart'; import 'package:flutter_translate/model/scene_model.dart'; import 'package:flutter_translate/pages/home/components/photo_picker.dart'; import 'package:flutter_translate/router/get_gages.dart'; import 'package:get/get.dart'; import 'package:image_picker/image_picker.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:speech_to_text/speech_recognition_result.dart'; class HomeController extends GetxController { static HomeController get to => Get.find(); final GlobalKey scaffoldKey = GlobalKey(); var versionName = ''.obs; var options = ['Privacy Policy', 'User Agreement', 'Color Setting']; var lastWords = ''; var fromLanguage = Rx( LanguageModel(code: 'en', name: 'English'), ); var toLanguage = Rx( LanguageModel(code: 'zh-cn', name: 'Chinese (Simplified)'), ); var fromScene = Rx("loading"); var toScene = Rx("loading"); RxBool isPlayingFrom = false.obs; RxBool isPlayingTo = false.obs; RxInt appColor = 0xff529F2D.obs; final languageModels = [ LanguageModel(code: 'zh-cn', name: 'Chinese (Simplified)'), LanguageModel(code: 'ar', name: 'Arabic'), LanguageModel(code: 'bn', name: 'Bengali'), LanguageModel(code: 'en', name: 'English'), LanguageModel(code: 'fr', name: 'French'), LanguageModel(code: 'de', name: 'German'), LanguageModel(code: 'hi', name: 'Hindi'), LanguageModel(code: 'ja', name: 'Japanese'), LanguageModel(code: 'jv', name: 'Javanese'), LanguageModel(code: 'ko', name: 'Korean'), LanguageModel(code: 'pt', name: 'Portuguese'), LanguageModel(code: 'pa', name: 'Punjabi'), LanguageModel(code: 'ru', name: 'Russian'), LanguageModel(code: 'es', name: 'Spanish'), ]; var historyList = [].obs; @override void onInit() { super.onInit(); AppTrackingTransparencyManager().requestATT(); AppLifecycleReactor().listenToAppStateChanges(); appColor.value = SharedUtil.getInstance().get("appColor") ?? 0xff554281; _getAppVersion(); } @override void onReady() { super.onReady(); initData(); initHistoryList(); } @override void onClose() { AppLifecycleReactor().appLifecycleListener?.dispose(); super.onClose(); } void initHistoryList() { historyList.value = HistoryData().findAll().reversed.toList(); } Future initData() async { LanguageModel fromModel = Translate().fromLanguageEntity.value; LanguageModel toModel = Translate().toLanguageEntity.value; LanguageModel? f = languageModels.firstWhereOrNull((e) => e.code == fromModel.code); if (f != null) fromLanguage.value = f; LanguageModel? t = languageModels.firstWhereOrNull((e) => e.code == toModel.code); if (t != null) toLanguage.value = t; var data = jsonDecode(await rootBundle.loadString(Assets.jsonScene)); if (data != null && data is List) { var sceneTypeList = data.map((e) => SceneModel.fromMap(e)).toList(); if (sceneTypeList.isNotEmpty) { var x = NumberUtils.getRandomNumber(0, sceneTypeList.length); SceneModel entity = sceneTypeList[x]; if (entity.sceneList != null && entity.sceneList!.isNotEmpty) { var y = NumberUtils.getRandomNumber(0, entity.sceneList!.length); fromScene.value = _getSentence(true, entity.sceneList![y]); toScene.value = _getSentence(false, entity.sceneList![y]); } } } } /// 获取版本号 void _getAppVersion() async { final packageInfo = await PackageInfo.fromPlatform(); versionName.value = 'version number ${packageInfo.version}'; } void openHomeDrawer() { scaffoldKey.currentState?.openDrawer(); } void clickItem(int index) { Log.d(index); if (index == 2) { Get.toNamed(GetPages.colorSetting); } else { Get.toNamed(GetPages.webView, arguments: { 'title': options[index], 'url': index == 0 ? 'https://trans-globe.mystrikingly.com/privacy' : 'https://trans-globe.mystrikingly.com/terms', }); } scaffoldKey.currentState?.closeDrawer(); } String _getSentence(bool isFrom, SceneList sceneList) { List keys = sceneList.toMap().keys.toList(); List values = sceneList.toMap().values.toList(); int index = keys.indexWhere( (e) => e == (isFrom ? fromLanguage.value.name : toLanguage.value.name)); if (index != -1) { return values[index]; } return 'loading'; } void textConvertVoice(bool isFrom) async { isPlayingTo.value = false; isPlayingFrom.value = false; if (isFrom) { isPlayingFrom.value = true; await Tts().voiceTranslator(fromScene.value, fromLanguage.value.code); isPlayingFrom.value = false; } else { isPlayingTo.value = true; await Tts().voiceTranslator(toScene.value, toLanguage.value.code); isPlayingTo.value = false; } } void sceneCategory() { Get.toNamed(GetPages.sceneCategory); } Future photos() async { Get.bottomSheet( isScrollControlled: true, PhotoPicker( funCamera: () async { await PermissionUtils.checkPermission( [Permission.camera], ).then((res) { if (!res) return; _openCameraGallery(ImageSource.camera); }); }, funGallery: () async { Permission permission = Permission.photos; await PermissionUtils.checkPermission( [permission], ).then((res) { if (!res) return; _openCameraGallery(ImageSource.gallery); }); }, ), ); } void face2face() { InterstitialAdManager().showAdIfReady( InterstitialAdManager().adIds[2], onTap: () async { bool micResult = await PermissionUtils.checkPermission( [Permission.microphone], ); if (!micResult) return; if (Platform.isIOS) { bool speechResult = await PermissionUtils.checkPermission( [Permission.speech], ); if (!speechResult) return; } Get.toNamed(GetPages.faceToFace); }, ); } void history() { Get.toNamed(GetPages.history); } void translator() { Get.toNamed(GetPages.translator); } Future speak() async { bool micResult = await PermissionUtils.checkPermission([Permission.microphone]); if (!micResult) return; if (Platform.isIOS) { bool speechResult = await PermissionUtils.checkPermission([Permission.speech]); if (!speechResult) return; } await Get.dialog( barrierDismissible: true, useSafeArea: false, SpeakAlert( isListening: SpeechConvert().isListening, onTap: () async { if (!SpeechConvert().hasSpeech) { await SpeechConvert().init(); } if (SpeechConvert().hasSpeech) { if (SpeechConvert().isListening.value) { _translatedText(); } else { SpeechConvert().start( Translate().fromLanguageEntity.value.code, (SpeechRecognitionResult result) { Log.d('识别结果:${result.recognizedWords}'); lastWords = result.recognizedWords; }, ); } } else { Get.back(); toast('Speech not available'); } }, ), ); if (Get.isDialogOpen != null && !Get.isDialogOpen!) { SpeechConvert().stop(showLoading: false); } } void _translatedText() { if (ObjectUtils.isEmpty(lastWords)) { toast('No text recognized'); return; } Get.back(); Get.toNamed( GetPages.translateText, arguments: {"sourceText": lastWords}, ); } Future _openCameraGallery(ImageSource source) async { String detectedValue = ""; final ImagePicker picker = ImagePicker(); final XFile? photo = await picker.pickImage(source: source); if (photo != null) { Get.to(() => SelectImageAreaTextDetect( detectOnce: true, enableImageInteractions: true, imagePath: photo.path, onDetectText: (v) { if (v is String) { detectedValue = v; } if (v is List) { int counter = 0; for (var element in v) { detectedValue += "$counter. \t\t $element \n\n"; counter++; } } }, onDetectError: (error) { ///This error will occurred in Android only while user will try to crop image at max zoom level then ml kit will throw max 32 height/width exception if (error is PlatformException && (error.message?.contains( "InputImage width and height should be at least 32!", ) ?? false)) { toast( 'Selected area should be able to crop image with at least 32 width and height.', ); } }, ))?.then( (onValue) { InterstitialAdManager().showAdIfReady( InterstitialAdManager().adIds[3], onTap: () { if (ObjectUtils.isEmpty(detectedValue)) { toast('No text recognized'); return; } Get.toNamed( GetPages.translateText, arguments: {"sourceText": detectedValue}, ); }, ); }, ); } } void removeHistoryByIndex(int index) { // 计算原始列表中的对应索引 int indexToRemoveFromDb = HistoryData().findAll().length - 1 - index; historyList.removeAt(indexToRemoveFromDb); HistoryData().remove(indexToRemoveFromDb); } void historyTranslate(HistoryModel historyModel) { Get.toNamed( GetPages.translateText, arguments: { 'isHistory': true, 'sourceText': historyModel.sourceText, 'targetText': historyModel.targetText, 'fromLanguageName': historyModel.sourceLanguageName, 'toLanguageName': historyModel.targetLanguageName, 'fromLanguageCode': historyModel.sourceLanguageCode, 'toLanguageCode': historyModel.targetLanguageCode, }, ); } }