diff --git a/android/app/build.gradle b/android/app/build.gradle index 6dbee3d..2243ec7 100755 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -22,8 +22,12 @@ if (flutterVersionName == null) { flutterVersionName = '1.0' } +def keystorePropertiesFile = rootProject.file("key.properties") +def keystoreProperties = new Properties() +keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) + android { - namespace "com.lux.translator.translator_lux" + namespace "com.translate.language.camera" compileSdk flutter.compileSdkVersion ndkVersion flutter.ndkVersion @@ -34,20 +38,46 @@ android { defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "com.lux.translator.translator_lux" + applicationId "com.translate.language.camera" // You can update the following values to match your application needs. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. - minSdkVersion 21 - targetSdkVersion flutter.targetSdkVersion + minSdkVersion 23 +// targetSdkVersion flutter.targetSdkVersion + targetSdkVersion 33 versionCode flutterVersionCode.toInteger() versionName flutterVersionName } - buildTypes { + signingConfigs { + release { + keyAlias keystoreProperties['keyAlias'] + keyPassword keystoreProperties['keyPassword'] + storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null + storePassword keystoreProperties['storePassword'] + } + } + + buildTypes { + debug { + signingConfig signingConfigs.release + } + release { - // TODO: Add your own signing config for the release build. // Signing with the debug keys for now, so `flutter run --release` works. - signingConfig signingConfigs.debug + signingConfig signingConfigs.release + + ndk{ + abiFilters "armeabi", "armeabi-v7a", "arm64-v8a" + } + } + } + + android.applicationVariants.all { variant -> + variant.outputs.all { output -> + if (!variant.buildType.isDebuggable()) { + // 配置APK输出文件名 + outputFileName = "TransLark-${flutterVersionName}-${variant.buildType.name}.apk" + } } } } diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 4625b24..b083f8c 100755 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,28 +1,63 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + android:enableOnBackInvokedCallback="true" + android:icon="@mipmap/launcher_icon" + android:label="TransLark"> + android:name="io.flutter.embedding.android.NormalTheme" + android:resource="@style/NormalTheme" /> - - + + - - + + diff --git a/android/app/src/main/java/com/lux/translator/translator_lux/MainActivity.java b/android/app/src/main/java/com/translate/language/camera/MainActivity.java similarity index 71% rename from android/app/src/main/java/com/lux/translator/translator_lux/MainActivity.java rename to android/app/src/main/java/com/translate/language/camera/MainActivity.java index 670912e..e8c9c4e 100755 --- a/android/app/src/main/java/com/lux/translator/translator_lux/MainActivity.java +++ b/android/app/src/main/java/com/translate/language/camera/MainActivity.java @@ -1,4 +1,4 @@ -package com.lux.translator.translator_lux; +package com.translate.language.camera; import io.flutter.embedding.android.FlutterActivity; diff --git a/android/app/src/main/res/drawable-v21/launch_background.xml b/android/app/src/main/res/drawable-v21/launch_background.xml index f74085f..e171cdc 100755 --- a/android/app/src/main/res/drawable-v21/launch_background.xml +++ b/android/app/src/main/res/drawable-v21/launch_background.xml @@ -1,12 +1,12 @@ - + - + diff --git a/android/app/src/main/res/drawable/launch_background.xml b/android/app/src/main/res/drawable/launch_background.xml index 304732f..e171cdc 100755 --- a/android/app/src/main/res/drawable/launch_background.xml +++ b/android/app/src/main/res/drawable/launch_background.xml @@ -4,9 +4,9 @@ - + diff --git a/android/app/src/main/res/mipmap-hdpi/launcher_icon.png b/android/app/src/main/res/mipmap-hdpi/launcher_icon.png new file mode 100644 index 0000000..25512ce Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/launcher_icon.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/launcher_icon.png b/android/app/src/main/res/mipmap-mdpi/launcher_icon.png new file mode 100644 index 0000000..03892ef Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/launcher_icon.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/launch_image.png b/android/app/src/main/res/mipmap-xhdpi/launch_image.png new file mode 100755 index 0000000..84752e0 Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/launch_image.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png b/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png new file mode 100644 index 0000000..a92863b Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/launch_image.png b/android/app/src/main/res/mipmap-xxhdpi/launch_image.png new file mode 100755 index 0000000..9fdec7a Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/launch_image.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png b/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png new file mode 100644 index 0000000..1d02333 Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/launch_image.png b/android/app/src/main/res/mipmap-xxxhdpi/launch_image.png new file mode 100755 index 0000000..d341b50 Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/launch_image.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png b/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png new file mode 100644 index 0000000..8dc9a64 Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png differ diff --git a/android/app/src/main/res/values-night-v27/styles.xml b/android/app/src/main/res/values-night-v27/styles.xml new file mode 100644 index 0000000..1bc3655 --- /dev/null +++ b/android/app/src/main/res/values-night-v27/styles.xml @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/values-night/styles.xml b/android/app/src/main/res/values-night/styles.xml index 06952be..245640e 100755 --- a/android/app/src/main/res/values-night/styles.xml +++ b/android/app/src/main/res/values-night/styles.xml @@ -5,6 +5,7 @@ @drawable/launch_background + true + @drawable/launch_background + true + shortEdges + + \ No newline at end of file diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml index cb1ef88..9accca7 100755 --- a/android/app/src/main/res/values/styles.xml +++ b/android/app/src/main/res/values/styles.xml @@ -5,6 +5,7 @@ @drawable/launch_background + true @@ -14,24 +16,28 @@ + - + + - - + + + + - + - + diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 130dd11..052f8ca 100755 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -2,10 +2,12 @@ + CADisableMinimumFrameDurationOnPhone + CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName - Translator Lux + TransLark CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -13,7 +15,7 @@ CFBundleInfoDictionaryVersion 6.0 CFBundleName - translator_lux + trans_lark CFBundlePackageType APPL CFBundleShortVersionString @@ -24,6 +26,18 @@ $(FLUTTER_BUILD_NUMBER) LSRequiresIPhoneOS + NSCameraUsageDescription + This will enable you to take photos and recognize text within them for translation. + NSPhotoLibraryUsageDescription + This will enable you to select photos from your library for text recognition and translation. + NSMicrophoneUsageDescription + This will enable you to input content through voice for recognition and translation. + NSSpeechRecognitionUsageDescription + This will enable you to use speech recognition to convert voice content to text for translation. + NSUserTrackingUsageDescription + This will help us provide a more personalized advertising experience. Your data privacy will be protected. + UIApplicationSupportsIndirectInputEvents + UILaunchStoryboardName LaunchScreen UIMainStoryboardFile @@ -41,9 +55,5 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight - CADisableMinimumFrameDurationOnPhone - - UIApplicationSupportsIndirectInputEvents - diff --git a/lib/config/config.dart b/lib/config/config.dart deleted file mode 100755 index d1fd874..0000000 --- a/lib/config/config.dart +++ /dev/null @@ -1,5 +0,0 @@ -import 'package:camera/camera.dart'; - -class AppConfig{ - static List cameraList=[]; -} \ No newline at end of file diff --git a/lib/core/router/router.dart b/lib/core/router/router.dart index 4a211c4..81d3eeb 100755 --- a/lib/core/router/router.dart +++ b/lib/core/router/router.dart @@ -1,34 +1,84 @@ import 'package:get/get.dart'; -import 'package:translator_lux/page/main/main_view.dart'; -import 'package:translator_lux/page/translator/translator_view.dart'; -import 'package:translator_lux/page/translator_result/translator_result_view.dart'; -import 'package:translator_lux/translator_history/translator_history_view.dart'; +import 'package:trans_lark/page/face_to_face/face_to_face_binding.dart'; +import 'package:trans_lark/page/face_to_face/face_to_face_view.dart'; +import 'package:trans_lark/page/home/home_view.dart'; +import 'package:trans_lark/page/scene_list/scene_list_binding.dart'; +import 'package:trans_lark/page/scene_list/scene_list_view.dart'; +import 'package:trans_lark/page/scene_type/scene_type_binding.dart'; +import 'package:trans_lark/page/scene_type/scene_type_view.dart'; +import 'package:trans_lark/page/splash/splash_binding.dart'; +import 'package:trans_lark/page/splash/splash_view.dart'; +import 'package:trans_lark/page/translator/translator_view.dart'; +import 'package:trans_lark/page/translator_history/translator_history_view.dart'; +import 'package:trans_lark/page/translator_result/translator_result_view.dart'; +import 'package:trans_lark/page/web_page/web_page_binding.dart'; +import 'package:trans_lark/page/web_page/web_page_view.dart'; class GetRouter { - static String initRouterName = MainPage.routName; + static const splash = '/'; + static const home = '/home'; + static const sceneType = '/scene_type'; + static const sceneList = '/scene_list'; + static const translate = '/translate'; + static const faceToFace = '/face_to_face'; + static const translateResult = '/translate_result'; + static const translateHistory = '/translate_history'; + static const terms = '/terms'; + static const privacy = '/privacy'; + static List routers = [ + // GetPage( + // name: splash, + // page: () => SplashView(), + // binding: SplashBinding(), + // ), //首页 GetPage( - name: MainPage.routName, - page: () => MainPage(), + name: home, + page: () => HomePage(), + ), + GetPage( + name: sceneType, + page: () => SceneTypeView(), + binding: SceneTypeBinding(), + ), + GetPage( + name: sceneList, + page: () => SceneListView(), + binding: SceneListBinding(), ), //翻译输入页面 GetPage( - name: TranlatorPage.routName, - page: () => const TranlatorPage(), - transition: Transition.rightToLeft, + name: translate, + page: () => const TranslatorPage(), ), //翻译结果页 GetPage( - name: TranslatorResultPage.routName, + name: translateResult, page: () => const TranslatorResultPage(), - transition: Transition.rightToLeft, ), //翻译历史页面 GetPage( - name: TranslatorHistoryPage.routName, + name: translateHistory, page: () => const TranslatorHistoryPage(), - transition: Transition.rightToLeft, + ), + // 面对面翻译 + GetPage( + name: faceToFace, + page: () => FaceToFaceView(), + binding: FaceToFaceBinding(), + ), + // 隐私政策 + GetPage( + name: privacy, + page: () => WebPageView(), + binding: WebPageBinding(), + ), + // 用户协议 + GetPage( + name: terms, + page: () => WebPageView(), + binding: WebPageBinding(), ), ]; } diff --git a/lib/dataBase/table/translator_history_table.dart b/lib/dataBase/table/translator_history_table.dart deleted file mode 100755 index c6fa042..0000000 --- a/lib/dataBase/table/translator_history_table.dart +++ /dev/null @@ -1,143 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:sqflite/sqflite.dart'; -import 'package:translator_lux/entity/translator_history_entity.dart'; - -class TranslatorHistoryTable { - static String tableName = "translatorHistoryTable"; - static Database? thDB; - - static createTable(Database db) async { - await db.execute( - "CREATE TABLE $tableName (tid TEXT,sourceText TEXT,sourcecountryCode TEXT,sourcelanguageName TEXT,sourcelanguageCode TEXT,targetText TEXT,targetcountryCode TEXT,targetlanguageName TEXT,targetlanguageCode TEXT,translatorTime TEXT,isShow Text)"); - await db.execute( - 'CREATE INDEX translator_history_tid ON $tableName (tid)', - ); - debugPrint("TranslatorHistoryTable create success"); - } - - static init(Database db) { - thDB = db; - } - - static insertData(TranslatorHistoryEntity historyEntity) { - thDB!.insert(tableName, { - "tid": historyEntity.tid, - "sourceText": historyEntity.sourceText, - "sourcecountryCode": historyEntity.sourcecountryCode, - "sourcelanguageName": historyEntity.sourcelanguageName, - "sourcelanguageCode": historyEntity.sourcelanguageCode, - "targetText": historyEntity.targetText, - "targetcountryCode": historyEntity.targetcountryCode, - "targetlanguageName": historyEntity.targetlanguageName, - "targetlanguageCode": historyEntity.targetlanguageCode, - "translatorTime": historyEntity.translatorTime, - "isShow": historyEntity.isShow, - }); - } - - static Future> queryData() async { - List responseData = []; - List> result = await thDB!.query( - tableName, - ); - for (var historyData in result) { - responseData.add( - TranslatorHistoryEntity( - tid: historyData["tid"].toString(), - sourceText: historyData["sourceText"].toString(), - sourcecountryCode: historyData["sourcecountryCode"].toString(), - sourcelanguageName: historyData["sourcelanguageName"].toString(), - sourcelanguageCode: historyData["sourcelanguageCode"].toString(), - targetText: historyData["targetText"].toString(), - targetcountryCode: historyData["targetcountryCode"].toString(), - targetlanguageName: historyData["targetlanguageName"].toString(), - targetlanguageCode: historyData["targetlanguageCode"].toString(), - translatorTime: historyData["translatorTime"].toString(), - isShow: historyData["isShow"].toString(), - ), - ); - } - return responseData; - } - - static Future> queryShowData() async { - List responseData = []; - List> result = await thDB!.query( - tableName, - where: "isShow = ? ", - whereArgs: ["true"], - ); - for (var historyData in result) { - responseData.add( - TranslatorHistoryEntity( - tid: historyData["tid"].toString(), - sourceText: historyData["sourceText"].toString(), - sourcecountryCode: historyData["sourcecountryCode"].toString(), - sourcelanguageName: historyData["sourcelanguageName"].toString(), - sourcelanguageCode: historyData["sourcelanguageCode"].toString(), - targetText: historyData["targetText"].toString(), - targetcountryCode: historyData["targetcountryCode"].toString(), - targetlanguageName: historyData["targetlanguageName"].toString(), - targetlanguageCode: historyData["targetlanguageCode"].toString(), - translatorTime: historyData["translatorTime"].toString(), - isShow: historyData["isShow"].toString(), - ), - ); - } - return responseData; - } - - static Future queryShowDataByTid(String tid) async { - TranslatorHistoryEntity responseData = TranslatorHistoryEntity(); - List> result = await thDB!.query( - tableName, - where: "tid = ? ", - whereArgs: [tid], - ); - var historyData = result.first; - responseData.copyWith( - tid: historyData["tid"].toString(), - sourceText: historyData["sourceText"].toString(), - sourcecountryCode: historyData["sourcecountryCode"].toString(), - sourcelanguageName: historyData["sourcelanguageName"].toString(), - sourcelanguageCode: historyData["sourcelanguageCode"].toString(), - targetText: historyData["targetText"].toString(), - targetcountryCode: historyData["targetcountryCode"].toString(), - targetlanguageName: historyData["targetlanguageName"].toString(), - targetlanguageCode: historyData["targetlanguageCode"].toString(), - translatorTime: historyData["translatorTime"].toString(), - isShow: historyData["isShow"].toString(), - ); - return responseData; - } - - static cleanAllShowData() async { - await thDB!.update( - tableName, - { - "isShow": "false", - }, - where: " isShow = ? ", - whereArgs: ["true"], - ); - } - - static cleanShowDataByTid(String tid) async { - await thDB!.update( - tableName, - { - "isShow": "false", - }, - where: " tid = ? ", - whereArgs: [tid], - ); - } - - static deleteHistoryDataByTid(String tid) async { - await thDB!.delete( - tableName, - where: " tid = ? ", - whereArgs: [tid], - ); - } -} diff --git a/lib/dataBase/translator_data_base.dart b/lib/dataBase/translator_data_base.dart deleted file mode 100755 index 350d909..0000000 --- a/lib/dataBase/translator_data_base.dart +++ /dev/null @@ -1,28 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:sqflite/sqflite.dart'; -import 'package:translator_lux/dataBase/table/translator_history_table.dart'; - -class TranslatorDataBase { - static Database? thDB; - static String dataBaseName = "translator.db"; - - static init() async { - String path = await getDatabasesPath(); - path = "$path/$dataBaseName"; - debugPrint(path); - await openDatabase( - path, - version: 1, - onCreate: (db, version) async { - debugPrint("dataBase create success"); - await TranslatorHistoryTable.createTable(db); - debugPrint("dataTable create success"); - }, - ).then( - (value) { - thDB = value; - TranslatorHistoryTable.init(value); - }, - ); - } -} diff --git a/lib/entity/history_model.dart b/lib/entity/history_model.dart new file mode 100644 index 0000000..f3aa465 --- /dev/null +++ b/lib/entity/history_model.dart @@ -0,0 +1,86 @@ +// Author: fengshengxiong +// Date: 2024/6/4 +// Description: 历史记录模型 + +import 'dart:convert'; + +import 'package:hive/hive.dart'; + +part 'history_model.g.dart'; + +@HiveType(typeId: 1) +class HistoryEntity extends HiveObject { + @HiveField(1) + int? translationTime; + + @HiveField(2) + String? sourceText; + + @HiveField(3) + String? targetText; + + @HiveField(4) + String? sourceLanguageName; + + @HiveField(5) + String? sourceLanguageCode; + + @HiveField(6) + String? targetLanguageName; + + @HiveField(7) + String? targetLanguageCode; + + HistoryEntity({ + this.translationTime, + this.sourceText, + this.targetText, + this.sourceLanguageName, + this.sourceLanguageCode, + this.targetLanguageName, + this.targetLanguageCode, + }); + + HistoryEntity fromJson(String str) => HistoryEntity.fromMap(json.decode(str)); + + String toJson() => json.encode(toMap()); + + HistoryEntity copyWith({ + int? translationTime, + String? sourceText, + String? targetText, + String? sourceLanguageName, + String? sourceLanguageCode, + String? targetLanguageName, + String? targetLanguageCode, + }) => + HistoryEntity( + translationTime: translationTime ?? this.translationTime, + sourceText: sourceText ?? this.sourceText, + targetText: targetText ?? this.targetText, + sourceLanguageName: sourceLanguageName ?? this.sourceLanguageName, + sourceLanguageCode: sourceLanguageCode ?? this.sourceLanguageCode, + targetLanguageName: targetLanguageName ?? this.targetLanguageName, + targetLanguageCode: targetLanguageCode ?? this.targetLanguageCode, + ); + + factory HistoryEntity.fromMap(Map json) => HistoryEntity( + translationTime: json["translationTime"], + sourceText: json["sourceText"], + targetText: json["targetText"], + sourceLanguageName: json["sourceLanguageName"], + sourceLanguageCode: json["sourceLanguageCode"], + targetLanguageName: json["targetLanguageName"], + targetLanguageCode: json["targetLanguageCode"], + ); + + Map toMap() => { + "translationTime": translationTime, + "sourceText": sourceText, + "targetText": targetText, + "sourceLanguageName": sourceLanguageName, + "sourceLanguageCode": sourceLanguageCode, + "targetLanguageName": targetLanguageName, + "targetLanguageCode": targetLanguageCode, + }; +} \ No newline at end of file diff --git a/lib/entity/history_model.g.dart b/lib/entity/history_model.g.dart new file mode 100644 index 0000000..f005a30 --- /dev/null +++ b/lib/entity/history_model.g.dart @@ -0,0 +1,59 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'history_model.dart'; + +// ************************************************************************** +// TypeAdapterGenerator +// ************************************************************************** + +class HistoryEntityAdapter extends TypeAdapter { + @override + final int typeId = 1; + + @override + HistoryEntity read(BinaryReader reader) { + final numOfFields = reader.readByte(); + final fields = { + for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), + }; + return HistoryEntity( + translationTime: fields[1] as int?, + sourceText: fields[2] as String?, + targetText: fields[3] as String?, + sourceLanguageName: fields[4] as String?, + sourceLanguageCode: fields[5] as String?, + targetLanguageName: fields[6] as String?, + targetLanguageCode: fields[7] as String?, + ); + } + + @override + void write(BinaryWriter writer, HistoryEntity obj) { + writer + ..writeByte(7) + ..writeByte(1) + ..write(obj.translationTime) + ..writeByte(2) + ..write(obj.sourceText) + ..writeByte(3) + ..write(obj.targetText) + ..writeByte(4) + ..write(obj.sourceLanguageName) + ..writeByte(5) + ..write(obj.sourceLanguageCode) + ..writeByte(6) + ..write(obj.targetLanguageName) + ..writeByte(7) + ..write(obj.targetLanguageCode); + } + + @override + int get hashCode => typeId.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is HistoryEntityAdapter && + runtimeType == other.runtimeType && + typeId == other.typeId; +} diff --git a/lib/entity/language_entity.dart b/lib/entity/language_entity.dart index dea1e5d..b9f6ecc 100755 --- a/lib/entity/language_entity.dart +++ b/lib/entity/language_entity.dart @@ -1,14 +1,31 @@ -class SelectLanguageItemEntity { - String countryCode; - String languageName; +import 'dart:convert'; + +class LanguageEntity { String languageCode; - SelectLanguageItemEntity({ - required this.countryCode, - required this.languageName, + String languageName; + + LanguageEntity({ required this.languageCode, + required this.languageName, }); - @override - String toString() => - 'SelectLanguageItemEntity(countryCode: $countryCode, languageName: $languageName, languageCode: $languageCode)'; + LanguageEntity copyWith() => LanguageEntity( + languageCode: languageCode, + languageName: languageName, + ); + + factory LanguageEntity.fromJson(String str) => + LanguageEntity.fromMap(json.decode(str)); + + String toJson() => json.encode(toMap()); + + factory LanguageEntity.fromMap(Map json) => LanguageEntity( + languageCode: json["languageCode"], + languageName: json["languageName"], + ); + + Map toMap() => { + "languageCode": languageCode, + "languageName": languageName, + }; } diff --git a/lib/entity/scene_entity.dart b/lib/entity/scene_entity.dart new file mode 100644 index 0000000..79d0ee9 --- /dev/null +++ b/lib/entity/scene_entity.dart @@ -0,0 +1,149 @@ +// Author: fengshengxiong +// Date: 2024/7/8 +// Description: 常用场景模型 + +import 'dart:convert'; + +class SceneEntity { + String? sceneType; + List? sceneList; + + SceneEntity({ + this.sceneType, + this.sceneList, + }); + + SceneEntity copyWith({ + String? sceneType, + List? sceneList, + }) => + SceneEntity( + sceneType: sceneType ?? this.sceneType, + sceneList: sceneList ?? this.sceneList, + ); + + factory SceneEntity.fromJson(String str) => SceneEntity.fromMap(json.decode(str)); + + String toJson() => json.encode(toMap()); + + factory SceneEntity.fromMap(Map json) => SceneEntity( + sceneType: json["sceneType"], + sceneList: json["sceneList"] == null ? [] : List.from(json["sceneList"]!.map((x) => SceneList.fromMap(x))), + ); + + Map toMap() => { + "sceneType": sceneType, + "sceneList": sceneList == null ? [] : List.from(sceneList!.map((x) => x.toMap())), + }; +} + +class SceneList { + String? english; + String? sceneType; + String? chineseSimplified; + String? spanish; + String? hindi; + String? arabic; + String? portuguese; + String? bengali; + String? russian; + String? punjabi; + String? javanese; + String? korean; + String? french; + String? german; + String? japanese; + + SceneList({ + this.english, + this.sceneType, + this.chineseSimplified, + this.spanish, + this.hindi, + this.arabic, + this.portuguese, + this.bengali, + this.russian, + this.punjabi, + this.javanese, + this.korean, + this.french, + this.german, + this.japanese, + }); + + SceneList copyWith({ + String? english, + String? sceneType, + String? chineseSimplified, + String? spanish, + String? hindi, + String? arabic, + String? portuguese, + String? bengali, + String? russian, + String? punjabi, + String? javanese, + String? korean, + String? french, + String? german, + String? japanese, + }) => + SceneList( + english: english ?? this.english, + sceneType: sceneType ?? this.sceneType, + chineseSimplified: chineseSimplified ?? this.chineseSimplified, + spanish: spanish ?? this.spanish, + hindi: hindi ?? this.hindi, + arabic: arabic ?? this.arabic, + portuguese: portuguese ?? this.portuguese, + bengali: bengali ?? this.bengali, + russian: russian ?? this.russian, + punjabi: punjabi ?? this.punjabi, + javanese: javanese ?? this.javanese, + korean: korean ?? this.korean, + french: french ?? this.french, + german: german ?? this.german, + japanese: japanese ?? this.japanese, + ); + + factory SceneList.fromJson(String str) => SceneList.fromMap(json.decode(str)); + + String toJson() => json.encode(toMap()); + + factory SceneList.fromMap(Map json) => SceneList( + english: json["English"], + sceneType: json["sceneType"], + chineseSimplified: json["Chinese (Simplified)"], + spanish: json["Spanish"], + hindi: json["Hindi"], + arabic: json["Arabic"], + portuguese: json["Portuguese"], + bengali: json["Bengali"], + russian: json["Russian"], + punjabi: json["Punjabi"], + javanese: json["Javanese"], + korean: json["Korean"], + french: json["French"], + german: json["German"], + japanese: json["Japanese"], + ); + + Map toMap() => { + "English": english, + "sceneType": sceneType, + "Chinese (Simplified)": chineseSimplified, + "Spanish": spanish, + "Hindi": hindi, + "Arabic": arabic, + "Portuguese": portuguese, + "Bengali": bengali, + "Russian": russian, + "Punjabi": punjabi, + "Javanese": javanese, + "Korean": korean, + "French": french, + "German": german, + "Japanese": japanese, + }; +} diff --git a/lib/generated/assets.dart b/lib/generated/assets.dart new file mode 100644 index 0000000..0b9fe01 --- /dev/null +++ b/lib/generated/assets.dart @@ -0,0 +1,43 @@ +///This file is automatically generated. DO NOT EDIT, all your changes would be lost. +class Assets { + Assets._(); + + static const String imagesAppbarBack = 'assets/images/appbar_back.png'; + static const String imagesCopy = 'assets/images/copy.png'; + static const String imagesDeleteIcon = 'assets/images/delete_icon.png'; + static const String imagesDialog = 'assets/images/dialog.png'; + static const String imagesFullScreen = 'assets/images/fullScreen.png'; + static const String imagesHistory = 'assets/images/history.png'; + static const String imagesHomeBackground = 'assets/images/homeBackground.png'; + static const String imagesHomeMore = 'assets/images/homeMore.png'; + static const String imagesPhoto = 'assets/images/photo.png'; + static const String imagesPoyline = 'assets/images/poyline.png'; + static const String imagesRabbitSayGood = 'assets/images/rabbitSayGood.png'; + static const String imagesSpeakFace = 'assets/images/speak_face.png'; + static const String imagesStar = 'assets/images/star.png'; + static const String imagesSwitchHomeIcon = 'assets/images/switch_home_icon.png'; + static const String imagesToHomeBottom = 'assets/images/to_home_bottom.png'; + static const String imagesTranslatorResultBackground = 'assets/images/translatorResultBackground.png'; + static const String jsonLanguage = 'assets/json/language.json'; + static const String jsonTranslationScene = 'assets/json/translation_scene.json'; + static const String launcherIconLauncherIcon = 'assets/launcher_icon/launcher_icon.png'; + static const String svgDailyGreetings = 'assets/svg/daily_greetings.svg'; + static const String svgDinning = 'assets/svg/dinning.svg'; + static const String svgEars = 'assets/svg/ears.svg'; + static const String svgFaceVoice = 'assets/svg/face_voice.svg'; + static const String svgFamily = 'assets/svg/family.svg'; + static const String svgHealthy = 'assets/svg/healthy.svg'; + static const String svgHomeMore = 'assets/svg/homeMore.svg'; + static const String svgHomeRightIcon = 'assets/svg/home_right_icon.svg'; + static const String svgHomeVoice = 'assets/svg/homeVoice.svg'; + static const String svgMakeFriends = 'assets/svg/make_friends.svg'; + static const String svgRotate = 'assets/svg/rotate.svg'; + static const String svgShopping = 'assets/svg/shopping.svg'; + static const String svgSpeaker = 'assets/svg/speaker.svg'; + static const String svgSpeakerWhite = 'assets/svg/speaker_white.svg'; + static const String svgStudy = 'assets/svg/study.svg'; + static const String svgTransportation = 'assets/svg/transportation.svg'; + static const String svgTravel = 'assets/svg/travel.svg'; + static const String svgWork = 'assets/svg/work.svg'; + +} diff --git a/lib/global/app_config.dart b/lib/global/app_config.dart new file mode 100644 index 0000000..0f4766c --- /dev/null +++ b/lib/global/app_config.dart @@ -0,0 +1,9 @@ +// Author: fengshengxiong +// Date: 2024/6/13 +// Description: App配置 + +class AppConfig { + /// App名称 + static const appName = 'TransLark'; +} + diff --git a/lib/global/global_state.dart b/lib/global/global_state.dart deleted file mode 100755 index b55dadb..0000000 --- a/lib/global/global_state.dart +++ /dev/null @@ -1,16 +0,0 @@ -import 'package:flutter_tts/flutter_tts.dart'; -import 'package:get/get.dart'; -import 'package:translator_lux/enum/tts_enum.dart'; - -class GlobalState { - static RxString fromCountryName = "English".obs; - static RxString fromCountryCode = "us".obs; - static RxString fromLanguageCode = "en".obs; - - static RxString toCountryName = "Chinese".obs; - static RxString toCountryCode = "cn".obs; - static RxString toLanguageCode = "zh-cn".obs; - - static FlutterTts ttsController = FlutterTts(); - static TtsEnum ttsEnum = TtsEnum.stopped; -} diff --git a/lib/global/speech_to_text_manager.dart b/lib/global/speech_to_text_manager.dart new file mode 100644 index 0000000..eb884fc --- /dev/null +++ b/lib/global/speech_to_text_manager.dart @@ -0,0 +1,85 @@ +// Author: fengshengxiong +// Date: 2024/5/8 +// Description: 语音转文本 + +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:speech_to_text/speech_recognition_error.dart'; +import 'package:speech_to_text/speech_to_text.dart'; +import 'package:trans_lark/widget/base_easyloading.dart'; + +class SpeechToTextManager { + static final SpeechToTextManager _instance = SpeechToTextManager._(); + + factory SpeechToTextManager() { + return _instance; + } + + SpeechToTextManager._(); + + final _speechToText = SpeechToText(); + var hasSpeech = false; + var isListening = false.obs; + + Future initSpeech() async { + try { + BaseEasyLoading.loading(); + hasSpeech = await _speechToText.initialize( + onStatus: _statusListener, + onError: _errorListener, + ); + BaseEasyLoading.dismiss(); + _speechToText.statusListener ??= _statusListener; + _speechToText.errorListener ??= _errorListener; + } catch (e) { + debugPrint('Speech recognition failed: ${e.toString()}'); + } + } + + void _statusListener(String status) { + debugPrint('状态:$status'); + if (status == 'listening') { + isListening.value = true; + } else { + isListening.value = false; + } + } + + void _errorListener(SpeechRecognitionError error) { + debugPrint('Received error status: $error, listening: ${_speechToText.isListening}'); + BaseEasyLoading.toast('Speech recognition failed: ${error.errorMsg}'); + } + + Future startListening(String localeId, SpeechResultListener onResult) async { + BaseEasyLoading.loading(); + try { + await _speechToText.listen( + onResult: onResult, + localeId: localeId, + listenFor: const Duration(minutes: 30), + pauseFor: const Duration(minutes: 3), + listenOptions: SpeechListenOptions( + cancelOnError: true, + autoPunctuation: true, + listenMode: ListenMode.dictation, + ), + ); + BaseEasyLoading.dismiss(); + } catch (e) { + if (e.runtimeType == ListenFailedException) { + debugPrint('speechToText.listen:${(e as ListenFailedException).message}'); + } else { + debugPrint('speechToText.listen:${e.toString()}'); + } + BaseEasyLoading.toast('The current language does not support speech recognition'); + } + } + + Future stopListening() async { + if (isListening.value) { + BaseEasyLoading.loading(); + await _speechToText.stop(); + BaseEasyLoading.dismiss(); + } + } +} diff --git a/lib/global/translate_language.dart b/lib/global/translate_language.dart new file mode 100644 index 0000000..dd752e3 --- /dev/null +++ b/lib/global/translate_language.dart @@ -0,0 +1,68 @@ +// Author: fengshengxiong +// Date: 2024/5/8 +// Description: 历史数据操作单例 + +import 'dart:convert'; +import 'dart:ui'; + +import 'package:devicelocale/devicelocale.dart'; +import 'package:flutter/services.dart'; +import 'package:get/get.dart'; +import 'package:trans_lark/entity/language_entity.dart'; +import 'package:trans_lark/generated/assets.dart'; +import 'package:trans_lark/util/num_util.dart'; + +class TranslateLanguage { + TranslateLanguage._(); + + static final TranslateLanguage _instance = TranslateLanguage._(); + + factory TranslateLanguage() { + return _instance; + } + + /// 支持翻译的所有语言 + var languageList = [].obs; + + /// 源语言 + var fromLanguageEntity = LanguageEntity( + languageCode: 'en', + languageName: 'English', + ).obs; + + /// 目标语言 + var toLanguageEntity = LanguageEntity( + languageCode: 'zh-cn', + languageName: 'Chinese (Simplified)', + ).obs; + + /// 是否选择的源语言 + var isSelectFromLanguage = true.obs; + + Future init() async { + var data = jsonDecode(await rootBundle.loadString(Assets.jsonLanguage)); + if (data != null && data is List) { + languageList.value = data.map((e) => LanguageEntity.fromMap(e)).toList(); + } + + List languages = await Devicelocale.preferredLanguagesAsLocales; + if (languages.isNotEmpty) { + var languageEntity = languageList.firstWhereOrNull((e) => e.languageCode.contains(languages[0].languageCode)); + if (languageEntity != null) { + toLanguageEntity.value = languageEntity.copyWith(); + } + if (toLanguageEntity.value.languageCode == fromLanguageEntity.value.languageCode) { + var index = 0; + for (var i = 0; i < languageList.length; ++i) { + var o = languages[i]; + if (o.languageCode == toLanguageEntity.value.languageCode) { + index = i; + break; + } + } + var num = NumUtil.getRandomNumberExcludingCurrent(0, languageList.length, index); + fromLanguageEntity.value = languageList[num].copyWith(); + } + } + } +} diff --git a/lib/global/tts_manager.dart b/lib/global/tts_manager.dart new file mode 100755 index 0000000..5652869 --- /dev/null +++ b/lib/global/tts_manager.dart @@ -0,0 +1,43 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter_tts/flutter_tts.dart'; +import 'package:trans_lark/widget/base_easyloading.dart'; + +class TtsManager { + TtsManager._(); + + static final TtsManager _instance = TtsManager._(); + + factory TtsManager() { + return _instance; + } + + late FlutterTts ttsController; + + Future initTts() async { + debugPrint("TTS Service init start"); + ttsController = FlutterTts(); + await ttsController.setSharedInstance(true); + await ttsController.setIosAudioCategory( + IosTextToSpeechAudioCategory.ambient, + [ + IosTextToSpeechAudioCategoryOptions.allowBluetooth, + IosTextToSpeechAudioCategoryOptions.allowBluetoothA2DP, + IosTextToSpeechAudioCategoryOptions.mixWithOthers + ], + IosTextToSpeechAudioMode.voicePrompt); + await ttsController.awaitSpeakCompletion(true); + await ttsController.awaitSynthCompletion(true); + debugPrint("TTS Service init success"); + } + + Future translatorTtsPlay(String text, String language) async { + try { + await ttsController.stop(); + await ttsController.setLanguage(language); + await ttsController.speak(text); + } catch (e) { + BaseEasyLoading.toast('This language cannot be played'); + return; + } + } +} diff --git a/lib/main.dart b/lib/main.dart index 63328ab..5e2376a 100755 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,38 +1,66 @@ -import 'package:camera/camera.dart'; +import 'dart:io'; + import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_easyloading/flutter_easyloading.dart'; +import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart'; import 'package:get/get.dart'; -import 'package:translator_lux/config/config.dart'; -import 'package:translator_lux/core/router/router.dart'; -import 'package:translator_lux/dataBase/translator_data_base.dart'; -import 'package:translator_lux/util/tts_util.dart'; +import 'package:trans_lark/core/router/router.dart'; +import 'package:trans_lark/global/app_config.dart'; +import 'package:trans_lark/global/translate_language.dart'; +import 'package:trans_lark/global/tts_manager.dart'; +import 'package:trans_lark/storage/hive_storage.dart'; +import 'package:trans_lark/widget/base_easyloading.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); - AppConfig.cameraList = await availableCameras(); + + // 初始化Hive + await initHive(); + + // 竖屏 + SystemChrome.setPreferredOrientations([ + DeviceOrientation.portraitUp, + DeviceOrientation.portraitDown, + ]); + + // 设置安卓状态栏背景色透明 + if (Platform.isAndroid) { + SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle( + statusBarColor: Colors.transparent, + )); + } + + await TranslateLanguage().init(); + await TtsManager().initTts(); + runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); - // This widget is the root of your application. @override Widget build(BuildContext context) { - return GetMaterialApp( - debugShowCheckedModeBanner: false, - title: 'TranslatorLux', - theme: ThemeData( - colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), - useMaterial3: true, + final easyLoading = EasyLoading.init(); + return KeyboardDismissOnTap( + child: GetMaterialApp( + debugShowCheckedModeBanner: false, + title: AppConfig.appName, + theme: ThemeData( + colorScheme: ColorScheme.fromSeed(seedColor: const Color.fromARGB(255, 185, 239, 200)), + useMaterial3: true, + ), + initialRoute: GetRouter.home, + getPages: GetRouter.routers, + defaultTransition: Transition.rightToLeft, + builder: (context, widget) { + BaseEasyLoading.configLoading(); + widget = easyLoading(context, widget); + // 设置文字大小不随系统设置改变 + return MediaQuery.withNoTextScaling(child: widget); + }, ), - initialBinding: BindingsBuilder(() async { - debugPrint("init start"); - await TtsUtil.initTts(); - await TranslatorDataBase.init(); - debugPrint("init success"); - }), - initialRoute: GetRouter.initRouterName, - getPages: GetRouter.routers, ); } } diff --git a/lib/page/face_to_face/face_to_face_binding.dart b/lib/page/face_to_face/face_to_face_binding.dart new file mode 100644 index 0000000..b3c77e8 --- /dev/null +++ b/lib/page/face_to_face/face_to_face_binding.dart @@ -0,0 +1,10 @@ +import 'package:get/get.dart'; + +import 'face_to_face_controller.dart'; + +class FaceToFaceBinding extends Bindings { + @override + void dependencies() { + Get.lazyPut(() => FaceToFaceController()); + } +} diff --git a/lib/page/face_to_face/face_to_face_controller.dart b/lib/page/face_to_face/face_to_face_controller.dart new file mode 100644 index 0000000..49ccb40 --- /dev/null +++ b/lib/page/face_to_face/face_to_face_controller.dart @@ -0,0 +1,130 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:speech_to_text/speech_recognition_result.dart'; +import 'package:translator/translator.dart'; +import 'package:trans_lark/global/speech_to_text_manager.dart'; +import 'package:trans_lark/global/translate_language.dart'; +import 'package:trans_lark/util/obj_util.dart'; +import 'package:trans_lark/widget/base_easyloading.dart'; + +class FaceToFaceController extends GetxController with GetSingleTickerProviderStateMixin{ + final aboveScrollController = ScrollController(); + final underScrollController = ScrollController(); + + var angle = 0.0.obs; + + /// 翻译 + final _translator = GoogleTranslator(); + + /// 显示的文本 + var aboveText = ''.obs; + var underText = ''.obs; + + /// 是否选择的下方 + var selectedUnder = true.obs; + + @override + void onReady() async { + super.onReady(); + onTapSpeak(true); + } + + @override + void onClose() async { + await SpeechToTextManager().stopListening(); + aboveScrollController.dispose(); + underScrollController.dispose(); + super.onClose(); + } + + Future onBackTap() async { + await SpeechToTextManager().stopListening(); + Get.back(); + } + + /// 旋转 180 度 + void onTapRotate() { + angle.value -= 0.5; + } + + Future onTapSpeak(bool isUnder) async { + if (!SpeechToTextManager().hasSpeech) { + await SpeechToTextManager().initSpeech(); + } + if (SpeechToTextManager().hasSpeech) { + if (isUnder) { + if (!selectedUnder.value) { + await SpeechToTextManager().stopListening(); + } + selectedUnder.value = true; + _scrollToBottom(); + SpeechToTextManager().startListening(TranslateLanguage().fromLanguageEntity.value.languageCode, _onSpeechResult); + } else { + if (selectedUnder.value) { + await SpeechToTextManager().stopListening(); + } + selectedUnder.value = false; + _scrollToBottom(); + SpeechToTextManager().startListening(TranslateLanguage().toLanguageEntity.value.languageCode, _onSpeechResult); + } + } else { + BaseEasyLoading.toast('Speech not available'); + } + } + + Future _onSpeechResult(SpeechRecognitionResult result) async { + debugPrint('识别结果:${result.recognizedWords}'); + if (ObjUtil.isNotEmptyStr(result.recognizedWords)) { + if (selectedUnder.value) { + underText.value += '${result.recognizedWords}\n'; + var translateText = await _translation(result.recognizedWords); + debugPrint('翻译结果:${result.recognizedWords}'); + if (ObjUtil.isNotEmptyStr(translateText)) { + aboveText.value += '$translateText\n'; + } + } else { + aboveText.value += '${result.recognizedWords}\n'; + var translateText = await _translation(result.recognizedWords); + debugPrint('翻译结果:${result.recognizedWords}'); + if (ObjUtil.isNotEmptyStr(translateText)) { + underText.value += '$translateText\n'; + } + } + _scrollToBottom(); + } + } + + Future _translation(String sourceText) async { + try { + String from = selectedUnder.value ? TranslateLanguage().fromLanguageEntity.value.languageCode : TranslateLanguage().toLanguageEntity.value.languageCode; + String to = selectedUnder.value ? TranslateLanguage().toLanguageEntity.value.languageCode : TranslateLanguage().fromLanguageEntity.value.languageCode; + Translation translate = await _translator.translate( + sourceText, + from: from, + to: to, + ); + return translate.text; + } catch (e) { + debugPrint('Translation failed,${e.toString()}'); + } + return ''; + } + + /// 滚动到列表的最底部 + void _scrollToBottom() { + if (aboveScrollController.hasClients) { + aboveScrollController.animateTo( + aboveScrollController.position.maxScrollExtent, + duration: const Duration(milliseconds: 200), + curve: Curves.easeOut, + ); + } + if (underScrollController.hasClients) { + underScrollController.animateTo( + underScrollController.position.maxScrollExtent, + duration: const Duration(milliseconds: 200), + curve: Curves.easeOut, + ); + } + } +} diff --git a/lib/page/face_to_face/face_to_face_view.dart b/lib/page/face_to_face/face_to_face_view.dart new file mode 100644 index 0000000..f8ee388 --- /dev/null +++ b/lib/page/face_to_face/face_to_face_view.dart @@ -0,0 +1,239 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:get/get.dart'; +import 'package:trans_lark/generated/assets.dart'; +import 'package:trans_lark/global/speech_to_text_manager.dart'; +import 'package:trans_lark/util/obj_util.dart'; +import 'package:trans_lark/widget/base_appbar.dart'; +import 'package:wave/config.dart'; +import 'package:wave/wave.dart'; + +import 'face_to_face_controller.dart'; + +class FaceToFaceView extends StatelessWidget { + FaceToFaceView({super.key}); + + final controller = Get.find(); + + @override + Widget build(BuildContext context) { + return Scaffold( + extendBodyBehindAppBar: true, + extendBody: true, + backgroundColor: Colors.black, + appBar: BaseAppBar( + backgroundColor: Colors.transparent , + backColor: Colors.white, + onBackTap: controller.onBackTap, + actionWidget: Padding( + padding: const EdgeInsets.only(right: 10), + child: ClipOval( + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: controller.onTapRotate, + child: Padding( + padding: const EdgeInsets.all(10), + child: SvgPicture.asset( + Assets.svgRotate, + width: 24, + height: 24, + ), + ), + ), + ), + ), + ), + ), + body: Column( + children: [ + _buildAbove(context), + _buildUnder(context), + ], + ), + ); + } + + Widget _buildAbove(BuildContext context) { + return Expanded( + child: GestureDetector( + behavior: HitTestBehavior.translucent, + onTap: () => controller.onTapSpeak(false), + child: SizedBox( + width: MediaQuery.of(context).size.width, + height: double.infinity, + child: Stack( + children: [ + Obx(() { + return Visibility( + visible: !controller.selectedUnder.value, + child: SpeechToTextManager().isListening.value ? RotatedBox( + quarterTurns: 2, + child: WaveWidget( + config: CustomConfig( + colors: [ + const Color(0xff45A7FE).withOpacity(0.2), + const Color(0xff45A7FE), + ], + durations: [4000, 8000], + heightPercentages: [-0.08, -0.08], + ), + size: const Size(double.infinity, double.infinity), + backgroundColor: Colors.transparent, + waveAmplitude: 4, + // wavePhase: 0, + // waveFrequency: 0, + ), + ) : Container( + width: double.infinity, + height: double.infinity, + color: const Color(0xff45A7FE), + ), + ); + }), + Obx(() { + return Container( + width: double.infinity, + height: double.infinity, + padding: EdgeInsets.fromLTRB(30, SpeechToTextManager().isListening.value + ? MediaQuery.of(context).padding.top + kToolbarHeight + : 0, 30, 40), + child: AnimatedRotation( + turns: controller.angle.value, + duration: const Duration(milliseconds: 500), + child: Column( + mainAxisAlignment: SpeechToTextManager().isListening.value ? MainAxisAlignment.start : MainAxisAlignment.center, + crossAxisAlignment: SpeechToTextManager().isListening.value ? CrossAxisAlignment.start : CrossAxisAlignment.center, + children: [ + if (!controller.selectedUnder.value) ...[ + SvgPicture.asset( + Assets.svgFaceVoice, + width: 24, + height: 24, + ), + const SizedBox(height: 20), + ], + Flexible( + child: Visibility( + visible: SpeechToTextManager().isListening.value && ObjUtil.isNotEmptyStr(controller.aboveText.value), + replacement: const Text( + 'Please start talking', + style: TextStyle( + color: Colors.white, + fontSize: 22, + fontWeight: FontWeight.w500, + ), + ), + child: SingleChildScrollView( + controller: controller.aboveScrollController, + child: Text( + controller.aboveText.value, + style: const TextStyle( + color: Colors.white, + fontSize: 18, + height: 1.8, + ), + ), + ), + ), + ), + ], + ), + ), + ); + }), + ], + ), + ), + ), + ); + } + + Widget _buildUnder(BuildContext context) { + return Expanded( + child: GestureDetector( + behavior: HitTestBehavior.translucent, + onTap: () => controller.onTapSpeak(true), + child: SizedBox( + width: MediaQuery.of(context).size.width, + height: double.infinity, + child: Stack( + children: [ + Obx(() { + return Visibility( + visible: controller.selectedUnder.value, + child: SpeechToTextManager().isListening.value ? WaveWidget( + config: CustomConfig( + colors: [ + const Color(0xff87ECB3).withOpacity(0.2), + const Color(0xff87ECB3), + ], + durations: [4000, 8000], + heightPercentages: [-0.08, -0.08], + ), + size: const Size(double.infinity, double.infinity), + backgroundColor: Colors.transparent, + waveAmplitude: 4, + // wavePhase: 0, + // waveFrequency: 0, + ) : Container( + width: double.infinity, + height: double.infinity, + color: const Color(0xff87ECB3), + ), + ); + }), + Obx(() { + return Container( + width: double.infinity, + height: double.infinity, + padding: EdgeInsets.fromLTRB(30, SpeechToTextManager().isListening.value + ? 80 + : 0, 30, MediaQuery.of(context).padding.bottom), + child: Column( + mainAxisAlignment: SpeechToTextManager().isListening.value ? MainAxisAlignment.start : MainAxisAlignment.center, + crossAxisAlignment: SpeechToTextManager().isListening.value ? CrossAxisAlignment.start : CrossAxisAlignment.center, + children: [ + if (controller.selectedUnder.value) ...[ + SvgPicture.asset( + Assets.svgFaceVoice, + width: 24, + height: 24, + ), + const SizedBox(height: 20), + ], + Flexible( + child: Visibility( + visible: SpeechToTextManager().isListening.value && ObjUtil.isNotEmptyStr(controller.underText.value), + replacement: const Text( + 'Please start talking', + style: TextStyle( + color: Colors.white, + fontSize: 22, + fontWeight: FontWeight.w500, + ), + ), + child: SingleChildScrollView( + controller: controller.underScrollController, + child: Text( + controller.underText.value, + style: const TextStyle( + color: Colors.white, + fontSize: 18, + height: 1.8, + ), + ), + ), + ), + ), + ], + ), + ); + }), + ], + ), + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/page/home/home_logic.dart b/lib/page/home/home_logic.dart new file mode 100755 index 0000000..62b0d6e --- /dev/null +++ b/lib/page/home/home_logic.dart @@ -0,0 +1,262 @@ +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:get/get.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:permission_handler/permission_handler.dart'; +import 'package:speech_to_text/speech_recognition_result.dart'; +import 'package:trans_lark/core/router/router.dart'; +import 'package:trans_lark/entity/language_entity.dart'; +import 'package:trans_lark/entity/scene_entity.dart'; +import 'package:trans_lark/generated/assets.dart'; +import 'package:trans_lark/global/speech_to_text_manager.dart'; +import 'package:trans_lark/global/translate_language.dart'; +import 'package:trans_lark/global/tts_manager.dart'; +import 'package:trans_lark/page/settings/settings_view.dart'; +import 'package:trans_lark/util/device_info_util.dart'; +import 'package:trans_lark/util/num_util.dart'; +import 'package:trans_lark/util/obj_util.dart'; +import 'package:trans_lark/util/permission_util.dart'; +import 'package:trans_lark/util/tracking_authorization_util.dart'; +import 'package:trans_lark/widget/base_easyloading.dart'; +import 'package:trans_lark/widget/photo_picker_bottom_sheet.dart'; +import 'package:trans_lark/widget/speak_dialog.dart'; + +/// @description: +/// @author +/// @date: 2024-06-26 14:23:43 +class HomeLogic extends GetxController { + var lastWords = ''; + + var fromLanguage = Rx(null); + var toLanguage = Rx(null); + var fromStr = Rx(null); + var toStr = Rx(null); + + @override + void onInit() { + super.onInit(); + TrackingAuthorizationUtil().requestTrackingAuthorization(); + } + + @override + void onReady() { + super.onReady(); + _getDailyQuote(); + } + + Future _getDailyQuote() async { + var languageScene = [ + LanguageEntity(languageCode: 'ar', languageName: 'Arabic'), + LanguageEntity(languageCode: 'bn', languageName: 'Bengali'), + LanguageEntity(languageCode: 'zh-cn', languageName: 'Chinese (Simplified)'), + LanguageEntity(languageCode: 'en', languageName: 'English'), + LanguageEntity(languageCode: 'fr', languageName: 'French'), + LanguageEntity(languageCode: 'de', languageName: 'German'), + LanguageEntity(languageCode: 'hi', languageName: 'Hindi'), + LanguageEntity(languageCode: 'ja', languageName: 'Japanese'), + LanguageEntity(languageCode: 'jv', languageName: 'Javanese'), + LanguageEntity(languageCode: 'ko', languageName: 'Korean'), + LanguageEntity(languageCode: 'pt', languageName: 'Portuguese'), + LanguageEntity(languageCode: 'pa', languageName: 'Punjabi'), + LanguageEntity(languageCode: 'ru', languageName: 'Russian'), + LanguageEntity(languageCode: 'es', languageName: 'Spanish'), + ]; + final f = NumUtil.getRandomNumber(0, languageScene.length); + final t = NumUtil.getRandomNumberExcludingCurrent(0, languageScene.length, f); + fromLanguage.value = languageScene[f]; + toLanguage.value = languageScene[t]; + + var data = jsonDecode(await rootBundle.loadString(Assets.jsonTranslationScene)); + if (data != null && data is List) { + var sceneTypeList = data.map((e) => SceneEntity.fromMap(e)).toList(); + if (sceneTypeList.isNotEmpty) { + var x = NumUtil.getRandomNumber(0, sceneTypeList.length); + SceneEntity entity = sceneTypeList[x]; + if (entity.sceneList != null && entity.sceneList!.isNotEmpty) { + var y = NumUtil.getRandomNumber(0, entity.sceneList!.length); + + fromStr.value = _getSentence(true, entity.sceneList![y]); + toStr.value = _getSentence(false, entity.sceneList![y]); + } + } + } + } + + String _getSentence(bool isFrom, SceneList item) { + List keys = item.toMap().keys.toList(); + int index = 0; + for (var i = 0; i < keys.length; ++i) { + var e = keys[i]; + if (e == (isFrom ? fromLanguage.value?.languageName : toLanguage.value?.languageName)) { + index = i; + break; + } + } + if (item.toMap().values.toList()[index] != null) { + return item.toMap().values.toList()[index]; + } + return 'loading'; + } + + void translatorTtsPlay(bool isFrom) { + if (fromLanguage.value != null && toLanguage.value != null) { + TtsManager().translatorTtsPlay( + isFrom ? fromStr.value! : toStr.value!, + isFrom ? fromLanguage.value!.languageCode : toLanguage.value!.languageCode, + ); + } + } + + void openSettings() { + Get.bottomSheet( + isScrollControlled: true, + SettingsView(), + ); + } + + void openSceneType() { + Get.toNamed(GetRouter.sceneType); + } + + Future onTapPhotos() async { + Get.bottomSheet( + isScrollControlled: true, + PhotoPickerBottomSheet( + funCamera: () async { + bool result = await PermissionUtil.checkPermission([Permission.camera]); + if (!result) return; + _openCameraGallery(ImageSource.camera); + }, + funGallery: () async { + Permission permission = Permission.photos; + if (Platform.isAndroid) { + int sdkInt = await DeviceInfoUtil.getAndroidSDKInt(); + if (sdkInt <= 32) { + permission = Permission.storage; + } else { + permission = Permission.photos; + } + } + bool result = await PermissionUtil.checkPermission([permission]); + if (!result) return; + _openCameraGallery(ImageSource.gallery); + }, + ), + ); + } + + 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)) { + BaseEasyLoading.toast('Selected area should be able to crop image with at least 32 width and height.'); + } + }, + ))?.then( + (onValue) { + if (ObjUtil.isEmpty(detectedValue)) { + BaseEasyLoading.toast('No text recognized'); + return; + } + Get.toNamed( + GetRouter.translateResult, + arguments: {"sourceText": detectedValue}, + ); + }, + ); + } + } + + Future onTapSpeak() async { + bool micResult = await PermissionUtil.checkPermission([Permission.microphone]); + if (!micResult) return; + if (Platform.isIOS) { + bool speechResult = await PermissionUtil.checkPermission([Permission.speech]); + if (!speechResult) return; + } + await Get.dialog( + barrierDismissible: true, + useSafeArea: false, + SpeakDialog( + isListening: SpeechToTextManager().isListening, + onTap: () async { + if (!SpeechToTextManager().hasSpeech) { + await SpeechToTextManager().initSpeech(); + } + if (SpeechToTextManager().hasSpeech) { + if (SpeechToTextManager().isListening.value) { + _openTranslatorResultPage(); + } else { + SpeechToTextManager().startListening(TranslateLanguage().fromLanguageEntity.value.languageCode, _onSpeechResult); + } + } else { + Get.back(); + BaseEasyLoading.toast('Speech not available'); + } + }, + ), + ); + if (Get.isDialogOpen != null && !Get.isDialogOpen!) { + SpeechToTextManager().stopListening(); + } + } + + void _onSpeechResult(SpeechRecognitionResult result) { + debugPrint('识别结果:${result.recognizedWords}'); + lastWords = result.recognizedWords; + } + + void _openTranslatorResultPage() { + if (ObjUtil.isEmpty(lastWords)) { + BaseEasyLoading.toast('No text recognized'); + return; + } + Get.back(); + Get.toNamed( + GetRouter.translateResult, + arguments: {"sourceText": lastWords}, + ); + } + + void openTranslate() { + Get.toNamed(GetRouter.translate); + } + + Future openFaceToFace() async { + bool micResult = await PermissionUtil.checkPermission([Permission.microphone]); + if (!micResult) return; + if (Platform.isIOS) { + bool speechResult = await PermissionUtil.checkPermission([Permission.speech]); + if (!speechResult) return; + } + Get.toNamed(GetRouter.faceToFace); + } + + void openTranslateHistory() { + Get.toNamed(GetRouter.translateHistory); + } +} diff --git a/lib/page/home/home_view.dart b/lib/page/home/home_view.dart new file mode 100755 index 0000000..89722dd --- /dev/null +++ b/lib/page/home/home_view.dart @@ -0,0 +1,442 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:get/get.dart'; +import 'package:trans_lark/generated/assets.dart'; +import 'package:trans_lark/page/home/home_logic.dart'; +import 'package:trans_lark/util/obj_util.dart'; +import 'package:trans_lark/widget/language_bar.dart'; + +/// @description: 翻译首页 +/// @author : lifu +/// @date: 2024-06-26 14:23:43 +class HomePage extends StatelessWidget { + final HomeLogic logic = Get.put(HomeLogic()); + + HomePage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Container( + width: double.infinity, + height: double.infinity, + padding: const EdgeInsets.symmetric(horizontal: 20), + decoration: const BoxDecoration( + image: DecorationImage( + image: AssetImage( + Assets.imagesHomeBackground, + ), + fit: BoxFit.fill, + ), + ), + child: SafeArea( + child: Column( + children: [ + _buildHead(), + Expanded( + child: SingleChildScrollView( + child: Column( + children: [ + _buildDailyQuote(), + Column( + children: [ + const SizedBox(height: 66), + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Column( + children: [ + GestureDetector( + onTap: logic.onTapPhotos, + child: Container( + width: 72, + height: 72, + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: const Color(0xffD2FFFB), + borderRadius: BorderRadius.circular(36), + ), + child: Image.asset( + Assets.imagesPhoto, + ), + ), + ), + const Padding( + padding: EdgeInsets.only(top: 10), + child: Text( + "Photo", + style: TextStyle( + fontSize: 14, + color: Colors.black, + fontWeight: FontWeight.w500, + ), + ), + ) + ], + ), + Column( + children: [ + GestureDetector( + onTap: logic.openFaceToFace, + child: Container( + width: 72, + height: 72, + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: const Color(0xffE1E2FE), + borderRadius: BorderRadius.circular(36), + ), + child: Image.asset( + Assets.imagesDialog, + ), + ), + ), + const Padding( + padding: EdgeInsets.only(top: 10), + child: Text( + "Face2face", + style: TextStyle( + fontSize: 14, + color: Colors.black, + fontWeight: FontWeight.w500, + ), + ), + ) + ], + ), + Column( + children: [ + GestureDetector( + onTap: logic.openTranslateHistory, + child: Container( + width: 72, + height: 72, + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: const Color(0xffFEE5C5), + borderRadius: BorderRadius.circular(36), + ), + child: Image.asset( + Assets.imagesHistory, + ), + ), + ), + const Padding( + padding: EdgeInsets.only(top: 10), + child: Text( + "History", + style: TextStyle( + fontSize: 14, + color: Colors.black, + fontWeight: FontWeight.w500, + ), + ), + ) + ], + ), + ], + ), + const SizedBox(height: 96), + Column( + children: [ + GestureDetector( + onTap: logic.openTranslate, + child: Container( + height: 76, + alignment: Alignment.center, + decoration: BoxDecoration( + color: const Color.fromARGB(213, 69, 168, 254), + borderRadius: BorderRadius.circular(16), + border: Border.all( + width: 1.5, + color: const Color(0xff0087FF), + ), + ), + child: Row( + children: [ + const Expanded( + child: Padding( + padding: EdgeInsets.only(left: 20), + child: Text( + "Type to Translate", + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w400, + color: Colors.white, + ), + ), + ), + ), + Padding( + padding: const EdgeInsets.only(right: 20), + child: GestureDetector( + onTap: logic.onTapSpeak, + child: Container( + height: 36, + width: 36, + padding: const EdgeInsets.all(5), + decoration: BoxDecoration( + color: const Color(0xff8BCFFE), + borderRadius: BorderRadius.circular(18), + ), + child: + SvgPicture.asset(Assets.svgHomeVoice), + ), + ), + ) + ], + ), + ), + ), + const SizedBox( + height: 20, + ), + const LanguageBar(), + const SizedBox( + height: 20, + ), + ], + ), + ], + ), + ], + ), + ), + ), + ], + ), + ), + ), + ); + } + + Widget _buildHead() { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 10), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text( + "Courses", + style: TextStyle( + color: Color(0xff1E1E1E), + fontSize: 24, + fontWeight: FontWeight.w700, + ), + ), + GestureDetector( + onTap: logic.openSettings, + child: ClipOval( + child: Container( + width: 32, + height: 32, + color: const Color(0xff4ECA8C), + child: FittedBox( + fit: BoxFit.none, + child: SvgPicture.asset( + Assets.svgHomeMore, + width: 20, + height: 20, + ), + ), + ), + ), + ), + ], + ), + ); + } + + Widget _buildDailyQuote() { + return Stack( + children: [ + GestureDetector( + onTap: logic.openSceneType, + child: Column( + children: [ + const SizedBox(height: 30), + Container( + height: 222, + decoration: BoxDecoration( + border: + Border.all(width: 1.5, color: const Color(0xff3AAA72)), + color: const Color.fromARGB(213, 78, 202, 140), + borderRadius: BorderRadius.circular(16)), + ), + ], + ), + ), + Positioned( + top: 46, + left: 20, + child: GestureDetector( + onTap: logic.openSceneType, + child: Row( + children: [ + const Text( + "Daily Quote", + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w800, + fontStyle: FontStyle.italic, + color: Colors.white, + ), + ), + Padding( + padding: const EdgeInsets.only(left: 5), + child: SvgPicture.asset(Assets.svgHomeRightIcon), + ) + ], + ), + ), + ), + Positioned( + top: -4, + right: 24, + child: Image.asset( + Assets.imagesRabbitSayGood, + width: 90, + height: 90, + ), + ), + Column( + children: [ + const SizedBox( + height: 85, + ), + Container( + width: double.infinity, + height: 168, + padding: const EdgeInsets.symmetric(horizontal: 20), + alignment: Alignment.center, + decoration: BoxDecoration( + border: Border.all(width: 1.5, color: const Color(0xff3AAA72)), + color: Colors.white, + borderRadius: BorderRadius.circular(16), + ), + child: Column( + children: [ + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Obx(() { + return Text( + logic.fromLanguage.value != null ? logic.fromLanguage.value!.languageName : '', + style: const TextStyle( + fontSize: 12, + color: Color(0xffC2C3C5), + ), + ); + }), + const SizedBox(height: 9), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Obx(() { + return Text( + ObjUtil.getStr(logic.fromStr.value), + overflow: TextOverflow.ellipsis, + style: const TextStyle( + fontSize: 16, + color: Color(0xff152A3D), + fontWeight: FontWeight.w500, + ), + ); + }), + ), + GestureDetector( + onTap: () => logic.translatorTtsPlay(true), + child: Container( + width: 24, + height: 24, + padding: const EdgeInsets.all(3), + decoration: BoxDecoration( + color: const Color.fromARGB(78, 78, 202, 140), + borderRadius: BorderRadius.circular(12)), + child: FittedBox( + fit: BoxFit.none, + child: SvgPicture.asset( + Assets.svgSpeaker, + width: 16, + height: 16, + ), + ), + ), + ), + ], + ), + ], + ), + ), + const Divider( + height: 1, + thickness: 1, + ), + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Obx(() { + return Text( + logic.toLanguage.value != null ? logic.toLanguage.value!.languageName : '', + style: const TextStyle( + fontSize: 12, + color: Color(0xffC2C3C5), + ), + ); + }), + const SizedBox(height: 9), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Obx(() { + return Text( + ObjUtil.getStr(logic.toStr.value), + overflow: TextOverflow.ellipsis, + style: const TextStyle( + fontSize: 16, + color: Color(0xff152A3D), + fontWeight: FontWeight.w500, + ), + ); + }), + ), + GestureDetector( + onTap: () => logic.translatorTtsPlay(false), + child: Container( + width: 24, + height: 24, + padding: const EdgeInsets.all(3), + decoration: BoxDecoration( + color: const Color.fromARGB(78, 78, 202, 140), + borderRadius: BorderRadius.circular(12), + ), + child: FittedBox( + fit: BoxFit.none, + child: SvgPicture.asset( + Assets.svgSpeaker, + width: 16, + height: 16, + ), + ), + ), + ), + ], + ), + ], + ), + ), + ], + ), + ), + ], + ), + ], + ); + } +} diff --git a/lib/page/main/main_logic.dart b/lib/page/main/main_logic.dart deleted file mode 100755 index ad4c2ff..0000000 --- a/lib/page/main/main_logic.dart +++ /dev/null @@ -1,10 +0,0 @@ -import 'package:get/get.dart'; - -import 'main_state.dart'; - -/// @description: -/// @author -/// @date: 2024-06-26 14:23:43 -class MainLogic extends GetxController { - final state = MainState(); -} diff --git a/lib/page/main/main_state.dart b/lib/page/main/main_state.dart deleted file mode 100755 index a602119..0000000 --- a/lib/page/main/main_state.dart +++ /dev/null @@ -1,4 +0,0 @@ -/// @description: -/// @author -/// @date: 2024-06-26 14:23:43 -class MainState {} diff --git a/lib/page/main/main_view.dart b/lib/page/main/main_view.dart deleted file mode 100755 index a368714..0000000 --- a/lib/page/main/main_view.dart +++ /dev/null @@ -1,386 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_svg/flutter_svg.dart'; -import 'package:get/get.dart'; -import 'package:translator_lux/page/translator/translator_view.dart'; -import 'package:translator_lux/translator_history/translator_history_view.dart'; -import 'package:translator_lux/widget/camera_widget.dart'; -import 'package:translator_lux/util/image_asset.dart'; -import 'package:translator_lux/util/svg_asset.dart'; -import 'package:translator_lux/util/tts_util.dart'; -import 'package:translator_lux/widget/tranlator_from_to.dart'; - -import 'main_logic.dart'; -import 'main_state.dart'; - -/// @description: 翻译首页 -/// @author : lifu -/// @date: 2024-06-26 14:23:43 -class MainPage extends StatelessWidget { - static String routName = "/mainPage"; - final MainLogic logic = Get.put(MainLogic()); - final MainState state = Get.find().state; - - MainPage({super.key}); - - @override - Widget build(BuildContext context) { - return Scaffold( - // floatingActionButton: FloatingActionButton(onPressed: () async { - // // await TranslatorHistoryTable.cleanShowData(); - - // // List queryShowData = - // // await TranslatorHistoryTable.queryShowData(); - // // for (var element in queryShowData) { - // // debugPrint(element.toString()); - // // } - - // // TranslatorHistoryTable.queryData(); - - // // TranslatorHistoryTable.cleanShowDataByTid( - // // "e1916380-09db-1055-a599-9dcbcefb47d8"); - - // // TranslatorHistoryTable.deleteHistoryDataByTid( - // // "e1916380-09db-1055-a599-9dcbcefb47d8"); - // }), - body: Container( - width: double.infinity, - height: double.infinity, - decoration: BoxDecoration( - image: DecorationImage( - image: AssetImage( - ImageAsset.homeBackground, - ), - fit: BoxFit.cover, - )), - child: SafeArea( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), - child: Column( - children: [ - Padding( - padding: const EdgeInsets.only(top: 20), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Text( - "Courses", - style: - TextStyle(fontSize: 24, fontWeight: FontWeight.w700), - ), - Container( - padding: const EdgeInsets.all(5), - decoration: BoxDecoration( - color: const Color(0xff4ECA8C), - borderRadius: BorderRadius.circular(16)), - child: SvgPicture.asset( - SvgAsset.homeMore, - fit: BoxFit.contain, - ), - ) - ], - ), - ), - Stack( - children: [ - Column( - children: [ - Container( - height: 40, - ), - Container( - height: 233, - decoration: BoxDecoration( - border: Border.all( - width: 1.5, color: const Color(0xff3AAA72)), - color: const Color.fromARGB(213, 78, 202, 140), - borderRadius: BorderRadius.circular(20)), - ), - ], - ), - Row( - crossAxisAlignment: CrossAxisAlignment.end, - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - SizedBox( - width: 130, - height: 50, - child: Row( - children: [ - const Text( - "Daily Quote", - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.w600, - color: Colors.white), - ), - Padding( - padding: const EdgeInsets.only(left: 5), - child: SvgPicture.asset(SvgAsset.homeRightIcon), - ) - ], - ), - ), - Image.asset( - ImageAsset.rabbitSayGood, - width: 90, - height: 90, - ), - ], - ), - Column( - children: [ - const SizedBox( - height: 83, - ), - Container( - padding: const EdgeInsets.all(15), - decoration: BoxDecoration( - border: Border.all( - width: 1.5, color: const Color(0xff3AAA72)), - color: Colors.white, - borderRadius: BorderRadius.circular(20), - ), - height: 190, - width: double.infinity, - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceAround, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text( - "English", - style: TextStyle(color: Colors.grey), - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Text( - "Let's discuss this later.", - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - ), - ), - GestureDetector( - onTap: () { - TtsUtil.translatorTtsPlay( - "Let's discuss this later.", - ); - }, - child: Container( - width: 24, - height: 24, - padding: const EdgeInsets.all(3), - decoration: BoxDecoration( - color: const Color.fromARGB( - 78, 78, 202, 140), - borderRadius: - BorderRadius.circular(12)), - child: SvgPicture.asset( - SvgAsset.speaker, - ), - ), - ), - ], - ), - const Divider( - height: 1, - ), - const Text( - "Chinese", - style: TextStyle(color: Colors.grey), - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Text( - "我们稍后再讨论这个", - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - ), - ), - GestureDetector( - onTap: () { - TtsUtil.translatorTtsPlay("我们稍后再讨论这个"); - }, - child: Container( - width: 24, - height: 24, - padding: const EdgeInsets.all(3), - decoration: BoxDecoration( - color: const Color.fromARGB( - 78, 78, 202, 140), - borderRadius: - BorderRadius.circular(12)), - child: SvgPicture.asset( - SvgAsset.speaker, - ), - ), - ), - ], - ), - ], - ), - ) - ], - ) - ], - ), - Expanded( - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - Column( - children: [ - GestureDetector( - onTap: () { - Get.to(() => const CameraPage()); - }, - child: Container( - width: 72, - height: 72, - padding: const EdgeInsets.all(10), - decoration: BoxDecoration( - color: const Color(0xffD2FFFB), - borderRadius: BorderRadius.circular(36)), - child: Image.asset( - ImageAsset.photo, - ), - ), - ), - const Padding( - padding: EdgeInsets.only(top: 10), - child: Text( - "Photo", - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, - ), - ), - ) - ], - ), - Column( - children: [ - Container( - width: 72, - height: 72, - padding: const EdgeInsets.all(10), - decoration: BoxDecoration( - color: const Color(0xffE1E2FE), - borderRadius: BorderRadius.circular(36)), - child: Image.asset( - ImageAsset.dialog, - ), - ), - const Padding( - padding: EdgeInsets.only(top: 10), - child: Text( - "Dialog", - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, - ), - ), - ) - ], - ), - Column( - children: [ - GestureDetector( - onTap: () { - Get.toNamed(TranslatorHistoryPage.routName); - }, - child: Container( - width: 72, - height: 72, - padding: const EdgeInsets.all(10), - decoration: BoxDecoration( - color: const Color(0xffFEE5C5), - borderRadius: BorderRadius.circular(36)), - child: Image.asset( - ImageAsset.history, - ), - ), - ), - const Padding( - padding: EdgeInsets.only(top: 10), - child: Text( - "History", - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, - ), - ), - ) - ], - ), - ], - ), - Column( - children: [ - GestureDetector( - onTap: () { - Get.toNamed(TranlatorPage.routName); - }, - child: Container( - height: 80, - alignment: Alignment.center, - decoration: BoxDecoration( - color: const Color.fromARGB(213, 69, 168, 254), - borderRadius: BorderRadius.circular(16), - border: Border.all( - width: 1.5, - color: const Color(0xff0087FF), - ), - ), - child: Row( - children: [ - const Expanded( - child: Padding( - padding: EdgeInsets.only(left: 22), - child: Text( - "Type to Translate", - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.w400, - color: Colors.white, - ), - ), - ), - ), - Padding( - padding: const EdgeInsets.only(right: 10), - child: Container( - height: 36, - width: 36, - padding: const EdgeInsets.all(5), - decoration: BoxDecoration( - color: const Color(0xff8BCFFE), - borderRadius: BorderRadius.circular(18), - ), - child: SvgPicture.asset(SvgAsset.homeVoice), - ), - ) - ], - ), - ), - ), - const SizedBox( - height: 50, - ), - const TranslatorFromTo() - ], - ), - ], - )), - ], - ), - )), - ), - ); - } -} diff --git a/lib/page/scene_list/scene_list_binding.dart b/lib/page/scene_list/scene_list_binding.dart new file mode 100644 index 0000000..de4fb91 --- /dev/null +++ b/lib/page/scene_list/scene_list_binding.dart @@ -0,0 +1,10 @@ +import 'package:get/get.dart'; + +import 'scene_list_controller.dart'; + +class SceneListBinding extends Bindings { + @override + void dependencies() { + Get.lazyPut(() => SceneListController()); + } +} diff --git a/lib/page/scene_list/scene_list_controller.dart b/lib/page/scene_list/scene_list_controller.dart new file mode 100644 index 0000000..09e9317 --- /dev/null +++ b/lib/page/scene_list/scene_list_controller.dart @@ -0,0 +1,76 @@ +import 'package:get/get.dart'; +import 'package:trans_lark/entity/language_entity.dart'; +import 'package:trans_lark/entity/scene_entity.dart'; +import 'package:trans_lark/global/tts_manager.dart'; +import 'package:trans_lark/widget/language_scene_bottom_sheet.dart'; + +class SceneListController extends GetxController { + var languageScene = [ + LanguageEntity(languageCode: 'ar', languageName: 'Arabic'), + LanguageEntity(languageCode: 'bn', languageName: 'Bengali'), + LanguageEntity(languageCode: 'zh-cn', languageName: 'Chinese (Simplified)'), + LanguageEntity(languageCode: 'en', languageName: 'English'), + LanguageEntity(languageCode: 'fr', languageName: 'French'), + LanguageEntity(languageCode: 'de', languageName: 'German'), + LanguageEntity(languageCode: 'hi', languageName: 'Hindi'), + LanguageEntity(languageCode: 'ja', languageName: 'Japanese'), + LanguageEntity(languageCode: 'jv', languageName: 'Javanese'), + LanguageEntity(languageCode: 'ko', languageName: 'Korean'), + LanguageEntity(languageCode: 'pt', languageName: 'Portuguese'), + LanguageEntity(languageCode: 'pa', languageName: 'Punjabi'), + LanguageEntity(languageCode: 'ru', languageName: 'Russian'), + LanguageEntity(languageCode: 'es', languageName: 'Spanish'), + ]; + late Rx fromLanguage; + late Rx toLanguage; + + var sceneList = []; + + @override + void onInit() { + super.onInit(); + sceneList = Get.arguments; + fromLanguage = languageScene[3].copyWith().obs; + toLanguage = languageScene[2].copyWith().obs; + } + + void onTapFrom() { + Get.bottomSheet( + LanguageSceneBottomSheet( + languageScene: languageScene, + selectedLanguage: fromLanguage, + ), + ); + } + + void onTapTo() { + Get.bottomSheet( + LanguageSceneBottomSheet( + languageScene: languageScene, + selectedLanguage: toLanguage, + ), + ); + } + + String getSentence(bool isFrom, SceneList item) { + List keys = item.toMap().keys.toList(); + int index = 0; + for (var i = 0; i < keys.length; ++i) { + var e = keys[i]; + if (e == (isFrom ? fromLanguage.value.languageName : toLanguage.value.languageName)) { + index = i; + break; + } + } + if (item.toMap().values.toList()[index] != null) { + return item.toMap().values.toList()[index]; + } + return 'loading'; + } + + void translatorTtsPlay(bool isFrom, SceneList item) { + TtsManager().translatorTtsPlay( + getSentence(isFrom, item), isFrom ? fromLanguage.value.languageCode : toLanguage.value.languageCode + ); + } +} diff --git a/lib/page/scene_list/scene_list_view.dart b/lib/page/scene_list/scene_list_view.dart new file mode 100644 index 0000000..8cff489 --- /dev/null +++ b/lib/page/scene_list/scene_list_view.dart @@ -0,0 +1,176 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:get/get.dart'; +import 'package:trans_lark/entity/scene_entity.dart'; +import 'package:trans_lark/generated/assets.dart'; +import 'package:trans_lark/widget/base_appbar.dart'; +import 'package:trans_lark/widget/base_scrollbar.dart'; +import 'package:trans_lark/widget/language_scene_bar.dart'; + +import 'scene_list_controller.dart'; + +class SceneListView extends StatelessWidget { + SceneListView({super.key}); + + final controller = Get.find(); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: const Color(0xffF5F6F8), + appBar: BaseAppBar( + titleWidget: LanguageSceneBar( + fromLanguage: controller.fromLanguage, + toLanguage: controller.toLanguage, + onTapFrom: controller.onTapFrom, + onTapTo: controller.onTapTo, + ), + ), + body: SafeArea( + top: false, + child: Column( + children: [ + Expanded( + child: BaseScrollbar( + child: ListView.separated( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + itemCount: controller.sceneList.length, + itemBuilder: (context, index) { + var item = controller.sceneList[index]; + return _buildItem(item); + }, + separatorBuilder: (context, index) { + return const SizedBox(height: 12); + }, + ), + ), + ), + ], + ), + ), + ); + } + + Widget _buildItem(SceneList item) { + return Container( + padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 20), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + ), + child: Column( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Obx(() { + return Text( + controller.fromLanguage.value.languageName, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + color: Color(0xffC2C3C5), + fontSize: 12, + fontWeight: FontWeight.w500, + ), + ); + }), + const SizedBox(height: 8), + Row( + children: [ + Expanded( + child: Obx(() { + return Text( + controller.getSentence(true, item), + style: const TextStyle( + color: Color(0xff152A3D), + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ); + }), + ), + GestureDetector( + onTap: () => controller.translatorTtsPlay(true, item), + child: ClipOval( + child: Container( + width: 24, + height: 24, + color: const Color(0xff4ECA8C).withOpacity(0.2), + child: FittedBox( + fit: BoxFit.none, + child: SvgPicture.asset( + Assets.svgSpeaker, + width: 16, + height: 16, + ), + ), + ), + ), + ), + ], + ), + const SizedBox(height: 20), + ], + ), + Container( + width: double.infinity, + height: 1, + color: const Color(0xffF4F4F4), + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 20), + Obx(() { + return Text( + controller.toLanguage.value.languageName, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + color: Color(0xffC2C3C5), + fontSize: 12, + fontWeight: FontWeight.w500, + ), + ); + }), + const SizedBox(height: 8), + Row( + children: [ + Expanded( + child: Obx(() { + return Text( + controller.getSentence(false, item), + style: const TextStyle( + color: Color(0xff152A3D), + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ); + }), + ), + GestureDetector( + onTap: () => controller.translatorTtsPlay(false, item), + child: ClipOval( + child: Container( + width: 24, + height: 24, + color: const Color(0xff4ECA8C).withOpacity(0.2), + child: FittedBox( + fit: BoxFit.none, + child: SvgPicture.asset( + Assets.svgSpeaker, + width: 16, + height: 16, + ), + ), + ), + ), + ), + ], + ), + ], + ), + ], + ), + ); + } +} diff --git a/lib/page/scene_type/scene_type_binding.dart b/lib/page/scene_type/scene_type_binding.dart new file mode 100644 index 0000000..ff54032 --- /dev/null +++ b/lib/page/scene_type/scene_type_binding.dart @@ -0,0 +1,10 @@ +import 'package:get/get.dart'; + +import 'scene_type_controller.dart'; + +class SceneTypeBinding extends Bindings { + @override + void dependencies() { + Get.lazyPut(() => SceneTypeController()); + } +} diff --git a/lib/page/scene_type/scene_type_controller.dart b/lib/page/scene_type/scene_type_controller.dart new file mode 100644 index 0000000..ab93d78 --- /dev/null +++ b/lib/page/scene_type/scene_type_controller.dart @@ -0,0 +1,28 @@ +import 'dart:convert'; + +import 'package:flutter/services.dart'; +import 'package:get/get.dart'; +import 'package:trans_lark/core/router/router.dart'; +import 'package:trans_lark/entity/scene_entity.dart'; +import 'package:trans_lark/generated/assets.dart'; +import 'package:trans_lark/widget/view_state_widget.dart'; + +class SceneTypeController extends GetxController { + var sceneTypeList = [].obs; + var viewState = ViewState.loading.obs; + + @override + void onReady() async { + super.onReady(); + + var data = jsonDecode(await rootBundle.loadString(Assets.jsonTranslationScene)); + if (data != null && data is List) { + sceneTypeList.value = data.map((e) => SceneEntity.fromMap(e)).toList(); + } + viewState.value = sceneTypeList.isNotEmpty ? ViewState.normal : ViewState.empty; + } + + void onTapItem(SceneEntity item) { + Get.toNamed(GetRouter.sceneList, arguments: item.sceneList); + } +} diff --git a/lib/page/scene_type/scene_type_view.dart b/lib/page/scene_type/scene_type_view.dart new file mode 100644 index 0000000..17eb59f --- /dev/null +++ b/lib/page/scene_type/scene_type_view.dart @@ -0,0 +1,157 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:get/get.dart'; +import 'package:trans_lark/entity/scene_entity.dart'; +import 'package:trans_lark/generated/assets.dart'; +import 'package:trans_lark/util/obj_util.dart'; +import 'package:trans_lark/widget/base_appbar.dart'; +import 'package:trans_lark/widget/base_scrollbar.dart'; +import 'package:trans_lark/widget/view_state_widget.dart'; + +import 'scene_type_controller.dart'; + +class SceneTypeView extends StatelessWidget { + SceneTypeView({super.key}); + + final controller = Get.find(); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.white, + appBar: const BaseAppBar( + title: 'Selected oral expressions', + ), + body: SafeArea( + top: false, + child: Obx(() { + return ViewStateWidget( + viewState: controller.viewState.value, + child: BaseScrollbar( + child: ListView.builder( + itemCount: controller.sceneTypeList.length, + itemBuilder: (context, index) { + var item = controller.sceneTypeList[index]; + return _buildItem(item); + }, + ), + ), + ); + }), + ), + ); + } + + Widget _buildItem(SceneEntity item) { + String assetsSvg = Assets.svgShopping; + Color bgColor = const Color(0xff4E5AEC); + if (item.sceneType == 'Shopping') { + assetsSvg = Assets.svgShopping; + bgColor = const Color(0xff4E5AEC); + } else if (item.sceneType == 'Study') { + assetsSvg = Assets.svgStudy; + bgColor = const Color(0xff79CB9A); + } else if (item.sceneType == 'Work') { + assetsSvg = Assets.svgWork; + bgColor = const Color(0xffFC6C6B); + } else if (item.sceneType == 'Healthy') { + assetsSvg = Assets.svgHealthy; + bgColor = const Color(0xff53DED1); + } else if (item.sceneType == 'Daily greetings') { + assetsSvg = Assets.svgDailyGreetings; + bgColor = const Color(0xffADAEFF); + } else if (item.sceneType == 'Family') { + assetsSvg = Assets.svgFamily; + bgColor = const Color(0xffFFEE8B); + } else if (item.sceneType == 'Making friends') { + assetsSvg = Assets.svgMakeFriends; + bgColor = const Color(0xffFFE7EF); + } else if (item.sceneType == 'Transportation') { + assetsSvg = Assets.svgTransportation; + bgColor = const Color(0xff45A7FE); + } else if (item.sceneType == 'Travel') { + assetsSvg = Assets.svgTravel; + bgColor = const Color(0xffFEE5C5); + } else if (item.sceneType == 'Dining') { + assetsSvg = Assets.svgDinning; + bgColor = const Color(0xffFD9D65); + } + return InkWell( + onTap: () => controller.onTapItem(item), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 20), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: 66, + height: 82, + decoration: BoxDecoration( + color: bgColor, + borderRadius: BorderRadius.circular(7), + ), + child: Stack( + children: [ + Positioned( + bottom: 0, + right: 0, + child: FittedBox( + fit: BoxFit.none, + child: SvgPicture.asset( + assetsSvg, + width: 54, + height: 54, + ), + ), + ), + Positioned( + top: 4, + left: 7, + right: 7, + child: Text( + ObjUtil.getStr(item.sceneType).toUpperCase(), + maxLines: 3, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + color: Colors.white, + fontSize: 14, + fontWeight: FontWeight.w700, + ), + ), + ), + ], + ), + ), + const SizedBox(width: 15), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + ObjUtil.getStr(item.sceneType), + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + color: Color(0xff152A3D), + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + Text( + '${item.sceneList != null ? item.sceneList!.length : 0} selected authentic spoken sentences', + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + color: Color(0xff696969), + fontSize: 12, + ), + ), + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/page/settings/settings_controller.dart b/lib/page/settings/settings_controller.dart new file mode 100644 index 0000000..207936e --- /dev/null +++ b/lib/page/settings/settings_controller.dart @@ -0,0 +1,37 @@ +import 'package:get/get.dart'; +import 'package:package_info_plus/package_info_plus.dart'; +import 'package:trans_lark/core/router/router.dart'; + +class SettingsController extends GetxController { + var versionName = ''.obs; + // var options = ['Feedback', 'Privacy Policy', 'User Agreement']; + var options = ['Privacy Policy', 'User Agreement']; + + @override + void onReady() { + super.onReady(); + _getVersion(); + } + + /// 获取版本号 + void _getVersion() async { + final packageInfo = await PackageInfo.fromPlatform(); + versionName.value = 'version number ${packageInfo.version}'; + } + + void itemOnTap(int index) { + if (index == -1) { + + } else if (index == 0) { + Get.toNamed(GetRouter.privacy, arguments: { + 'title': options[index], + 'url': 'https://translark.bitbucket.io/privacy.html', + }); + } else if (index == 1) { + Get.toNamed(GetRouter.privacy, arguments: { + 'title': options[index], + 'url': 'https://translark.bitbucket.io/terms.html', + }); + } + } +} diff --git a/lib/page/settings/settings_view.dart b/lib/page/settings/settings_view.dart new file mode 100644 index 0000000..36f32b6 --- /dev/null +++ b/lib/page/settings/settings_view.dart @@ -0,0 +1,147 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:trans_lark/generated/assets.dart'; +import 'package:trans_lark/global/app_config.dart'; +import 'package:trans_lark/widget/divider_widget.dart'; + +import 'settings_controller.dart'; + +class SettingsView extends StatelessWidget { + SettingsView({super.key}); + + final controller = Get.put(SettingsController()); + + @override + Widget build(BuildContext context) { + return Container( + width: MediaQuery.of(context).size.width, + height: MediaQuery.of(context).size.height * 0.77, + clipBehavior: Clip.antiAlias, + decoration: const BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(26), topRight: Radius.circular(26), + ), + ), + child: Stack( + children: [ + Image.asset( + Assets.imagesHomeBackground, + width: MediaQuery.of(context).size.width, + height: 384, + fit: BoxFit.fitWidth, + ), + Column( + children: [ + const SizedBox(height: 46), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 34), + child: Row( + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(6), + child: Image.asset( + Assets.launcherIconLauncherIcon, + width: 48, + height: 48, + fit: BoxFit.cover, + ), + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + AppConfig.appName, + style: TextStyle( + color: Colors.black, + fontSize: 22, + fontWeight: FontWeight.w500, + overflow: TextOverflow.ellipsis, + ), + ), + Obx(() { + return Text( + controller.versionName.value, + style: const TextStyle( + color: Color(0xff979797), + fontSize: 12, + overflow: TextOverflow.ellipsis, + ), + ); + }), + ], + ), + ), + ], + ), + ), + const SizedBox(height: 15), + // _buildItem('Give a good review', -1), + // const SizedBox(height: 15), + const Padding( + padding: EdgeInsets.symmetric(horizontal: 34), + child: DividerWidget(), + ), + _buildListView(), + Image.asset( + Assets.imagesRabbitSayGood, + width: 90, + height: 90, + fit: BoxFit.cover, + ), + const SizedBox(height: 58), + ], + ), + ], + ), + ); + } + + Widget _buildListView() { + return Expanded( + child: ListView.builder( + itemCount: controller.options.length, + itemBuilder: (context, index) { + var item = controller.options[index]; + return _buildItem(item, index); + }, + ), + ); + } + + Widget _buildItem(String item, int index) { + return Material( + color: Colors.transparent, + child: InkWell( + onTap: () => controller.itemOnTap(index), + child: Container( + padding: const EdgeInsets.symmetric(vertical: 15, horizontal: 30), + child: Row( + children: [ + Expanded( + child: Text( + item, + style: const TextStyle( + color: Color(0xff222F38), + fontSize: 16, + overflow: TextOverflow.ellipsis, + ), + ), + ), + const Padding( + padding: EdgeInsets.only(left: 10), + child: Icon( + Icons.keyboard_arrow_right_rounded, + color: Color(0xffBCC0CE), + size: 20, + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/page/splash/splash_binding.dart b/lib/page/splash/splash_binding.dart new file mode 100644 index 0000000..a9f0016 --- /dev/null +++ b/lib/page/splash/splash_binding.dart @@ -0,0 +1,9 @@ +import 'package:get/get.dart'; +import 'package:trans_lark/page/splash/splash_controller.dart'; + +class SplashBinding extends Bindings { + @override + void dependencies() { + Get.lazyPut(() => SplashController()); + } +} diff --git a/lib/page/splash/splash_controller.dart b/lib/page/splash/splash_controller.dart new file mode 100644 index 0000000..1fd3a03 --- /dev/null +++ b/lib/page/splash/splash_controller.dart @@ -0,0 +1,46 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:trans_lark/core/router/router.dart'; + +class SplashController extends GetxController with GetTickerProviderStateMixin { + var processValue = 0.0.obs; + late AnimationController _controller; + late Animation _animation; + + @override + void onInit() { + super.onInit(); + _controller = AnimationController( + duration: const Duration(seconds: 10), + vsync: this, + ); + + // 创建 Tween 并绑定到 AnimationController + _animation = Tween(begin: 0, end: 1).animate(_controller) + ..addListener(() { + processValue.value = _animation.value; + if (processValue.value >= 1) { + _openInitial(); + } + }); + + // 启动动画 + _controller.forward(); + } + + @override + void onReady() { + super.onReady(); + } + + @override + void onClose() { + _controller.dispose(); + super.onClose(); + } + + /// 打开首个页面 + void _openInitial() { + Get.offNamed(GetRouter.home); + } +} diff --git a/lib/page/splash/splash_view.dart b/lib/page/splash/splash_view.dart new file mode 100644 index 0000000..70eb14f --- /dev/null +++ b/lib/page/splash/splash_view.dart @@ -0,0 +1,62 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:trans_lark/page/splash/splash_controller.dart'; + +class SplashView extends StatelessWidget { + SplashView({super.key}); + + final controller = Get.find(); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Stack( + children: [ + // _buildImageBg(), + _buildProgress(), + ], + ), + ); + } + + // Widget _buildImageBg() { + // return Image.asset( + // Assets.sideALaunchImage, + // width: , + // height: , + // fit: BoxFit.cover, + // ); + // } + + Widget _buildProgress() { + return Container( + alignment: Alignment.center, + margin: const EdgeInsets.only(bottom: 60), + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + SizedBox( + width: 0.5, + child: Obx(() { + return LinearProgressIndicator( + value: controller.processValue.value, + backgroundColor: Colors.white, + valueColor: const AlwaysStoppedAnimation(Color.fromARGB(255, 185, 239, 200)), + borderRadius: BorderRadius.circular(8), + ); + }), + ), + const SizedBox(height: 14), + const Text( + 'Resource Loading...', + style: TextStyle( + color: Colors.white, + fontSize: 12, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ); + } +} diff --git a/lib/page/translator/translator_logic.dart b/lib/page/translator/translator_logic.dart index d6bd8cf..e66fd6b 100755 --- a/lib/page/translator/translator_logic.dart +++ b/lib/page/translator/translator_logic.dart @@ -1,8 +1,18 @@ -import 'package:flutter/foundation.dart'; +import 'dart:io'; + +import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import 'package:translator_lux/dataBase/table/translator_history_table.dart'; -import 'package:translator_lux/entity/translator_history_entity.dart'; -import 'package:translator_lux/page/translator_result/translator_result_view.dart'; +import 'package:permission_handler/permission_handler.dart'; +import 'package:speech_to_text/speech_recognition_result.dart'; +import 'package:trans_lark/core/router/router.dart'; +import 'package:trans_lark/entity/history_model.dart'; +import 'package:trans_lark/global/speech_to_text_manager.dart'; +import 'package:trans_lark/global/translate_language.dart'; +import 'package:trans_lark/storage/history_data.dart'; +import 'package:trans_lark/util/obj_util.dart'; +import 'package:trans_lark/util/permission_util.dart'; +import 'package:trans_lark/widget/base_easyloading.dart'; +import 'package:trans_lark/widget/speak_dialog.dart'; import 'translator_state.dart'; @@ -13,36 +23,118 @@ class TranslatorLogic extends GetxController { final state = TranslatorState(); @override - onInit() { + void onInit() { super.onInit(); initData(); - debugPrint("进来了"); } @override - dispose() { - super.dispose(); - state.historyShowData.clear(); + void onClose() { + state.textController.dispose(); + super.onClose(); } initData() async { - List queryShowData = - await TranslatorHistoryTable.queryShowData(); - state.historyShowData.addAll(queryShowData); - if (state.historyShowData.isNotEmpty) { - state.isThereData.value = true; + state.historyList.value = HistoryData().getList().reversed.toList(); + } + + void onTapSuffix() { + if (state.isValue.value) { + state.textController.text = ""; + state.isValue.value = false; + } else { + _onTapSpeak(); } } - toResult(String value) { + Future _onTapSpeak() async { + bool micResult = await PermissionUtil.checkPermission([Permission.microphone]); + if (!micResult) return; + if (Platform.isIOS) { + bool speechResult = await PermissionUtil.checkPermission([Permission.speech]); + if (!speechResult) return; + } + await Get.dialog( + barrierDismissible: true, + useSafeArea: false, + SpeakDialog( + isListening: SpeechToTextManager().isListening, + onTap: () async { + if (!SpeechToTextManager().hasSpeech) { + await SpeechToTextManager().initSpeech(); + } + if (SpeechToTextManager().hasSpeech) { + if (SpeechToTextManager().isListening.value) { + _openTranslatorResultPage(); + } else { + SpeechToTextManager().startListening(TranslateLanguage().fromLanguageEntity.value.languageCode, _onSpeechResult); + } + } else { + Get.back(); + BaseEasyLoading.toast('Speech not available'); + } + }, + ), + ); + if (Get.isDialogOpen != null && !Get.isDialogOpen!) { + SpeechToTextManager().stopListening(); + } + } + + void _onSpeechResult(SpeechRecognitionResult result) { + debugPrint('识别结果:${result.recognizedWords}'); + state.lastWords = result.recognizedWords; + } + + void _openTranslatorResultPage() { + if (ObjUtil.isEmpty(state.lastWords)) { + BaseEasyLoading.toast('No text recognized'); + return; + } + Get.back(); Get.toNamed( - TranslatorResultPage.routName, - arguments: {"translateString": value}, + GetRouter.translateResult, + arguments: {"sourceText": state.lastWords}, ); } - cleanAllHistory() { - TranslatorHistoryTable.cleanAllShowData(); - state.isThereData.value = false; + void onTapHistoryItem(HistoryEntity entity) { + Get.toNamed( + GetRouter.translateResult, + arguments: { + 'sourceText': entity.sourceText, + 'targetText': entity.targetText, + 'isHistory': true, + 'fromLanguage': entity.sourceLanguageName, + 'toLanguage': entity.targetLanguageName, + }, + ); + } + + void deleteItem(int index) { + // 计算原始列表中的对应索引 + int indexToRemoveFromDb = HistoryData().getList().length - 1 - index; + state.historyList.removeAt(indexToRemoveFromDb); + HistoryData().delete(indexToRemoveFromDb); + } + + void onChanged(String value) { + state.isValue.value = value.trim().isNotEmpty; + } + + void onSubmitted(String value) { + Get.toNamed( + GetRouter.translateResult, + arguments: {"sourceText": value}, + ); + } + + void insertHistory(HistoryEntity entity) { + state.historyList.insert(0, entity); + } + + void cleanAllHistory() { + state.historyList.clear(); + HistoryData().clear(); } } diff --git a/lib/page/translator/translator_state.dart b/lib/page/translator/translator_state.dart index 863bd57..b9704c3 100755 --- a/lib/page/translator/translator_state.dart +++ b/lib/page/translator/translator_state.dart @@ -1,14 +1,14 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import 'package:translator_lux/entity/translator_history_entity.dart'; +import 'package:trans_lark/entity/history_model.dart'; +import 'package:trans_lark/entity/translator_history_entity.dart'; /// @description: /// @author /// @date: 2024-06-27 13:55:27 class TranslatorState { - TextEditingController textController = TextEditingController(); - RxBool isValue = false.obs; - RxList historyShowData = - [].obs; - RxBool isThereData = false.obs; + var textController = TextEditingController(); + var isValue = false.obs; + var historyList = [].obs; + var lastWords = ''; } diff --git a/lib/page/translator/translator_view.dart b/lib/page/translator/translator_view.dart index 04b1c29..47147ed 100755 --- a/lib/page/translator/translator_view.dart +++ b/lib/page/translator/translator_view.dart @@ -1,9 +1,11 @@ +import 'package:custom_pop_up_menu/custom_pop_up_menu.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:get/get.dart'; -import 'package:translator_lux/util/image_asset.dart'; -import 'package:translator_lux/util/svg_asset.dart'; -import 'package:translator_lux/widget/tranlator_from_to.dart'; +import 'package:trans_lark/generated/assets.dart'; +import 'package:trans_lark/util/obj_util.dart'; +import 'package:trans_lark/widget/base_appbar.dart'; +import 'package:trans_lark/widget/language_bar.dart'; import 'translator_logic.dart'; import 'translator_state.dart'; @@ -11,14 +13,14 @@ import 'translator_state.dart'; /// @description: 翻译页面 /// @lifu /// @date: 2024-06-27 13:55:27 -class TranlatorPage extends StatefulWidget { - const TranlatorPage({super.key}); - static String routName = "/translate"; +class TranslatorPage extends StatefulWidget { + const TranslatorPage({super.key}); + @override - State createState() => _TranlatorPageState(); + State createState() => _TranslatorPageState(); } -class _TranlatorPageState extends State { +class _TranslatorPageState extends State { final TranslatorLogic logic = Get.put(TranslatorLogic()); final TranslatorState state = Get.find().state; @@ -31,187 +33,202 @@ class _TranlatorPageState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - surfaceTintColor: Colors.transparent, - automaticallyImplyLeading: false, - title: Row( - children: [ - IconButton( - onPressed: () { - Get.back(); - }, - icon: const Icon( - Icons.arrow_back_ios, - ), - ), - const Expanded(child: TranslatorFromTo()), - ], - ), + backgroundColor: Colors.white, + appBar: const BaseAppBar( + titleWidget: LanguageBar(), ), - body: SafeArea( - child: Padding( + body: Padding( padding: const EdgeInsets.symmetric(horizontal: 20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ + const SizedBox(height: 20), Container( - constraints: const BoxConstraints( - maxHeight: 200.0, - minHeight: 76.0, - ), decoration: BoxDecoration( - border: - Border.all(width: 1.5, color: const Color(0xff0087FF)), - color: const Color(0xff45A7FE), - borderRadius: const BorderRadius.all(Radius.circular(10))), - padding: const EdgeInsets.only( - left: 16.0, right: 16.0, top: 10.0, bottom: 4.0), - child: TextField( - textInputAction: TextInputAction.done, - controller: state.textController, - style: const TextStyle( - color: Colors.black, - fontSize: 20, - fontWeight: FontWeight.w400, - ), - maxLines: null, - autofocus: true, - decoration: InputDecoration( - border: InputBorder.none, - hintText: "Type to Translate", - hintStyle: const TextStyle( - color: Colors.white, - fontSize: 20, - fontWeight: FontWeight.w400, - ), - suffixIcon: Obx( - () => GestureDetector( - onTap: () { - if (state.isValue.value) { - state.textController.text = ""; - state.isValue.value = !state.isValue.value; - } else {} - }, - child: Container( - height: 36, - width: 36, - padding: const EdgeInsets.all(8), - decoration: BoxDecoration( - color: const Color(0xff8BCFFE), - borderRadius: BorderRadius.circular(25), - ), - child: state.isValue.value - ? const Icon(Icons.close) - : SvgPicture.asset(SvgAsset.homeVoice), + border: Border.all(width: 1.5, color: const Color(0xff0087FF)), + color: const Color(0xff45A7FE), + borderRadius: const BorderRadius.all(Radius.circular(16)), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: TextField( + textInputAction: TextInputAction.done, + textAlignVertical: TextAlignVertical.center, + textAlign: TextAlign.left, + controller: state.textController, + maxLines: null, + minLines: 1, + autofocus: true, + cursorColor: Colors.white, + style: const TextStyle( + color: Colors.black, + fontSize: 20, + height: 1.2, ), + decoration: const InputDecoration( + counterText: '', + isCollapsed: true, + contentPadding: EdgeInsets.fromLTRB(20, 26, 4, 26), + border: InputBorder.none, + hintText: "Type to Translate", + hintStyle: TextStyle( + color: Colors.white, + fontSize: 20, + ), + ), + onChanged: (value) => logic.onChanged(value), + onSubmitted: (value) => logic.onSubmitted(value), ), ), - ), - onChanged: (value) { - if (value != "" && value.isNotEmpty) { - state.isValue.value = true; - } else { - state.isValue.value = false; - } - }, - onSubmitted: (value) { - if (value != "" && value.isNotEmpty) { - logic.toResult(value); - } - }, - ), - ), - Obx(() => state.isThereData.value - ? Expanded( - child: Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Text( - "History", - style: TextStyle( - fontSize: 16, - color: Color( - 0xff979797, - ), - fontWeight: FontWeight.w500, + Obx(() { + return Padding( + padding: EdgeInsets.only(top: state.isValue.value ? 26 : 20, right: 20), + child: GestureDetector( + onTap: logic.onTapSuffix, + child: ClipOval( + child: Visibility( + visible: state.isValue.value, + replacement: Container( + width: 36, + height: 36, + color: const Color(0xff8BCFFE), + child: FittedBox( + fit: BoxFit.none, + child: SvgPicture.asset(Assets.svgHomeVoice), ), ), - IconButton( - onPressed: () { - logic.cleanAllHistory(); - }, - icon: Image.asset( - ImageAsset.deleteIcon, - width: 24, - height: 24, - ), - ) - ], - ), - const SizedBox( - height: 10, - ), - Expanded( - child: ListView.builder( - padding: EdgeInsets.zero, - itemBuilder: (context, index) { - return ListTile( - title: RichText( - overflow: TextOverflow.ellipsis, - text: TextSpan( - children: [ - TextSpan( - text: state - .historyShowData[index].sourceText!, - style: const TextStyle( - fontWeight: FontWeight.w500, - fontSize: 16, - color: Colors.black, - ), - ), - const WidgetSpan( - child: SizedBox( - width: 6, - )), - TextSpan( - text: state - .historyShowData[index].targetText!, - style: const TextStyle( - color: Color(0xff979797), - fontSize: 13, - fontWeight: FontWeight.w400, - ), - ), - ], - ), - ), - onTap: () { - state.textController.text = - state.historyShowData[index].sourceText!; - state.isValue.value = true; - }, - ); - }, - itemCount: state.historyShowData.length, + child: Container( + width: 24, + height: 24, + color: Colors.white, + child: const Icon(Icons.close, size: 12), + ), ), ), - ], - ), - ) - : Expanded( - child: Center( - child: Image.asset( - ImageAsset.rabbitSayGood, - width: 100, - height: 100, ), + ); + }), + ], + ), + ), + Obx(() => state.historyList.isNotEmpty + ? Expanded( + child: Column( + children: [ + const SizedBox(height: 30), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text( + "History", + style: TextStyle( + fontSize: 16, + color: Color(0xff979797), + fontWeight: FontWeight.w500, + ), + ), + IconButton( + onPressed: logic.cleanAllHistory, + icon: Image.asset( + Assets.imagesDeleteIcon, + width: 24, + height: 24, + ), + ), + ], + ), + Expanded( + child: ListView.builder( + itemCount: state.historyList.length, + itemBuilder: (context, index) { + final historyEntity = state.historyList[index]; + return CustomPopupMenu( + arrowColor: Colors.black, + barrierColor: Colors.transparent, + position: PreferredPosition.top, + verticalMargin: 0, + pressType: PressType.longPress, + menuBuilder: () { + return GestureDetector( + onTap: () => logic.deleteItem(index), + child: Container( + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: Colors.black, + borderRadius: BorderRadius.circular(8), + ), + child: Image.asset( + Assets.imagesDeleteIcon, + width: 24, + height: 24, + ), + ), + ); + }, + child: InkWell( + onTap: () => logic.onTapHistoryItem(historyEntity), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.symmetric(vertical: 10), + child: RichText( + maxLines: 1, + overflow: TextOverflow.ellipsis, + text: TextSpan( + children: [ + TextSpan( + text: ObjUtil.getStr( + historyEntity.sourceText), + style: const TextStyle( + fontWeight: FontWeight.w500, + fontSize: 16, + color: Color(0xff212121), + ), + ), + const WidgetSpan( + child: SizedBox( + width: 6, + ), + ), + TextSpan( + text: ObjUtil.getStr( + historyEntity.targetText), + style: const TextStyle( + color: Color(0xff979797), + fontSize: 13, + fontWeight: FontWeight.w400, + ), + ), + ], + ), + ), + ), + ], + ), + ), + ); + }, ), - )), + ), + ], + ), + ) + : Expanded( + child: Center( + child: Image.asset( + Assets.imagesRabbitSayGood, + width: 90, + height: 90, + ), + ), + ), + ), ], ), - )), + ), ); } } diff --git a/lib/page/translator_history/translator_history_logic.dart b/lib/page/translator_history/translator_history_logic.dart new file mode 100755 index 0000000..1ee8062 --- /dev/null +++ b/lib/page/translator_history/translator_history_logic.dart @@ -0,0 +1,54 @@ +import 'package:get/get.dart'; +import 'package:trans_lark/core/router/router.dart'; +import 'package:trans_lark/entity/history_model.dart'; +import 'package:trans_lark/global/tts_manager.dart'; +import 'package:trans_lark/storage/history_data.dart'; +import 'package:trans_lark/util/obj_util.dart'; + +import 'translator_history_state.dart'; + +/// @description: +/// @author +/// @date: 2024-06-28 16:26:22 +class TranslatorHistoryLogic extends GetxController { + final state = TranslatorHistoryState(); + + @override + void onInit() { + super.onInit(); + state.historyList.value = HistoryData().getGroupByList(); + } + + void onTapItem(HistoryEntity entity) { + Get.toNamed( + GetRouter.translateResult, + arguments: { + 'sourceText': entity.sourceText, + 'targetText': entity.targetText, + 'isHistory': true, + 'fromLanguage': entity.sourceLanguageName, + 'toLanguage': entity.targetLanguageName, + }, + ); + } + + void translatorTtsPlay(HistoryEntity entity) { + if (ObjUtil.isNotEmpty(entity.targetText)) { + TtsManager().translatorTtsPlay(entity.targetText!, entity.targetLanguageCode!); + } + } + + void deleteItem(String key, HistoryEntity entity, int index) { + final list = HistoryData().getList(); + state.historyList.forEach((key, value) { + value.removeWhere((e) => e.translationTime == entity.translationTime); + }); + if (state.historyList[key] != null && state.historyList[key]!.isEmpty) { + state.historyList.remove(key); + } + final deleteEntity = list.firstWhereOrNull((item) => item.translationTime == entity.translationTime); + if (deleteEntity != null) { + HistoryData().delete(list.indexOf(deleteEntity)); + } + } +} diff --git a/lib/page/translator_history/translator_history_state.dart b/lib/page/translator_history/translator_history_state.dart new file mode 100755 index 0000000..ea45444 --- /dev/null +++ b/lib/page/translator_history/translator_history_state.dart @@ -0,0 +1,11 @@ +import 'package:get/get.dart'; +import 'package:get/get_rx/get_rx.dart'; +import 'package:get/get_rx/src/rx_types/rx_types.dart'; +import 'package:trans_lark/entity/history_model.dart'; + +/// @description: +/// @author +/// @date: 2024-06-28 16:26:22 +class TranslatorHistoryState { + RxMap> historyList = >{}.obs; +} diff --git a/lib/page/translator_history/translator_history_view.dart b/lib/page/translator_history/translator_history_view.dart new file mode 100755 index 0000000..9212079 --- /dev/null +++ b/lib/page/translator_history/translator_history_view.dart @@ -0,0 +1,207 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:get/get.dart'; +import 'package:trans_lark/generated/assets.dart'; +import 'package:trans_lark/util/obj_util.dart'; +import 'package:trans_lark/widget/base_appbar.dart'; +import 'package:trans_lark/widget/base_scrollbar.dart'; + +import 'translator_history_logic.dart'; +import 'translator_history_state.dart'; + +/// @description: +/// @author +/// @date: 2024-06-28 16:26:22 +class TranslatorHistoryPage extends StatefulWidget { + static String routName = "/translateHistory"; + + const TranslatorHistoryPage({super.key}); + + @override + State createState() => _TranslatorHistoryPageState(); +} + +class _TranslatorHistoryPageState extends State { + final TranslatorHistoryLogic logic = Get.put(TranslatorHistoryLogic()); + + final TranslatorHistoryState state = Get.find().state; + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Scaffold( + backgroundColor: Colors.white, + appBar: const BaseAppBar( + title: 'History', + backColor: Colors.black, + ), + body: Obx(() { + return state.historyList.isNotEmpty ? BaseScrollbar( + child: ListView.builder( + padding: const EdgeInsets.symmetric(vertical: 20), + itemCount: state.historyList.length, + itemBuilder: (context, index) { + final map = state.historyList.entries.elementAt(index); + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Text( + map.key, + style: const TextStyle( + color: Color(0xff949494), + fontSize: 12, + ), + ), + ), + ListView.separated( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: map.value.length, + itemBuilder: (context, index) { + final item = map.value[index]; + return InkWell( + onTap: () => logic.onTapItem(item), + child: Dismissible( + key: Key(item.translationTime.toString()), + dismissThresholds: const { + DismissDirection.startToEnd: 0.5, + DismissDirection.endToStart: 0.5, + }, + onDismissed: (_) => logic.deleteItem(map.key, item, index), + background: Container( + color: const Color(0xffFF98BC), + child: Stack( + children: [ + Positioned( + left: 16, + top: 0, + bottom: 0, + child: Image.asset( + Assets.imagesDeleteIcon, + width: 24, + height: 24, + color: Colors.white, + ), + ), + ], + ), + ), + secondaryBackground: Container( + color: const Color(0xffFF98BC), + child: Stack( + children: [ + Positioned( + right: 16, + top: 0, + bottom: 0, + child: Image.asset( + Assets.imagesDeleteIcon, + width: 24, + height: 24, + color: Colors.white, + ), + ), + ], + ), + ), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 20), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + GestureDetector( + onTap: () => logic.translatorTtsPlay(item), + child: ClipOval( + child: Container( + width: 32, + height: 32, + color: const Color(0xff4ECA8C), + child: FittedBox( + fit: BoxFit.none, + child: SvgPicture.asset( + Assets.svgSpeakerWhite, + width: 20, + height: 20, + ), + ), + ), + ), + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + ObjUtil.getStr(item.sourceText), + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + color: Color(0xff212121), + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(height: 12), + Text( + ObjUtil.getStr(item.targetText), + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + color: Color(0xff979797), + fontSize: 13, + ), + ), + ], + ), + ), + ], + ), + ), + ), + ); + }, + separatorBuilder: (context, index) { + return Container( + height: 0.5, + margin: const EdgeInsets.symmetric(horizontal: 20), + color: const Color(0xffEDEDED), + ); + }, + ), + ], + ); + }, + ), + ) : Center( + child: Image.asset( + Assets.imagesRabbitSayGood, + width: 90, + height: 90, + fit: BoxFit.cover, + ), + ); + }), + ), + Obx(() { + return Visibility( + visible: state.historyList.isNotEmpty, + child: Positioned( + top: 59, + right: 23, + child: Image.asset( + Assets.imagesRabbitSayGood, + width: 90, + height: 90, + fit: BoxFit.cover, + ), + ), + ); + }), + ], + ); + } +} \ No newline at end of file diff --git a/lib/page/translator_result/translator_result_logic.dart b/lib/page/translator_result/translator_result_logic.dart index 5bc9a16..519df85 100755 --- a/lib/page/translator_result/translator_result_logic.dart +++ b/lib/page/translator_result/translator_result_logic.dart @@ -1,10 +1,15 @@ -import 'package:flutter/material.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/services.dart'; import 'package:get/get.dart'; import 'package:translator/translator.dart'; -import 'package:translator_lux/dataBase/table/translator_history_table.dart'; -import 'package:translator_lux/entity/translator_history_entity.dart'; -import 'package:translator_lux/global/global_state.dart'; -import 'package:uuid/uuid.dart'; +import 'package:trans_lark/entity/history_model.dart'; +import 'package:trans_lark/global/translate_language.dart'; +import 'package:trans_lark/global/tts_manager.dart'; +import 'package:trans_lark/page/translator/translator_logic.dart'; +import 'package:trans_lark/storage/history_data.dart'; +import 'package:trans_lark/util/obj_util.dart'; +import 'package:trans_lark/widget/base_easyloading.dart'; +import 'package:trans_lark/widget/translate_text_full_screen_page.dart'; import 'translator_result_state.dart'; @@ -17,49 +22,74 @@ class TranslatorResultPageLogic extends GetxController { @override void onInit() { super.onInit(); - translation(); + state.sourceText.value = Get.arguments['sourceText'] ?? ''; + state.targetText.value = Get.arguments['targetText'] ?? ''; + state.isHistory = Get.arguments['isHistory'] ?? false; + state.fromLanguage = Get.arguments['fromLanguage']; + state.toLanguage = Get.arguments['toLanguage']; } - translation() async { - if (state.sourceText.value == "" && state.targetText.value == "") { - var arguments = Get.arguments; - var translator = GoogleTranslator(); - debugPrint( - "arguments[\"translateString\"]参数有${arguments["translateString"]}"); - debugPrint( - "GlobalState.fromLanguageCode.value参数有${GlobalState.fromLanguageCode.value}"); - debugPrint( - "GlobalState.toLanguageCode.value参数有${GlobalState.toLanguageCode.value}"); - Translation translate = await translator.translate( - arguments["translateString"], - from: GlobalState.fromLanguageCode.value, - to: GlobalState.toLanguageCode.value, - ); - state.sourceText.value = translate.source; - state.targetText.value = translate.text; - debugPrint(translate.toString()); - addHistory( - sourceText: translate.source, - targetText: translate.text, - ); - update(); + @override + void onReady() { + super.onReady(); + if (state.isHistory) { + state.isTranslationCompleted.value = true; + } else { + translation(); } } - addHistory({required String sourceText, required String targetText}) { - var historyEntity = TranslatorHistoryEntity().copyWith( - tid: const Uuid().v1(), - sourceText: sourceText, - sourcecountryCode: GlobalState.fromCountryCode.value, - sourcelanguageName: GlobalState.fromCountryName.value, - sourcelanguageCode: GlobalState.fromLanguageCode.value, - targetText: targetText, - targetcountryCode: GlobalState.toCountryCode.value, - targetlanguageName: GlobalState.toCountryName.value, - targetlanguageCode: GlobalState.toLanguageCode.value, - translatorTime: DateTime.now().toString(), - isShow: "true", + Future translation() async { + if (ObjUtil.isNotEmpty(state.sourceText.value)) { + try { + Translation translate = await state.translator.translate( + state.sourceText.value, + from: TranslateLanguage().fromLanguageEntity.value.languageCode, + to: TranslateLanguage().toLanguageEntity.value.languageCode, + ); + state.targetText.value = translate.text; + addHistory(); + } catch (e) { + BaseEasyLoading.toast('Translation failed'); + debugPrint('Translation failed,${e.toString()}'); + } + state.isTranslationCompleted.value = true; + } + } + + void addHistory() { + final entity = HistoryEntity( + sourceText: state.sourceText.value, + targetText: state.targetText.value, + sourceLanguageName: TranslateLanguage().fromLanguageEntity.value.languageName, + sourceLanguageCode: TranslateLanguage().fromLanguageEntity.value.languageCode, + targetLanguageName: TranslateLanguage().toLanguageEntity.value.languageName, + targetLanguageCode: TranslateLanguage().toLanguageEntity.value.languageCode, ); - TranslatorHistoryTable.insertData(historyEntity); + HistoryData().addData(entity); + if (Get.isRegistered()) { + final logic = Get.find(); + logic.insertHistory(entity); + } + } + + void translatorTtsPlay(String text) { + if (ObjUtil.isNotEmpty(text)) { + TtsManager().translatorTtsPlay(text, TranslateLanguage().toLanguageEntity.value.languageCode); + } + } + + void copy() { + if (ObjUtil.isNotEmpty(state.targetText.value)) { + Clipboard.setData(ClipboardData(text: state.targetText.value)); + BaseEasyLoading.toast('Copied to clipboard'); + } + } + + void openFullScreenPage() { + if (ObjUtil.isNotEmpty(state.targetText.value)) { + Get.to(() => const TranslateTextFullScreenPage(), + arguments: {"data": state.targetText.value}); + } } } diff --git a/lib/page/translator_result/translator_result_state.dart b/lib/page/translator_result/translator_result_state.dart index c57a771..1cbc1d2 100755 --- a/lib/page/translator_result/translator_result_state.dart +++ b/lib/page/translator_result/translator_result_state.dart @@ -1,9 +1,16 @@ import 'package:get/get_rx/src/rx_types/rx_types.dart'; +import 'package:translator/translator.dart'; +import 'package:trans_lark/entity/history_model.dart'; /// @description: /// @author /// @date: 2024-06-27 16:53:29 class TranslatorResultPageState { + var translator = GoogleTranslator(); RxString sourceText = "".obs; RxString targetText = "".obs; + var isTranslationCompleted = false.obs; + bool isHistory = false; + String? fromLanguage; + String? toLanguage; } diff --git a/lib/page/translator_result/translator_result_view.dart b/lib/page/translator_result/translator_result_view.dart index 7481275..3ba9e62 100755 --- a/lib/page/translator_result/translator_result_view.dart +++ b/lib/page/translator_result/translator_result_view.dart @@ -1,21 +1,18 @@ import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_svg/svg.dart'; import 'package:get/get.dart'; -import 'package:translator_lux/page/translator_result/translator_result_logic.dart'; -import 'package:translator_lux/page/translator_result/translator_result_state.dart'; -import 'package:translator_lux/util/image_asset.dart'; -import 'package:translator_lux/util/svg_asset.dart'; -import 'package:translator_lux/util/tts_util.dart'; -import 'package:translator_lux/widget/tranlator_from_to.dart'; -import 'package:translator_lux/widget/translate_text_full_screen_page.dart'; +import 'package:trans_lark/generated/assets.dart'; +import 'package:trans_lark/global/translate_language.dart'; +import 'package:trans_lark/page/translator_result/translator_result_logic.dart'; +import 'package:trans_lark/page/translator_result/translator_result_state.dart'; +import 'package:trans_lark/util/obj_util.dart'; +import 'package:trans_lark/widget/base_appbar.dart'; +import 'package:trans_lark/widget/language_result_bar.dart'; /// @description: /// @author /// @date: 2024-06-27 16:53:29 class TranslatorResultPage extends StatefulWidget { - static String routName = "/translateResult"; - const TranslatorResultPage({super.key}); @override @@ -25,8 +22,7 @@ class TranslatorResultPage extends StatefulWidget { class _TranslatorResultPageState extends State { final TranslatorResultPageLogic logic = Get.put(TranslatorResultPageLogic()); - final TranslatorResultPageState state = - Get.find().state; + final TranslatorResultPageState state = Get.find().state; @override Widget build(BuildContext context) { @@ -34,220 +30,215 @@ class _TranslatorResultPageState extends State { body: Container( height: double.infinity, width: double.infinity, - decoration: BoxDecoration( + decoration: const BoxDecoration( image: DecorationImage( - fit: BoxFit.cover, - image: AssetImage(ImageAsset.translatorResultBackground), + image: AssetImage(Assets.imagesTranslatorResultBackground), + fit: BoxFit.fill, ), ), - child: SafeArea( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 10), - child: Column( + child: Column( + children: [ + BaseAppBar( + backgroundColor: Colors.transparent, + titleWidget: LanguageResultBar( + fromLanguage: state.isHistory + ? (state.fromLanguage != null ? state.fromLanguage! : '') + : TranslateLanguage().fromLanguageEntity.value.languageName, + toLanguage: state.isHistory + ? (state.toLanguage != null ? state.toLanguage! : '') + : TranslateLanguage().toLanguageEntity.value.languageName, + ), + ), + Expanded( + child: ListView( + padding: const EdgeInsets.symmetric(vertical: 20), children: [ - Row( - children: [ - IconButton( - onPressed: () { - Get.back(); - }, - icon: const Icon( - Icons.arrow_back_ios, - ), - ), - const Expanded(child: TranslatorFromTo()), - ], - ), - Expanded( - child: ListView( - children: [ - Obx( - () => Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), - child: Column( + Obx( + () => Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Padding( - padding: const EdgeInsets.only(top: 20), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - child: Text( - state.sourceText.value, - style: const TextStyle( - fontSize: 20, - fontWeight: FontWeight.w600, - ), - ), - ), - GestureDetector( - onTap: () { - Get.back(); - }, - child: Container( - width: 30, - height: 30, - decoration: BoxDecoration( - color: const Color(0xffF4F4F6), - borderRadius: - BorderRadius.circular(15)), - child: const Icon( - Icons.close, - size: 20, - ), - ), - ) - ], + Expanded( + child: Text( + state.sourceText.value, + style: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.w600, + color: Color(0xff152A3D), + ), ), ), - const SizedBox( - height: 30, - ), GestureDetector( - onTap: () { - if (state.sourceText.value != "") { - TtsUtil.translatorTtsPlay( - state.sourceText.value); - } - }, - child: Container( - width: 32, - height: 32, - padding: const EdgeInsets.all(5), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(16), - color: - const Color.fromARGB(104, 78, 202, 140), - ), - child: SvgPicture.asset( - SvgAsset.speaker, + onTap: Get.back, + child: ClipOval( + child: Container( + width: 30, + height: 30, + color: const Color(0xffF4F4F6), + child: const Icon( + Icons.close, + size: 20, + ), ), ), - ), - const SizedBox( - height: 30, - ), - const Divider( - height: 1, ) ], ), - ), + const SizedBox( + height: 30, + ), + GestureDetector( + onTap: () => logic.translatorTtsPlay(state.sourceText.value), + child: ClipOval( + child: Container( + width: 32, + height: 32, + color: const Color(0x334ECA8C), + child: FittedBox( + fit: BoxFit.none, + child: SvgPicture.asset( + Assets.svgSpeaker, + width: 20, + height: 20, + ), + ), + ), + ), + ), + const SizedBox( + height: 30, + ), + const Divider( + height: 1, + ) + ], ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Padding( - padding: EdgeInsets.symmetric(vertical: 20), - child: Text( - "Spanish", - style: TextStyle( - color: Color(0xff949494), - fontSize: 16, - fontWeight: FontWeight.w500, - ), - ), - ), - Obx( - () => Text( - state.targetText.value, - style: const TextStyle( - fontSize: 20, - fontWeight: FontWeight.w600, - ), - ), - ), - const SizedBox( - height: 20, - ), - Row( + ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.symmetric(vertical: 20), + child: Obx(() { + return Row( children: [ - Padding( - padding: const EdgeInsets.all(8.0), - child: GestureDetector( - onTap: () { - if (state.targetText.value != "") { - TtsUtil.translatorTtsPlay( - state.targetText.value); - } - }, - child: Container( - width: 32, - height: 32, - padding: const EdgeInsets.all(5), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(16), - color: const Color.fromARGB( - 104, 78, 202, 140), - ), - child: SvgPicture.asset( - SvgAsset.speaker, - ), + Flexible( + child: Text( + TranslateLanguage().toLanguageEntity.value.languageName, + style: const TextStyle( + color: Color(0xff949494), + fontSize: 16, + fontWeight: FontWeight.w500, ), ), ), - Padding( - padding: const EdgeInsets.all(8.0), - child: GestureDetector( - onTap: () { - if (state.targetText.value != "") { - Clipboard.setData( - ClipboardData( - text: state.targetText.value), - ); - } - }, - child: Container( - width: 32, - height: 32, - padding: const EdgeInsets.all(5), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(16), - color: const Color(0xffF4F4F6), - ), - child: Image.asset(ImageAsset.copy), - ), - ), - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: GestureDetector( - onTap: () { - Get.to( - () => - const TranslateTextFullScreenPage(), - arguments: { - "data": state.targetText.value - }); - }, - child: Container( - width: 32, - height: 32, - padding: const EdgeInsets.all(5), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(16), - color: const Color(0xffF4F4F6), - ), - child: Image.asset(ImageAsset.fullScreen), + const SizedBox(width: 20), + Visibility( + visible: !state.isTranslationCompleted.value, + child: const SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator( + strokeWidth: 2, + color: Color(0xff4ECA8C), ), ), ), ], - ) - ], + ); + }), ), - ), - const SizedBox( - height: 50, - ), - ], - )) + Obx( + () => Text( + state.targetText.value, + style: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.w600, + color: Color(0xff152A3D), + ), + ), + ), + const SizedBox( + height: 20, + ), + Obx(() { + return Visibility( + visible: ObjUtil.isNotEmptyStr(state.targetText.value), + child: Row( + children: [ + GestureDetector( + onTap: () => logic.translatorTtsPlay(state.targetText.value), + child: ClipOval( + child: Container( + width: 32, + height: 32, + color: const Color(0x334ECA8C), + child: FittedBox( + fit: BoxFit.none, + child: SvgPicture.asset( + Assets.svgSpeaker, + width: 20, + height: 20, + ), + ), + ), + ), + ), + const SizedBox(width: 16), + GestureDetector( + onTap: logic.copy, + child: ClipOval( + child: Container( + width: 32, + height: 32, + color: const Color(0xffF4F4F6), + child: FittedBox( + fit: BoxFit.none, + child: Image.asset( + Assets.imagesCopy, + width: 20, + height: 20, + ), + ), + ), + ), + ), + const SizedBox(width: 16), + GestureDetector( + onTap: logic.openFullScreenPage, + child: ClipOval( + child: Container( + width: 32, + height: 32, + color: const Color(0xffF4F4F6), + child: FittedBox( + fit: BoxFit.none, + child: Image.asset( + Assets.imagesFullScreen, + width: 20, + height: 20, + ), + ), + ), + ), + ), + ], + ), + ); + }), + ], + ), + ), ], - ), - ), + )), + ], ), ), ); diff --git a/lib/page/web_page/web_page_binding.dart b/lib/page/web_page/web_page_binding.dart new file mode 100644 index 0000000..b6fd67e --- /dev/null +++ b/lib/page/web_page/web_page_binding.dart @@ -0,0 +1,10 @@ +import 'package:get/get.dart'; + +import 'web_page_controller.dart'; + +class WebPageBinding extends Bindings { + @override + void dependencies() { + Get.lazyPut(() => WebPageController()); + } +} diff --git a/lib/page/web_page/web_page_controller.dart b/lib/page/web_page/web_page_controller.dart new file mode 100644 index 0000000..7c158b0 --- /dev/null +++ b/lib/page/web_page/web_page_controller.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:trans_lark/widget/view_state_widget.dart'; +import 'package:webview_flutter/webview_flutter.dart'; + +class WebPageController extends GetxController { + var title = ''; + var url = ''; + late final WebViewController webViewController; + var viewState = ViewState.loading.obs; + + @override + void onInit() { + super.onInit(); + title = Get.arguments['title']; + url = Get.arguments['url'] ?? ''; + webViewController = WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setBackgroundColor(Colors.white) + ..setNavigationDelegate( + NavigationDelegate( + onProgress: (int progress) { + // Update loading bar. + }, + onPageStarted: (String url) {}, + onPageFinished: (String url) { + viewState.value = ViewState.normal; + }, + onWebResourceError: (WebResourceError error) { + }, + onNavigationRequest: (NavigationRequest request) { + return NavigationDecision.navigate; + }, + ), + ) + ..loadRequest(Uri.parse(url)); + } +} diff --git a/lib/page/web_page/web_page_view.dart b/lib/page/web_page/web_page_view.dart new file mode 100644 index 0000000..c3876b7 --- /dev/null +++ b/lib/page/web_page/web_page_view.dart @@ -0,0 +1,28 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:trans_lark/page/web_page/web_page_controller.dart'; +import 'package:trans_lark/widget/base_appbar.dart'; +import 'package:trans_lark/widget/view_state_widget.dart'; +import 'package:webview_flutter/webview_flutter.dart'; + +class WebPageView extends StatelessWidget { + WebPageView({super.key}); + + final controller = Get.find(); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.white, + appBar: BaseAppBar( + title: controller.title, + ), + body: Obx(() { + return ViewStateWidget( + viewState: controller.viewState.value, + child: WebViewWidget(controller: controller.webViewController), + ); + }), + ); + } +} diff --git a/lib/storage/history_data.dart b/lib/storage/history_data.dart new file mode 100644 index 0000000..080a753 --- /dev/null +++ b/lib/storage/history_data.dart @@ -0,0 +1,59 @@ +// Author: fengshengxiong +// Date: 2024/5/8 +// Description: 历史数据操作单例 + +import 'package:collection/collection.dart'; +import 'package:trans_lark/entity/history_model.dart'; +import 'package:trans_lark/storage/hive_storage.dart'; +import 'package:trans_lark/util/date_util.dart'; + +class HistoryData { + /// 私有构造函数 + HistoryData._(); + + /// 静态常量用于保存类的唯一实例 + static final HistoryData _instance = HistoryData._(); + + /// 工厂构造函数返回类的唯一实例 + factory HistoryData() { + return _instance; + } + + /// 声明盒子 + /// 注意, main函数中这个盒子已经打开, 可以进行存储操作 + final _box = getHistoryBox(); + + /// 返回所有数据 + List getList() { + return _box.values.toList(); + } + + /// 返回分组后的所有数据 + Map> getGroupByList() { + // 使用 groupBy 函数进行分组 + var groupedEvents = groupBy( + getList().reversed.toList(), + (HistoryEntity event) => DateUtil.formatDateMs( + event.translationTime ?? DateUtil.getNowTimestamp(), + format: DateFormats.yMoD)); + return groupedEvents; + } + + /// 添加数据 + Future addData(HistoryEntity entity) async { + entity.translationTime = DateUtil.getNowTimestamp(); + return await _box.add(entity); + } + + /// 删除 + Future delete(int index) async { + await _box.deleteAt(index); + await _box.flush(); + } + + /// 清空所有数据 + Future clear() async { + await _box.clear(); + await _box.flush(); + } +} diff --git a/lib/storage/hive_storage.dart b/lib/storage/hive_storage.dart new file mode 100644 index 0000000..7c114fb --- /dev/null +++ b/lib/storage/hive_storage.dart @@ -0,0 +1,21 @@ +// Author: fengshengxiong +// Date: 2024/5/8 +// Description: 持久化储存 + +import 'package:hive_flutter/hive_flutter.dart'; +import 'package:trans_lark/entity/history_model.dart'; + +const historyBox = 'historyBox'; + +Future initHive() async { + // 初始化 + await Hive.initFlutter(); + // 注册类型适配器 + Hive.registerAdapter(HistoryEntityAdapter()); + // 打开盒子 + await Hive.openBox(historyBox); +} + +Box getHistoryBox() { + return Hive.box(historyBox); +} \ No newline at end of file diff --git a/lib/temp/temp_logic.dart b/lib/temp/temp_logic.dart deleted file mode 100755 index 6eae780..0000000 --- a/lib/temp/temp_logic.dart +++ /dev/null @@ -1,10 +0,0 @@ -import 'package:get/get.dart'; - -import 'temp_state.dart'; - -/// @description: -/// @author -/// @date: 2024-06-27 10:17:16 -class TempLogic extends GetxController { - final state = TempState(); -} diff --git a/lib/temp/temp_state.dart b/lib/temp/temp_state.dart deleted file mode 100755 index ae6f7d0..0000000 --- a/lib/temp/temp_state.dart +++ /dev/null @@ -1,8 +0,0 @@ -/// @description: -/// @author -/// @date: 2024-06-27 10:17:16 -class TempState { - TempState() { - ///Initialize variables - } -} diff --git a/lib/temp/temp_view.dart b/lib/temp/temp_view.dart deleted file mode 100755 index 09ae188..0000000 --- a/lib/temp/temp_view.dart +++ /dev/null @@ -1,57 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -import 'package:translator/translator.dart'; -import 'package:translator_lux/global/global_state.dart'; - -import 'temp_logic.dart'; -import 'temp_state.dart'; - -/// @description: -/// @author -/// @date: 2024-06-27 10:17:16 -class TempPage extends StatefulWidget { - const TempPage({super.key}); - - @override - State createState() => _TempPageState(); -} - -class _TempPageState extends State { - final TempLogic logic = Get.put(TempLogic()); - - final TempState state = Get.find().state; - - TextEditingController textController = TextEditingController(); - - @override - void dispose() { - super.dispose(); - textController.dispose(); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - body: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - TextField( - controller: textController, - ), - ElevatedButton( - onPressed: () async { - var input = textController.text; - var translator = GoogleTranslator(); - Translation translate = await translator.translate(input, - from: GlobalState.fromLanguageCode.value, - to: GlobalState.toLanguageCode.value); - debugPrint(translate.text); - }, - child: const Text("translator")) - ], - ), - ), - ); - } -} diff --git a/lib/temp_camera.dart b/lib/temp_camera.dart deleted file mode 100755 index f7b209a..0000000 --- a/lib/temp_camera.dart +++ /dev/null @@ -1,1059 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'dart:async'; -import 'dart:io'; - -import 'package:camera/camera.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/scheduler.dart'; -import 'package:video_player/video_player.dart'; - -/// Camera example home widget. -class CameraExampleHome extends StatefulWidget { - /// Default Constructor - const CameraExampleHome({super.key}); - - @override - State createState() { - return _CameraExampleHomeState(); - } -} - -/// Returns a suitable camera icon for [direction]. -IconData getCameraLensIcon(CameraLensDirection direction) { - switch (direction) { - case CameraLensDirection.back: - return Icons.camera_rear; - case CameraLensDirection.front: - return Icons.camera_front; - case CameraLensDirection.external: - return Icons.camera; - } - // This enum is from a different package, so a new value could be added at - // any time. The example should keep working if that happens. - // ignore: dead_code - return Icons.camera; -} - -void _logError(String code, String? message) { - // ignore: avoid_print - print('Error: $code${message == null ? '' : '\nError Message: $message'}'); -} - -class _CameraExampleHomeState extends State - with WidgetsBindingObserver, TickerProviderStateMixin { - CameraController? controller; - XFile? imageFile; - XFile? videoFile; - VideoPlayerController? videoController; - VoidCallback? videoPlayerListener; - bool enableAudio = true; - double _minAvailableExposureOffset = 0.0; - double _maxAvailableExposureOffset = 0.0; - double _currentExposureOffset = 0.0; - late AnimationController _flashModeControlRowAnimationController; - late Animation _flashModeControlRowAnimation; - late AnimationController _exposureModeControlRowAnimationController; - late Animation _exposureModeControlRowAnimation; - late AnimationController _focusModeControlRowAnimationController; - late Animation _focusModeControlRowAnimation; - double _minAvailableZoom = 1.0; - double _maxAvailableZoom = 1.0; - double _currentScale = 1.0; - double _baseScale = 1.0; - - // Counting pointers (number of user fingers on screen) - int _pointers = 0; - - @override - void initState() { - super.initState(); - WidgetsBinding.instance.addObserver(this); - - _flashModeControlRowAnimationController = AnimationController( - duration: const Duration(milliseconds: 300), - vsync: this, - ); - _flashModeControlRowAnimation = CurvedAnimation( - parent: _flashModeControlRowAnimationController, - curve: Curves.easeInCubic, - ); - _exposureModeControlRowAnimationController = AnimationController( - duration: const Duration(milliseconds: 300), - vsync: this, - ); - _exposureModeControlRowAnimation = CurvedAnimation( - parent: _exposureModeControlRowAnimationController, - curve: Curves.easeInCubic, - ); - _focusModeControlRowAnimationController = AnimationController( - duration: const Duration(milliseconds: 300), - vsync: this, - ); - _focusModeControlRowAnimation = CurvedAnimation( - parent: _focusModeControlRowAnimationController, - curve: Curves.easeInCubic, - ); - } - - @override - void dispose() { - WidgetsBinding.instance.removeObserver(this); - _flashModeControlRowAnimationController.dispose(); - _exposureModeControlRowAnimationController.dispose(); - super.dispose(); - } - - // #docregion AppLifecycle - @override - void didChangeAppLifecycleState(AppLifecycleState state) { - final CameraController? cameraController = controller; - - // App state changed before we got the chance to initialize. - if (cameraController == null || !cameraController.value.isInitialized) { - return; - } - - if (state == AppLifecycleState.inactive) { - cameraController.dispose(); - } else if (state == AppLifecycleState.resumed) { - _initializeCameraController(cameraController.description); - } - } - // #enddocregion AppLifecycle - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text('Camera example'), - ), - body: Column( - children: [ - Expanded( - child: Container( - decoration: BoxDecoration( - color: Colors.black, - border: Border.all( - color: - controller != null && controller!.value.isRecordingVideo - ? Colors.redAccent - : Colors.grey, - width: 3.0, - ), - ), - child: Padding( - padding: const EdgeInsets.all(1.0), - child: Center( - child: _cameraPreviewWidget(), - ), - ), - ), - ), - _captureControlRowWidget(), - _modeControlRowWidget(), - Padding( - padding: const EdgeInsets.all(5.0), - child: Row( - children: [ - _cameraTogglesRowWidget(), - _thumbnailWidget(), - ], - ), - ), - ], - ), - ); - } - - /// Display the preview from the camera (or a message if the preview is not available). - Widget _cameraPreviewWidget() { - final CameraController? cameraController = controller; - - if (cameraController == null || !cameraController.value.isInitialized) { - return const Text( - 'Tap a camera', - style: TextStyle( - color: Colors.white, - fontSize: 24.0, - fontWeight: FontWeight.w900, - ), - ); - } else { - return Listener( - onPointerDown: (_) => _pointers++, - onPointerUp: (_) => _pointers--, - child: CameraPreview( - controller!, - child: LayoutBuilder( - builder: (BuildContext context, BoxConstraints constraints) { - return GestureDetector( - behavior: HitTestBehavior.opaque, - onScaleStart: _handleScaleStart, - onScaleUpdate: _handleScaleUpdate, - onTapDown: (TapDownDetails details) => - onViewFinderTap(details, constraints), - ); - }), - ), - ); - } - } - - void _handleScaleStart(ScaleStartDetails details) { - _baseScale = _currentScale; - } - - Future _handleScaleUpdate(ScaleUpdateDetails details) async { - // When there are not exactly two fingers on screen don't scale - if (controller == null || _pointers != 2) { - return; - } - - _currentScale = (_baseScale * details.scale) - .clamp(_minAvailableZoom, _maxAvailableZoom); - - await controller!.setZoomLevel(_currentScale); - } - - /// Display the thumbnail of the captured image or video. - Widget _thumbnailWidget() { - final VideoPlayerController? localVideoController = videoController; - - return Expanded( - child: Align( - alignment: Alignment.centerRight, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - if (localVideoController == null && imageFile == null) - Container() - else - SizedBox( - width: 64.0, - height: 64.0, - child: (localVideoController == null) - ? ( - // The captured image on the web contains a network-accessible URL - // pointing to a location within the browser. It may be displayed - // either with Image.network or Image.memory after loading the image - // bytes to memory. - kIsWeb - ? Image.network(imageFile!.path) - : Image.file(File(imageFile!.path))) - : Container( - decoration: BoxDecoration( - border: Border.all(color: Colors.pink)), - child: Center( - child: AspectRatio( - aspectRatio: - localVideoController.value.aspectRatio, - child: VideoPlayer(localVideoController)), - ), - ), - ), - ], - ), - ), - ); - } - - /// Display a bar with buttons to change the flash and exposure modes - Widget _modeControlRowWidget() { - return Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - IconButton( - icon: const Icon(Icons.flash_on), - color: Colors.blue, - onPressed: controller != null ? onFlashModeButtonPressed : null, - ), - // The exposure and focus mode are currently not supported on the web. - ...!kIsWeb - ? [ - IconButton( - icon: const Icon(Icons.exposure), - color: Colors.blue, - onPressed: controller != null - ? onExposureModeButtonPressed - : null, - ), - IconButton( - icon: const Icon(Icons.filter_center_focus), - color: Colors.blue, - onPressed: - controller != null ? onFocusModeButtonPressed : null, - ) - ] - : [], - IconButton( - icon: Icon(enableAudio ? Icons.volume_up : Icons.volume_mute), - color: Colors.blue, - onPressed: controller != null ? onAudioModeButtonPressed : null, - ), - IconButton( - icon: Icon(controller?.value.isCaptureOrientationLocked ?? false - ? Icons.screen_lock_rotation - : Icons.screen_rotation), - color: Colors.blue, - onPressed: controller != null - ? onCaptureOrientationLockButtonPressed - : null, - ), - ], - ), - _flashModeControlRowWidget(), - _exposureModeControlRowWidget(), - _focusModeControlRowWidget(), - ], - ); - } - - Widget _flashModeControlRowWidget() { - return SizeTransition( - sizeFactor: _flashModeControlRowAnimation, - child: ClipRect( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - IconButton( - icon: const Icon(Icons.flash_off), - color: controller?.value.flashMode == FlashMode.off - ? Colors.orange - : Colors.blue, - onPressed: controller != null - ? () => onSetFlashModeButtonPressed(FlashMode.off) - : null, - ), - IconButton( - icon: const Icon(Icons.flash_auto), - color: controller?.value.flashMode == FlashMode.auto - ? Colors.orange - : Colors.blue, - onPressed: controller != null - ? () => onSetFlashModeButtonPressed(FlashMode.auto) - : null, - ), - IconButton( - icon: const Icon(Icons.flash_on), - color: controller?.value.flashMode == FlashMode.always - ? Colors.orange - : Colors.blue, - onPressed: controller != null - ? () => onSetFlashModeButtonPressed(FlashMode.always) - : null, - ), - IconButton( - icon: const Icon(Icons.highlight), - color: controller?.value.flashMode == FlashMode.torch - ? Colors.orange - : Colors.blue, - onPressed: controller != null - ? () => onSetFlashModeButtonPressed(FlashMode.torch) - : null, - ), - ], - ), - ), - ); - } - - Widget _exposureModeControlRowWidget() { - final ButtonStyle styleAuto = TextButton.styleFrom( - foregroundColor: controller?.value.exposureMode == ExposureMode.auto - ? Colors.orange - : Colors.blue, - ); - final ButtonStyle styleLocked = TextButton.styleFrom( - foregroundColor: controller?.value.exposureMode == ExposureMode.locked - ? Colors.orange - : Colors.blue, - ); - - return SizeTransition( - sizeFactor: _exposureModeControlRowAnimation, - child: ClipRect( - child: ColoredBox( - color: Colors.grey.shade50, - child: Column( - children: [ - const Center( - child: Text('Exposure Mode'), - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - TextButton( - style: styleAuto, - onPressed: controller != null - ? () => - onSetExposureModeButtonPressed(ExposureMode.auto) - : null, - onLongPress: () { - if (controller != null) { - controller!.setExposurePoint(null); - showInSnackBar('Resetting exposure point'); - } - }, - child: const Text('AUTO'), - ), - TextButton( - style: styleLocked, - onPressed: controller != null - ? () => - onSetExposureModeButtonPressed(ExposureMode.locked) - : null, - child: const Text('LOCKED'), - ), - TextButton( - style: styleLocked, - onPressed: controller != null - ? () => controller!.setExposureOffset(0.0) - : null, - child: const Text('RESET OFFSET'), - ), - ], - ), - const Center( - child: Text('Exposure Offset'), - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Text(_minAvailableExposureOffset.toString()), - Slider( - value: _currentExposureOffset, - min: _minAvailableExposureOffset, - max: _maxAvailableExposureOffset, - label: _currentExposureOffset.toString(), - onChanged: _minAvailableExposureOffset == - _maxAvailableExposureOffset - ? null - : setExposureOffset, - ), - Text(_maxAvailableExposureOffset.toString()), - ], - ), - ], - ), - ), - ), - ); - } - - Widget _focusModeControlRowWidget() { - final ButtonStyle styleAuto = TextButton.styleFrom( - foregroundColor: controller?.value.focusMode == FocusMode.auto - ? Colors.orange - : Colors.blue, - ); - final ButtonStyle styleLocked = TextButton.styleFrom( - foregroundColor: controller?.value.focusMode == FocusMode.locked - ? Colors.orange - : Colors.blue, - ); - - return SizeTransition( - sizeFactor: _focusModeControlRowAnimation, - child: ClipRect( - child: ColoredBox( - color: Colors.grey.shade50, - child: Column( - children: [ - const Center( - child: Text('Focus Mode'), - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - TextButton( - style: styleAuto, - onPressed: controller != null - ? () => onSetFocusModeButtonPressed(FocusMode.auto) - : null, - onLongPress: () { - if (controller != null) { - controller!.setFocusPoint(null); - } - showInSnackBar('Resetting focus point'); - }, - child: const Text('AUTO'), - ), - TextButton( - style: styleLocked, - onPressed: controller != null - ? () => onSetFocusModeButtonPressed(FocusMode.locked) - : null, - child: const Text('LOCKED'), - ), - ], - ), - ], - ), - ), - ), - ); - } - - /// Display the control bar with buttons to take pictures and record videos. - Widget _captureControlRowWidget() { - final CameraController? cameraController = controller; - - return Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - IconButton( - icon: const Icon(Icons.camera_alt), - color: Colors.blue, - onPressed: cameraController != null && - cameraController.value.isInitialized && - !cameraController.value.isRecordingVideo - ? onTakePictureButtonPressed - : null, - ), - IconButton( - icon: const Icon(Icons.videocam), - color: Colors.blue, - onPressed: cameraController != null && - cameraController.value.isInitialized && - !cameraController.value.isRecordingVideo - ? onVideoRecordButtonPressed - : null, - ), - IconButton( - icon: cameraController != null && - cameraController.value.isRecordingPaused - ? const Icon(Icons.play_arrow) - : const Icon(Icons.pause), - color: Colors.blue, - onPressed: cameraController != null && - cameraController.value.isInitialized && - cameraController.value.isRecordingVideo - ? (cameraController.value.isRecordingPaused) - ? onResumeButtonPressed - : onPauseButtonPressed - : null, - ), - IconButton( - icon: const Icon(Icons.stop), - color: Colors.red, - onPressed: cameraController != null && - cameraController.value.isInitialized && - cameraController.value.isRecordingVideo - ? onStopButtonPressed - : null, - ), - IconButton( - icon: const Icon(Icons.pause_presentation), - color: - cameraController != null && cameraController.value.isPreviewPaused - ? Colors.red - : Colors.blue, - onPressed: - cameraController == null ? null : onPausePreviewButtonPressed, - ), - ], - ); - } - - /// Display a row of toggle to select the camera (or a message if no camera is available). - Widget _cameraTogglesRowWidget() { - final List toggles = []; - - void onChanged(CameraDescription? description) { - if (description == null) { - return; - } - - onNewCameraSelected(description); - } - - if (_cameras.isEmpty) { - SchedulerBinding.instance.addPostFrameCallback((_) async { - showInSnackBar('No camera found.'); - }); - return const Text('None'); - } else { - for (final CameraDescription cameraDescription in _cameras) { - toggles.add( - SizedBox( - width: 90.0, - child: RadioListTile( - title: Icon(getCameraLensIcon(cameraDescription.lensDirection)), - groupValue: controller?.description, - value: cameraDescription, - onChanged: onChanged, - ), - ), - ); - } - } - - return Row(children: toggles); - } - - String timestamp() => DateTime.now().millisecondsSinceEpoch.toString(); - - void showInSnackBar(String message) { - ScaffoldMessenger.of(context) - .showSnackBar(SnackBar(content: Text(message))); - } - - void onViewFinderTap(TapDownDetails details, BoxConstraints constraints) { - if (controller == null) { - return; - } - - final CameraController cameraController = controller!; - - final Offset offset = Offset( - details.localPosition.dx / constraints.maxWidth, - details.localPosition.dy / constraints.maxHeight, - ); - cameraController.setExposurePoint(offset); - cameraController.setFocusPoint(offset); - } - - Future onNewCameraSelected(CameraDescription cameraDescription) async { - if (controller != null) { - return controller!.setDescription(cameraDescription); - } else { - return _initializeCameraController(cameraDescription); - } - } - - Future _initializeCameraController( - CameraDescription cameraDescription) async { - final CameraController cameraController = CameraController( - cameraDescription, - kIsWeb ? ResolutionPreset.max : ResolutionPreset.medium, - enableAudio: enableAudio, - imageFormatGroup: ImageFormatGroup.jpeg, - ); - - controller = cameraController; - - // If the controller is updated then update the UI. - cameraController.addListener(() { - if (mounted) { - setState(() {}); - } - if (cameraController.value.hasError) { - showInSnackBar( - 'Camera error ${cameraController.value.errorDescription}'); - } - }); - - try { - await cameraController.initialize(); - await Future.wait(>[ - // The exposure mode is currently not supported on the web. - ...!kIsWeb - ? >[ - cameraController.getMinExposureOffset().then( - (double value) => _minAvailableExposureOffset = value), - cameraController - .getMaxExposureOffset() - .then((double value) => _maxAvailableExposureOffset = value) - ] - : >[], - cameraController - .getMaxZoomLevel() - .then((double value) => _maxAvailableZoom = value), - cameraController - .getMinZoomLevel() - .then((double value) => _minAvailableZoom = value), - ]); - } on CameraException catch (e) { - switch (e.code) { - case 'CameraAccessDenied': - showInSnackBar('You have denied camera access.'); - case 'CameraAccessDeniedWithoutPrompt': - // iOS only - showInSnackBar('Please go to Settings app to enable camera access.'); - case 'CameraAccessRestricted': - // iOS only - showInSnackBar('Camera access is restricted.'); - case 'AudioAccessDenied': - showInSnackBar('You have denied audio access.'); - case 'AudioAccessDeniedWithoutPrompt': - // iOS only - showInSnackBar('Please go to Settings app to enable audio access.'); - case 'AudioAccessRestricted': - // iOS only - showInSnackBar('Audio access is restricted.'); - default: - _showCameraException(e); - break; - } - } - - if (mounted) { - setState(() {}); - } - } - - void onTakePictureButtonPressed() { - takePicture().then((XFile? file) { - if (mounted) { - setState(() { - imageFile = file; - videoController?.dispose(); - videoController = null; - }); - if (file != null) { - showInSnackBar('Picture saved to ${file.path}'); - } - } - }); - } - - void onFlashModeButtonPressed() { - if (_flashModeControlRowAnimationController.value == 1) { - _flashModeControlRowAnimationController.reverse(); - } else { - _flashModeControlRowAnimationController.forward(); - _exposureModeControlRowAnimationController.reverse(); - _focusModeControlRowAnimationController.reverse(); - } - } - - void onExposureModeButtonPressed() { - if (_exposureModeControlRowAnimationController.value == 1) { - _exposureModeControlRowAnimationController.reverse(); - } else { - _exposureModeControlRowAnimationController.forward(); - _flashModeControlRowAnimationController.reverse(); - _focusModeControlRowAnimationController.reverse(); - } - } - - void onFocusModeButtonPressed() { - if (_focusModeControlRowAnimationController.value == 1) { - _focusModeControlRowAnimationController.reverse(); - } else { - _focusModeControlRowAnimationController.forward(); - _flashModeControlRowAnimationController.reverse(); - _exposureModeControlRowAnimationController.reverse(); - } - } - - void onAudioModeButtonPressed() { - enableAudio = !enableAudio; - if (controller != null) { - onNewCameraSelected(controller!.description); - } - } - - Future onCaptureOrientationLockButtonPressed() async { - try { - if (controller != null) { - final CameraController cameraController = controller!; - if (cameraController.value.isCaptureOrientationLocked) { - await cameraController.unlockCaptureOrientation(); - showInSnackBar('Capture orientation unlocked'); - } else { - await cameraController.lockCaptureOrientation(); - showInSnackBar( - 'Capture orientation locked to ${cameraController.value.lockedCaptureOrientation.toString().split('.').last}'); - } - } - } on CameraException catch (e) { - _showCameraException(e); - } - } - - void onSetFlashModeButtonPressed(FlashMode mode) { - setFlashMode(mode).then((_) { - if (mounted) { - setState(() {}); - } - showInSnackBar('Flash mode set to ${mode.toString().split('.').last}'); - }); - } - - void onSetExposureModeButtonPressed(ExposureMode mode) { - setExposureMode(mode).then((_) { - if (mounted) { - setState(() {}); - } - showInSnackBar('Exposure mode set to ${mode.toString().split('.').last}'); - }); - } - - void onSetFocusModeButtonPressed(FocusMode mode) { - setFocusMode(mode).then((_) { - if (mounted) { - setState(() {}); - } - showInSnackBar('Focus mode set to ${mode.toString().split('.').last}'); - }); - } - - void onVideoRecordButtonPressed() { - startVideoRecording().then((_) { - if (mounted) { - setState(() {}); - } - }); - } - - void onStopButtonPressed() { - stopVideoRecording().then((XFile? file) { - if (mounted) { - setState(() {}); - } - if (file != null) { - showInSnackBar('Video recorded to ${file.path}'); - videoFile = file; - _startVideoPlayer(); - } - }); - } - - Future onPausePreviewButtonPressed() async { - final CameraController? cameraController = controller; - - if (cameraController == null || !cameraController.value.isInitialized) { - showInSnackBar('Error: select a camera first.'); - return; - } - - if (cameraController.value.isPreviewPaused) { - await cameraController.resumePreview(); - } else { - await cameraController.pausePreview(); - } - - if (mounted) { - setState(() {}); - } - } - - void onPauseButtonPressed() { - pauseVideoRecording().then((_) { - if (mounted) { - setState(() {}); - } - showInSnackBar('Video recording paused'); - }); - } - - void onResumeButtonPressed() { - resumeVideoRecording().then((_) { - if (mounted) { - setState(() {}); - } - showInSnackBar('Video recording resumed'); - }); - } - - Future startVideoRecording() async { - final CameraController? cameraController = controller; - - if (cameraController == null || !cameraController.value.isInitialized) { - showInSnackBar('Error: select a camera first.'); - return; - } - - if (cameraController.value.isRecordingVideo) { - // A recording is already started, do nothing. - return; - } - - try { - await cameraController.startVideoRecording(); - } on CameraException catch (e) { - _showCameraException(e); - return; - } - } - - Future stopVideoRecording() async { - final CameraController? cameraController = controller; - - if (cameraController == null || !cameraController.value.isRecordingVideo) { - return null; - } - - try { - return cameraController.stopVideoRecording(); - } on CameraException catch (e) { - _showCameraException(e); - return null; - } - } - - Future pauseVideoRecording() async { - final CameraController? cameraController = controller; - - if (cameraController == null || !cameraController.value.isRecordingVideo) { - return; - } - - try { - await cameraController.pauseVideoRecording(); - } on CameraException catch (e) { - _showCameraException(e); - rethrow; - } - } - - Future resumeVideoRecording() async { - final CameraController? cameraController = controller; - - if (cameraController == null || !cameraController.value.isRecordingVideo) { - return; - } - - try { - await cameraController.resumeVideoRecording(); - } on CameraException catch (e) { - _showCameraException(e); - rethrow; - } - } - - Future setFlashMode(FlashMode mode) async { - if (controller == null) { - return; - } - - try { - await controller!.setFlashMode(mode); - } on CameraException catch (e) { - _showCameraException(e); - rethrow; - } - } - - Future setExposureMode(ExposureMode mode) async { - if (controller == null) { - return; - } - - try { - await controller!.setExposureMode(mode); - } on CameraException catch (e) { - _showCameraException(e); - rethrow; - } - } - - Future setExposureOffset(double offset) async { - if (controller == null) { - return; - } - - setState(() { - _currentExposureOffset = offset; - }); - try { - offset = await controller!.setExposureOffset(offset); - } on CameraException catch (e) { - _showCameraException(e); - rethrow; - } - } - - Future setFocusMode(FocusMode mode) async { - if (controller == null) { - return; - } - - try { - await controller!.setFocusMode(mode); - } on CameraException catch (e) { - _showCameraException(e); - rethrow; - } - } - - Future _startVideoPlayer() async { - if (videoFile == null) { - return; - } - - final VideoPlayerController vController = kIsWeb - ? VideoPlayerController.networkUrl(Uri.parse(videoFile!.path)) - : VideoPlayerController.file(File(videoFile!.path)); - - videoPlayerListener = () { - if (videoController != null) { - // Refreshing the state to update video player with the correct ratio. - if (mounted) { - setState(() {}); - } - videoController!.removeListener(videoPlayerListener!); - } - }; - vController.addListener(videoPlayerListener!); - await vController.setLooping(true); - await vController.initialize(); - await videoController?.dispose(); - if (mounted) { - setState(() { - imageFile = null; - videoController = vController; - }); - } - await vController.play(); - } - - Future takePicture() async { - final CameraController? cameraController = controller; - if (cameraController == null || !cameraController.value.isInitialized) { - showInSnackBar('Error: select a camera first.'); - return null; - } - - if (cameraController.value.isTakingPicture) { - // A capture is already pending, do nothing. - return null; - } - - try { - final XFile file = await cameraController.takePicture(); - return file; - } on CameraException catch (e) { - _showCameraException(e); - return null; - } - } - - void _showCameraException(CameraException e) { - _logError(e.code, e.description); - showInSnackBar('Error: ${e.code}\n${e.description}'); - } -} - -/// CameraApp is the Main Application. -class CameraApp extends StatelessWidget { - /// Default Constructor - const CameraApp({super.key}); - - @override - Widget build(BuildContext context) { - return const MaterialApp( - home: CameraExampleHome(), - ); - } -} - -List _cameras = []; - -Future main() async { - // Fetch the available cameras before initializing the app. - try { - WidgetsFlutterBinding.ensureInitialized(); - _cameras = await availableCameras(); - } on CameraException catch (e) { - _logError(e.code, e.description); - } - runApp(const CameraApp()); -} diff --git a/lib/temp_long_click.dart b/lib/temp_long_click.dart deleted file mode 100755 index caf2ddd..0000000 --- a/lib/temp_long_click.dart +++ /dev/null @@ -1,99 +0,0 @@ -import 'package:flutter/material.dart'; - -void main() { - runApp(const MyApp()); -} - -class MyApp extends StatelessWidget { - const MyApp({super.key}); - - @override - Widget build(BuildContext context) { - return MaterialApp( - title: 'Flutter Demo', - theme: ThemeData( - primarySwatch: Colors.blue, - ), - home: const MyHomePage(), - ); - } -} - -class MyHomePage extends StatefulWidget { - const MyHomePage({super.key}); - - @override - // ignore: library_private_types_in_public_api - _MyHomePageState createState() => _MyHomePageState(); -} - -class _MyHomePageState extends State { - OverlayEntry? _overlayEntry; - - void _showOverlay(BuildContext context, Offset position) { - if (_overlayEntry != null) { - _overlayEntry!.remove(); - } - - _overlayEntry = OverlayEntry( - builder: (context) => Positioned( - left: position.dx, - top: position.dy, - child: Material( - child: Container( - width: 50, - height: 70, - color: Colors.black, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - IconButton( - icon: const Icon(Icons.delete), - onPressed: () { - // 在这里处理按钮点击事件 - debugPrint('删除按钮点击'); - _hideOverlay(); - }, - ), - const Text( - '删除', - style: TextStyle(fontSize: 10, color: Colors.white), - ), - ], - ), - ), - ), - ), - ); - - Overlay.of(context).insert(_overlayEntry!); - } - - void _hideOverlay() { - debugPrint("数据"); - _overlayEntry?.remove(); - _overlayEntry = null; - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text('长按显示按钮示例'), - ), - body: GestureDetector( - onLongPressStart: (details) { - _showOverlay(context, details.globalPosition); - }, - onTap: _hideOverlay, - child: const SizedBox( - width: double.infinity, - height: double.infinity, - child: ListTile( - title: Text('长按此项'), - ), - ), - ), - ); - } -} diff --git a/lib/translator_history/translator_history_logic.dart b/lib/translator_history/translator_history_logic.dart deleted file mode 100755 index 99627be..0000000 --- a/lib/translator_history/translator_history_logic.dart +++ /dev/null @@ -1,31 +0,0 @@ -import 'package:get/get.dart'; -import 'package:translator_lux/dataBase/table/translator_history_table.dart'; -import 'package:translator_lux/entity/translator_history_entity.dart'; - -import 'translator_history_state.dart'; - -/// @description: -/// @author -/// @date: 2024-06-28 16:26:22 -class TranslatorHistoryLogic extends GetxController { - final state = TranslatorHistoryState(); - - @override - void onInit() { - super.onInit(); - initData(); - } - - @override - dispose() { - super.dispose(); - state.historyData.clear(); - } - - initData() async { - List queryData = - await TranslatorHistoryTable.queryData(); - state.historyData.addAll(queryData); - update(); - } -} diff --git a/lib/translator_history/translator_history_state.dart b/lib/translator_history/translator_history_state.dart deleted file mode 100755 index d4b7f06..0000000 --- a/lib/translator_history/translator_history_state.dart +++ /dev/null @@ -1,9 +0,0 @@ -import 'package:get/get.dart'; -import 'package:translator_lux/entity/translator_history_entity.dart'; - -/// @description: -/// @author -/// @date: 2024-06-28 16:26:22 -class TranslatorHistoryState { - List historyData = [].obs; -} diff --git a/lib/translator_history/translator_history_view.dart b/lib/translator_history/translator_history_view.dart deleted file mode 100755 index 0d2d732..0000000 --- a/lib/translator_history/translator_history_view.dart +++ /dev/null @@ -1,193 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_svg/svg.dart'; -import 'package:get/get.dart'; -import 'package:translator_lux/util/date_converter.dart'; -import 'package:translator_lux/util/image_asset.dart'; -import 'package:translator_lux/util/svg_asset.dart'; -import 'package:translator_lux/util/tts_util.dart'; - -import 'translator_history_logic.dart'; -import 'translator_history_state.dart'; - -/// @description: -/// @author -/// @date: 2024-06-28 16:26:22 -class TranslatorHistoryPage extends StatefulWidget { - static String routName = "/translateHistory"; - - const TranslatorHistoryPage({super.key}); - - @override - State createState() => _TranslatorHistoryPageState(); -} - -class _TranslatorHistoryPageState extends State { - final TranslatorHistoryLogic logic = Get.put(TranslatorHistoryLogic()); - - final TranslatorHistoryState state = Get.find().state; - - ScrollController controller = ScrollController(); - - @override - Widget build(BuildContext context) { - return Scaffold( - extendBodyBehindAppBar: true, - extendBody: true, - appBar: AppBar( - surfaceTintColor: Colors.transparent, - automaticallyImplyLeading: false, - title: Row( - children: [ - IconButton( - onPressed: () { - Get.back(); - }, - icon: const Icon( - Icons.arrow_back_ios, - ), - ), - const Text( - "History", - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - ), - ) - ], - ), - ), - body: CustomScrollView( - slivers: [ - Obx( - () => SliverList.builder( - itemBuilder: (context, index) { - return Padding( - padding: - const EdgeInsets.symmetric(horizontal: 20, vertical: 10), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - index == 0 - ? SizedBox( - height: 120, - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - Text( - DateConverter.stringToYyMmDd(state - .historyData[index].translatorTime!), - style: const TextStyle( - fontSize: 12, - fontWeight: FontWeight.w400, - color: Color(0xff949494), - ), - ), - Image.asset(ImageAsset.rabbitSayGood) - ], - ), - ) - : Container( - height: 50, - color: Colors.red, - child: Text( - DateConverter.stringToYyMmDd( - state.historyData[index].translatorTime!), - style: const TextStyle( - fontSize: 12, - fontWeight: FontWeight.w400, - color: Color(0xff949494), - ), - ), - ), - const SizedBox( - height: 20, - ), - Column( - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - GestureDetector( - onTap: () { - TtsUtil.translatorTtsPlay( - "Let's discuss this later.", - ); - }, - child: Container( - width: 32, - height: 32, - padding: const EdgeInsets.all(5), - decoration: BoxDecoration( - color: const Color(0xff4ECA8C), - borderRadius: BorderRadius.circular(16), - ), - child: SvgPicture.asset( - SvgAsset.speaker, - // ignore: deprecated_member_use - color: Colors.white, - ), - ), - ), - const Expanded( - child: Padding( - padding: EdgeInsets.only(left: 16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Hello", - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - ), - ), - SizedBox( - height: 12, - ), - Text( - "Hola", - style: TextStyle( - fontSize: 13, - fontWeight: FontWeight.w400, - color: Color(0xff979797), - ), - ) - ], - ), - )), - ], - ) - ], - ) - ], - ), - ); - }, - itemCount: state.historyData.length, - ), - ), - ], - ), - ); - } -} -// GestureDetector( -// onTap: () { -// TtsUtil.translatorTtsPlay( -// "hello", -// ); -// }, -// child: Container( -// width: 32, -// height: 32, -// padding: const EdgeInsets.all(3), -// decoration: BoxDecoration( -// color: const Color.fromARGB(78, 78, 202, 140), -// borderRadius: BorderRadius.circular(16)), -// child: SvgPicture.asset( -// SvgAsset.speaker, -// ), -// ), -// ), \ No newline at end of file diff --git a/lib/util/date_util.dart b/lib/util/date_util.dart new file mode 100644 index 0000000..38cbf29 --- /dev/null +++ b/lib/util/date_util.dart @@ -0,0 +1,296 @@ +// Author: fengshengxiong +// Date: 2024/5/11 +// Description: 日期工具类 + +/// 一些常用格式参照。可以自定义格式,例如:'yyyy/MM/dd HH:mm:ss','yyyy/M/d HH:mm:ss'。 +/// 格式要求 +/// year -> yyyy/yy month -> MM/M day -> dd/d +/// hour -> HH/H minute -> mm/m second -> ss/s +class DateFormats { + static String full = 'yyyy-MM-dd HH:mm:ss'; + static String yMoDHM = 'yyyy-MM-dd HH:mm'; + static String yMoD = 'yyyy-MM-dd'; + static String yMo = 'yyyy-MM'; + static String moD = 'MM-dd'; + static String moDHM = 'MM-dd HH:mm'; + static String hMS = 'HH:mm:ss'; + static String hM = 'HH:mm'; + + static String zhFull = 'yyyy年MM月dd日 HH时mm分ss秒'; + static String zhYMoDHM = 'yyyy年MM月dd日 HH时mm分'; + static String zhYMoD = 'yyyy年MM月dd日'; + static String zhYMo = 'yyyy年MM月'; + static String zhMoD = 'MM月dd日'; + static String zhMoDHM = 'MM月dd日 HH时mm分'; + static String zhHMS = 'HH时mm分ss秒'; + static String zhHM = 'HH时mm分'; +} + +class DateUtil { + /// 月->天数. + static Map monthDay = { + 1: 31, + 2: 28, + 3: 31, + 4: 30, + 5: 31, + 6: 30, + 7: 31, + 8: 31, + 9: 30, + 10: 31, + 11: 30, + 12: 31, + }; + + /// 获取当前时间,默认格式(yyyy-MM-dd HH:mm:ss) + static String getNowTimeStr({String? format}) { + return formatDate(DateTime.now(), format: format); + } + + /// 获取当前时间戳 + static int getNowTimestamp() { + return DateTime.now().millisecondsSinceEpoch; + } + + /// 以毫秒为单位格式化日期 + static String formatDateMs(int ms, {bool isUtc = false, String? format}) { + return formatDate(getDateTimeByMs(ms, isUtc: isUtc), format: format); + } + + /// 毫秒 转 DateTime + static DateTime getDateTimeByMs(int ms, {bool isUtc = false}) { + return DateTime.fromMillisecondsSinceEpoch(ms, isUtc: isUtc); + } + + /// 日期str 转 毫秒 + static int? getDateMsByTimeStr(String dateStr, {bool? isUtc}) { + DateTime? dateTime = getDateTime(dateStr, isUtc: isUtc); + return dateTime?.millisecondsSinceEpoch; + } + + /// 按日期str格式化日期 + static String formatDateStr(String dateStr, {bool? isUtc, String? format}) { + return formatDate(getDateTime(dateStr, isUtc: isUtc), format: format); + } + + /// 日期str 转 DateTime + static DateTime? getDateTime(String dateStr, {bool? isUtc}) { + DateTime? dateTime = DateTime.tryParse(dateStr); + if (isUtc != null) { + if (isUtc) { + dateTime = dateTime?.toUtc(); + } else { + dateTime = dateTime?.toLocal(); + } + } + return dateTime; + } + + /// 按DateTime格式化日期 + /// format 转换格式(已提供常用格式 DateFormats,可以自定义格式:'yyyy/MM/dd HH:mm:ss') + /// 格式要求 + /// year -> yyyy/yy month -> MM/M day -> dd/d + /// hour -> HH/H minute -> mm/m second -> ss/s + static String formatDate(DateTime? dateTime, {String? format}) { + if (dateTime == null) return ''; + format = format ?? DateFormats.full; + if (format.contains('yy')) { + String year = dateTime.year.toString(); + if (format.contains('yyyy')) { + format = format.replaceAll('yyyy', year); + } else { + format = format.replaceAll( + 'yy', year.substring(year.length - 2, year.length)); + } + } + + format = _comFormat(dateTime.month, format, 'M', 'MM'); + format = _comFormat(dateTime.day, format, 'd', 'dd'); + format = _comFormat(dateTime.hour, format, 'H', 'HH'); + format = _comFormat(dateTime.minute, format, 'm', 'mm'); + format = _comFormat(dateTime.second, format, 's', 'ss'); + format = _comFormat(dateTime.millisecond, format, 'S', 'SSS'); + + return format; + } + + static String _comFormat(int value, String format, String single, String full) { + if (format.contains(single)) { + if (format.contains(full)) { + format = format.replaceAll(full, value < 10 ? '0$value' : value.toString()); + } else { + format = format.replaceAll(single, value.toString()); + } + } + return format; + } + + /// 工作日 + /// dateTime + /// isUtc + /// languageCode zh or en + /// short + static String getWeekday(DateTime? dateTime, {String languageCode = 'zh', bool short = false}) { + if (dateTime == null) return ''; + String weekday = ''; + switch (dateTime.weekday) { + case 1: + weekday = languageCode == 'zh' ? '星期一' : 'Monday'; + break; + case 2: + weekday = languageCode == 'zh' ? '星期二' : 'Tuesday'; + break; + case 3: + weekday = languageCode == 'zh' ? '星期三' : 'Wednesday'; + break; + case 4: + weekday = languageCode == 'zh' ? '星期四' : 'Thursday'; + break; + case 5: + weekday = languageCode == 'zh' ? '星期五' : 'Friday'; + break; + case 6: + weekday = languageCode == 'zh' ? '星期六' : 'Saturday'; + break; + case 7: + weekday = languageCode == 'zh' ? '星期日' : 'Sunday'; + break; + default: + break; + } + return languageCode == 'zh' + ? (short ? weekday.replaceAll('星期', '周') : weekday) + : weekday.substring(0, short ? 3 : weekday.length); + } + + /// 以毫秒计算工作日 + static String getWeekdayByMs(int milliseconds, {bool isUtc = false, String languageCode = 'zh', bool short = false}) { + DateTime dateTime = getDateTimeByMs(milliseconds, isUtc: isUtc); + return getWeekday(dateTime, languageCode: languageCode, short: short); + } + + /// 在今年的第几天 + static int getDayOfYear(DateTime dateTime) { + int year = dateTime.year; + int month = dateTime.month; + int days = dateTime.day; + for (int i = 1; i < month; i++) { + days = days + monthDay[i]!; + } + if (isLeapYearByYear(year) && month > 2) { + days = days + 1; + } + return days; + } + + /// 在今年的第几天 + static int getDayOfYearByMs(int ms, {bool isUtc = false}) { + return getDayOfYear(DateTime.fromMillisecondsSinceEpoch(ms, isUtc: isUtc)); + } + + /// 是否是当天 + static bool isToday(int? milliseconds, {bool isUtc = false, int? locMs}) { + if (milliseconds == null || milliseconds == 0) return false; + DateTime old = + DateTime.fromMillisecondsSinceEpoch(milliseconds, isUtc: isUtc); + DateTime now; + if (locMs != null) { + now = DateUtil.getDateTimeByMs(locMs); + } else { + now = isUtc ? DateTime.now().toUtc() : DateTime.now().toLocal(); + } + return old.year == now.year && old.month == now.month && old.day == now.day; + } + + /// 是否是昨天. + static bool isYesterday(DateTime dateTime, DateTime locDateTime) { + if (yearIsEqual(dateTime, locDateTime)) { + int spDay = getDayOfYear(locDateTime) - getDayOfYear(dateTime); + return spDay == 1; + } else { + return ((locDateTime.year - dateTime.year == 1) && + dateTime.month == 12 && + locDateTime.month == 1 && + dateTime.day == 31 && + locDateTime.day == 1); + } + } + + /// 是否是昨天 + static bool isYesterdayByMs(int ms, int locMs) { + return isYesterday(DateTime.fromMillisecondsSinceEpoch(ms), + DateTime.fromMillisecondsSinceEpoch(locMs)); + } + + /// 是否是本周 + static bool isWeek(int? ms, {bool isUtc = false, int? locMs}) { + if (ms == null || ms <= 0) { + return false; + } + DateTime oldT = DateTime.fromMillisecondsSinceEpoch(ms, isUtc: isUtc); + DateTime nowT; + if (locMs != null) { + nowT = DateUtil.getDateTimeByMs(locMs, isUtc: isUtc); + } else { + nowT = isUtc ? DateTime.now().toUtc() : DateTime.now().toLocal(); + } + + DateTime old = + nowT.millisecondsSinceEpoch > oldT.millisecondsSinceEpoch ? oldT : nowT; + DateTime now = + nowT.millisecondsSinceEpoch > oldT.millisecondsSinceEpoch ? nowT : oldT; + return (now.weekday >= old.weekday) && + (now.millisecondsSinceEpoch - old.millisecondsSinceEpoch <= + 7 * 24 * 60 * 60 * 1000); + } + + /// 是否同年 + static bool yearIsEqual(DateTime dateTime, DateTime locDateTime) { + return dateTime.year == locDateTime.year; + } + + /// 是否同年 + static bool yearIsEqualByMs(int ms, int locMs) { + return yearIsEqual(DateTime.fromMillisecondsSinceEpoch(ms), + DateTime.fromMillisecondsSinceEpoch(locMs)); + } + + /// 是否是闰年 + static bool isLeapYear(DateTime dateTime) { + return isLeapYearByYear(dateTime.year); + } + + /// 是否是闰年 + static bool isLeapYearByYear(int year) { + return year % 4 == 0 && year % 100 != 0 || year % 400 == 0; + } + + /// 判断两个 DateTime 是否是同一天 + static bool isSameDay(String date1, String date2) { + final dateTime1 = getDateTime(date1); + final dateTime2 = getDateTime(date2); + if(dateTime1?.year == dateTime2?.year && dateTime1?.month == dateTime2?.month && dateTime1?.day == dateTime2?.day) { + return true; + } + return false; + } + + /// 如果如果当前月是12,下月年份需要+1 + static int getNextMonthToYear() { + DateTime dateTime = DateTime.now(); + if(dateTime.month == 12) { + return dateTime.year + 1; + } + return dateTime.year; + } + + /// 获取当前下月 + static int getNextMonth() { + DateTime dateTime = DateTime.now(); + if (dateTime.month == 12) { + return 1; + } + return dateTime.month + 1; + } +} \ No newline at end of file diff --git a/lib/util/device_info_util.dart b/lib/util/device_info_util.dart new file mode 100644 index 0000000..aeb1d09 --- /dev/null +++ b/lib/util/device_info_util.dart @@ -0,0 +1,21 @@ +// Author: fengshengxiong +// Date: 2024/5/11 +// Description: 设备信息 + +import 'package:device_info_plus/device_info_plus.dart'; + +class DeviceInfoUtil { + /// 获取当前Android设备的系统版本 + static Future getAndroidSDKInt() async { + DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin(); + AndroidDeviceInfo androidInfo = await deviceInfoPlugin.androidInfo; + return androidInfo.version.sdkInt; + } + + /// 获取当前iOS设备的系统版本 + static Future getIOSSystemVersion() async { + DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin(); + IosDeviceInfo iosDeviceInfo = await deviceInfoPlugin.iosInfo; + return iosDeviceInfo.systemVersion; + } +} diff --git a/lib/util/image_asset.dart b/lib/util/image_asset.dart deleted file mode 100755 index eb56236..0000000 --- a/lib/util/image_asset.dart +++ /dev/null @@ -1,15 +0,0 @@ -class ImageAsset { - static String homeBackground = "assets/images/png/homeBackground.png"; - static String rabbitSayGood = "assets/images/png/rabbitSayGood.png"; - static String poyLine = "assets/images/png/poyline.png"; - static String photo = "assets/images/png/photo.png"; - static String dialog = "assets/images/png/dialog.png"; - static String history = "assets/images/png/history.png"; - static String toHomeBottom = "assets/images/png/to_home_bottom.png"; - static String switchHomeIcon = "assets/images/png/switch_home_icon.png"; - static String deleteIcon = "assets/images/png/delete_icon.png"; - static String translatorResultBackground = - "assets/images/png/translatorResultBackground.png"; - static String copy = "assets/images/png/copy.png"; - static String fullScreen = "assets/images/png/fullScreen.png"; -} diff --git a/lib/util/num_util.dart b/lib/util/num_util.dart new file mode 100644 index 0000000..d9abd77 --- /dev/null +++ b/lib/util/num_util.dart @@ -0,0 +1,23 @@ +// Author: fengshengxiong +// Date: 2024/5/7 +// Description: 整数、浮点数工具类 + +import 'dart:math'; + +class NumUtil { + /// 获取一个随机随 + static int getRandomNumber(int min, int max) { + Random random = Random(); + int randomNumber = random.nextInt(max - min) + min; + return randomNumber; + } + + /// 获取不包括当前的随机数 + static int getRandomNumberExcludingCurrent(int min, int max, int current) { + int newNumber; + do { + newNumber = getRandomNumber(min, max); + } while (newNumber == current); + return newNumber; + } +} \ No newline at end of file diff --git a/lib/util/obj_util.dart b/lib/util/obj_util.dart new file mode 100644 index 0000000..d1a6fae --- /dev/null +++ b/lib/util/obj_util.dart @@ -0,0 +1,51 @@ +// Author: fengshengxiong +// Date: 2024/5/7 +// Description: 对象工具类 + +class ObjUtil { + static bool isNotEmptyStr(String? str) { + return str != null && str.trim().isNotEmpty; + } + + static String getStr(String? str) { + return isNotEmptyStr(str) ? str! : ''; + } + + static bool isNotEmptyList(Iterable? list) { + return list != null && list.isNotEmpty; + } + + static bool isNotEmptyMap(Map? map) { + return map != null && map.isNotEmpty; + } + + static bool isEmpty(Object? object) { + if (object == null) return true; + if (object is String && object.trim().isEmpty) { + return true; + } else if (object is Iterable && object.isEmpty) { + return true; + } else if (object is Map && object.isEmpty) { + return true; + } + return false; + } + + static bool isNotEmpty(Object? object) { + return !isEmpty(object); + } + + /// Returns true Two List Is Equal. + static bool twoListIsEqual(List? listA, List? listB) { + if (listA == listB) return true; + if (listA == null || listB == null) return false; + int length = listA.length; + if (length != listB.length) return false; + for (int i = 0; i < length; i++) { + if (!listA.contains(listB[i])) { + return false; + } + } + return true; + } +} \ No newline at end of file diff --git a/lib/util/permission_util.dart b/lib/util/permission_util.dart new file mode 100644 index 0000000..a4b3a6e --- /dev/null +++ b/lib/util/permission_util.dart @@ -0,0 +1,146 @@ +// Author: fengshengxiong +// Date: 2024/5/10 +// Description: 权限处理 + +import 'dart:io'; + +import 'package:get/get.dart'; +import 'package:permission_handler/permission_handler.dart'; +import 'package:trans_lark/widget/remind_dialog.dart'; + +class PermissionUtil { + /// 检测是否有权限 + /// [permissionList] 权限申请列表 + static Future checkPermission(List permissionList, {bool showDialog = true}) async { + // 一个新待申请权限列表 + List newPermissionList = []; + // 遍历当前权限申请列表 + for (Permission permission in permissionList) { + PermissionStatus status = await permission.status; + // 如果不是允许状态就添加到新的申请列表中 + if (!status.isGranted) { + newPermissionList.add(permission); + } + } + + // 如果需要重新申请的列表不是空的 + if (newPermissionList.isNotEmpty) { + PermissionStatus permissionStatus = await _requestPermission(newPermissionList); + switch (permissionStatus) { + // 拒绝状态 + case PermissionStatus.denied: + if (showDialog) _showFailedDialog(newPermissionList); + return false; + // 允许状态 + case PermissionStatus.granted: + case PermissionStatus.limited: + return true; + // 永久拒绝 + case PermissionStatus.provisional: + case PermissionStatus.restricted: + case PermissionStatus.permanentlyDenied: + if (showDialog) _showFailedDialog(newPermissionList, isPermanentlyDenied: true); + break; + } + } else { + return true; + } + return false; + } + + /// 获取新列表中的权限 如果有一项不合格就返回false + static Future _requestPermission(List permissionList) async { + Map statuses = await permissionList.request(); + PermissionStatus currentPermissionStatus = PermissionStatus.granted; + statuses.forEach((key, value) { + if (!value.isGranted || !value.isLimited) { + currentPermissionStatus = value; + return; + } + }); + return currentPermissionStatus; + } + + /// 请求位置权限 + static Future checkLocationAlways() async { + // 获取前置状态 + // Android没有这一步 ios会先访问这个再访问其他的 + PermissionStatus status1 = PermissionStatus.granted; + status1 = await _checkSinglePermission(Permission.locationWhenInUse); + + // 获取第二个状态 + PermissionStatus status2 = PermissionStatus.denied; + + // 如果前置状态为成功才能执行获取第二个状态 + if (status1.isGranted) { + status2 = await _checkSinglePermission(Permission.locationAlways); + } + + // 如果两个都成功那么就返回成功 + if (status1.isGranted && status2.isGranted) { + return true; + } else { + // 如果有一个拒绝那么就失败了 + _showFailedDialog([Permission.locationWhenInUse, Permission.locationAlways], + isPermanentlyDenied: Platform.isIOS ? true : false, + ); + } + return false; + } + + /// 请求单个权限 + static _checkSinglePermission(Permission permission) async { + // 获取当前状态 + PermissionStatus status = await permission.status; + PermissionStatus currentPermissionStatus = PermissionStatus.granted; + // 如果它状态不是允许那么就去获取 + if (!status.isGranted) { + currentPermissionStatus = await _requestPermission([permission]); + } + // 返回最终状态 + return currentPermissionStatus; + } + + /// 权限拒绝后弹窗 + static _showFailedDialog(List permissionList, {bool isPermanentlyDenied = false}) async { + Get.dialog( + barrierDismissible: false, + RemindDialog( + content: await _getDescription(permissionList), + confirmText: isPermanentlyDenied ? 'Go open' : 'Confirm', + confirmOnTap: () { + if (isPermanentlyDenied) { + openAppSettings(); + } else { + checkPermission(permissionList); + } + }, + ), + ); + } + + /// 获取权限描述 + static Future _getDescription(List permissionList) async { + late Permission failedPermission; + for (Permission permission in permissionList) { + if (!await permission.status.isGranted) { + failedPermission = permission; + break; + } + } + + String description = ''; + if (failedPermission == Permission.camera) { + description = 'This will enable you to take photos and recognize text within them for translation.'; + } else if (failedPermission == Permission.photos) { + description = 'This will enable you to select photos from your library for text recognition and translation.'; + } else if (failedPermission == Permission.microphone) { + description = 'This will enable you to input content through voice for recognition and translation.'; + } else if (failedPermission == Permission.speech) { + description = 'This will enable you to use speech recognition to convert voice content to text for translation.'; + } else if (failedPermission == Permission.appTrackingTransparency) { + description = 'This will help us provide a more personalized advertising experience. Your data privacy will be protected.'; + } + return description; + } +} diff --git a/lib/util/svg_asset.dart b/lib/util/svg_asset.dart deleted file mode 100755 index 160a4ee..0000000 --- a/lib/util/svg_asset.dart +++ /dev/null @@ -1,6 +0,0 @@ -class SvgAsset { - static String homeMore = "assets/svg/homeMore.svg"; - static String homeRightIcon = "assets/svg/home_right_icon.svg"; - static String speaker = "assets/svg/speaker.svg"; - static String homeVoice = "assets/svg/homeVoice.svg"; -} diff --git a/lib/util/tracking_authorization_util.dart b/lib/util/tracking_authorization_util.dart new file mode 100644 index 0000000..2ae6d76 --- /dev/null +++ b/lib/util/tracking_authorization_util.dart @@ -0,0 +1,51 @@ +// Author: fengshengxiong +// Date: 2024/6/26 +// Description: iOS跟踪授权工具 + +import 'dart:async'; +import 'dart:io'; + +import 'package:app_tracking_transparency/app_tracking_transparency.dart'; +import 'package:flutter/material.dart'; + +class TrackingAuthorizationUtil { + TrackingAuthorizationUtil._(); + + static final TrackingAuthorizationUtil _instance = TrackingAuthorizationUtil._(); + + factory TrackingAuthorizationUtil() { + return _instance; + } + + Timer? _timer; + + /// 请求跟踪授权 + Future requestTrackingAuthorization() async { + if (Platform.isIOS) { + final TrackingStatus status = await AppTrackingTransparency.trackingAuthorizationStatus; + if (status == TrackingStatus.notDetermined) { + if (_timer != null && _timer!.isActive) { + final TrackingStatus status = await AppTrackingTransparency.requestTrackingAuthorization(); + debugPrint('跟踪授权状态: $status'); + } else { + _startTimer(); + } + } else { + _stopTimer(); + } + } + } + + /// 开始定时器 + void _startTimer() { + _timer = Timer.periodic(const Duration(seconds: 3), (Timer t) { + requestTrackingAuthorization(); + }); + } + + /// 停止定时器 + void _stopTimer() { + _timer?.cancel(); + _timer = null; + } +} diff --git a/lib/util/tts_util.dart b/lib/util/tts_util.dart deleted file mode 100755 index ebe6803..0000000 --- a/lib/util/tts_util.dart +++ /dev/null @@ -1,25 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:flutter_tts/flutter_tts.dart'; -import 'package:translator_lux/global/global_state.dart'; - -class TtsUtil { - static initTts() async { - debugPrint("TTS Service init start"); - await GlobalState.ttsController.setSharedInstance(true); - await GlobalState.ttsController.setIosAudioCategory( - IosTextToSpeechAudioCategory.ambient, - [ - IosTextToSpeechAudioCategoryOptions.allowBluetooth, - IosTextToSpeechAudioCategoryOptions.allowBluetoothA2DP, - IosTextToSpeechAudioCategoryOptions.mixWithOthers - ], - IosTextToSpeechAudioMode.voicePrompt); - await GlobalState.ttsController.awaitSpeakCompletion(true); - await GlobalState.ttsController.awaitSynthCompletion(true); - debugPrint("TTS Service init success"); - } - - static translatorTtsPlay(String text) { - GlobalState.ttsController.speak(text); - } -} diff --git a/lib/widget/base_appbar.dart b/lib/widget/base_appbar.dart new file mode 100644 index 0000000..6f5dc0c --- /dev/null +++ b/lib/widget/base_appbar.dart @@ -0,0 +1,93 @@ +// Author: fengshengxiong +// Date: 2024/5/7 +// Description: BaseAppBar + +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:trans_lark/generated/assets.dart'; + +class BaseAppBar extends StatelessWidget implements PreferredSizeWidget { + const BaseAppBar({ + super.key, + this.backgroundColor, + this.backColor, + this.title, + this.titleWidget, + this.actionWidget, + this.onBackTap, + }); + + final Color? backgroundColor; + final Color? backColor; + final String? title; + final Widget? titleWidget; + final Widget? actionWidget; + final Function()? onBackTap; + + @override + Widget build(BuildContext context) { + return AppBar( + backgroundColor: backgroundColor ?? Colors.white, + surfaceTintColor: Colors.transparent, + automaticallyImplyLeading: false, + centerTitle: false, + title: _buildTitle(context), + titleSpacing: 0.0, + leadingWidth: 0.0, + ); + } + + Widget _buildTitle(BuildContext context) { + return SizedBox( + height: preferredSize.height, + child: Row( + children: [ + SizedBox( + width: 60, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 4), + child: FittedBox( + fit: BoxFit.none, + child: ClipOval( + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: onBackTap ?? Get.back, + child: Padding( + padding: const EdgeInsets.all(10.0), + child: Image.asset( + Assets.imagesAppbarBack, + width: 24, + height: 24, + color: backColor, + ), + ), + ), + ), + ), + ), + ), + ), + Expanded( + child: titleWidget ?? Text( + title ?? '', + overflow: TextOverflow.ellipsis, + style: const TextStyle( + color: Colors.black, + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + ), + SizedBox( + width: 60, + child: actionWidget, + ), + ], + ), + ); + } + + @override + Size get preferredSize => const Size.fromHeight(kToolbarHeight); +} diff --git a/lib/widget/base_easyloading.dart b/lib/widget/base_easyloading.dart new file mode 100644 index 0000000..7ec946a --- /dev/null +++ b/lib/widget/base_easyloading.dart @@ -0,0 +1,38 @@ +// Author: fengshengxiong +// Date: 2024/5/7 +// Description: 加载、进度、提示框 + +import 'package:flutter_easyloading/flutter_easyloading.dart'; + +class BaseEasyLoading { + static void configLoading() { + EasyLoading.instance + // 当loading展示的时候,是否允许用户操作. + ..userInteractions = false + // 点击背景是否关闭. + ..dismissOnTap = false; + } + + static void toast(String? value, {bool? dismissOnTap, bool show = true}) { + EasyLoading.instance.userInteractions = true; + if (show && value != null) { + EasyLoading.showToast( + value, + dismissOnTap: dismissOnTap, + maskType: EasyLoadingMaskType.none, + ); + } + } + + static void loading({ + String? value, + bool show = true, + }) { + EasyLoading.instance.userInteractions = false; + if (show) EasyLoading.show(status: value); + } + + static void dismiss({bool dismiss = true}) { + if (dismiss) EasyLoading.dismiss(); + } +} diff --git a/lib/widget/base_scrollbar.dart b/lib/widget/base_scrollbar.dart new file mode 100644 index 0000000..c52bf5a --- /dev/null +++ b/lib/widget/base_scrollbar.dart @@ -0,0 +1,28 @@ +// Author: fengshengxiong +// Date: 2024/6/17 +// Description: 列表滚动条 + +import 'package:flutter/material.dart'; + +class BaseScrollbar extends StatelessWidget { + const BaseScrollbar({super.key, required this.child, this.thumbColor}); + + final Widget child; + final Color? thumbColor; + + @override + Widget build(BuildContext context) { + return MediaQuery.removePadding( + context: context, + removeTop: true, + removeBottom: true, + child: RawScrollbar( + thumbColor: thumbColor ?? const Color(0xff4ECA8C), + radius: const Radius.circular(8.0), + thickness: 4.0, + thumbVisibility: false, + child: child, + ), + ); + } +} diff --git a/lib/widget/camera_widget.dart b/lib/widget/camera_widget.dart deleted file mode 100755 index 4a1630e..0000000 --- a/lib/widget/camera_widget.dart +++ /dev/null @@ -1,130 +0,0 @@ -import 'package:camera/camera.dart'; -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -import 'package:translator_lux/config/config.dart'; -import 'package:translator_lux/global/global_state.dart'; - -class CameraPage extends StatefulWidget { - const CameraPage({super.key}); - - @override - State createState() => _CameraPageState(); -} - -class _CameraPageState extends State { - late CameraController controller; - - @override - void initState() { - super.initState(); - controller = - CameraController(AppConfig.cameraList[0], ResolutionPreset.high); - controller.initialize().then((_) { - if (!mounted) { - return; - } - setState(() {}); - }).catchError((Object e) { - if (e is CameraException) { - switch (e.code) { - case 'CameraAccessDenied': - // Handle access errors here. - break; - default: - // Handle other errors here. - break; - } - } - }); - } - - @override - void dispose() { - super.dispose(); - controller.dispose(); - } - - @override - Widget build(BuildContext context) { - if (!controller.value.isInitialized) { - return Container(); - } - // 设备尺寸 - final Size size = MediaQuery.of(context).size; - return Scaffold( - body: Stack(children: [ - _cameraView(size), - SafeArea( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), - child: Column( - children: [ - Row( - children: [ - GestureDetector( - onTap: () { - Get.back(); - }, - child: Container( - alignment: Alignment.center, - margin: const EdgeInsets.all(10), - decoration: BoxDecoration( - color: Colors.black, - borderRadius: BorderRadius.circular(30)), - child: const Padding( - padding: EdgeInsets.all(8.0), - child: Icon( - Icons.arrow_back_ios, - color: Colors.white, - size: 18, - ), - ), - ), - ), - Container( - alignment: Alignment.center, - margin: const EdgeInsets.all(10), - decoration: BoxDecoration( - color: Colors.black, - borderRadius: BorderRadius.circular(30)), - child: Padding( - padding: const EdgeInsets.all(8.0), - child: SizedBox( - width: 100, - child: Row( - children: [ - Text( - GlobalState.fromCountryName.value, - style: const TextStyle( - fontSize: 14, color: Colors.white), - ), - ], - ), - ), - ), - ) - ], - ) - ], - ), - )), - ]), - ); - } - - Widget _cameraView(Size size) { - // 设备像素比 - final double deviceRatio = size.width / size.height; - // 相机纵横比 - final double aspectRatio = controller.value.aspectRatio; - return Center( - child: Transform.scale( - scale: aspectRatio / deviceRatio, - child: AspectRatio( - aspectRatio: aspectRatio, - child: Center(child: CameraPreview(controller)), - ), - ), - ); - } -} diff --git a/lib/widget/divider_widget.dart b/lib/widget/divider_widget.dart new file mode 100644 index 0000000..8b1a888 --- /dev/null +++ b/lib/widget/divider_widget.dart @@ -0,0 +1,21 @@ +// Author: fengshengxiong +// Date: 2024/5/9 +// Description: 分割线 + +import 'package:flutter/material.dart'; + +class DividerWidget extends StatelessWidget { + const DividerWidget({super.key, this.height, this.color}); + + final double? height; + final Color? color; + + @override + Widget build(BuildContext context) { + return Divider( + height: height ?? 1, + thickness: height ?? 1, + color: color ?? const Color(0xFFEDEDED), + ); + } +} diff --git a/lib/widget/language_bar.dart b/lib/widget/language_bar.dart new file mode 100755 index 0000000..e172ea8 --- /dev/null +++ b/lib/widget/language_bar.dart @@ -0,0 +1,149 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:trans_lark/generated/assets.dart'; +import 'package:trans_lark/global/translate_language.dart'; +import 'package:trans_lark/widget/language_bottom_sheet.dart'; + +class LanguageBar extends StatelessWidget { + const LanguageBar({ + super.key, + this.isSelect = false, + this.onTapFrom, + this.onTapTo, + }); + + final bool isSelect; + final Function()? onTapFrom; + final Function()? onTapTo; + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Expanded( + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: onTapFrom ?? _onTapFrom, + child: Obx(() { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 10), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + const SizedBox(width: 8), + Flexible( + child: Text( + TranslateLanguage().fromLanguageEntity.value.languageName, + style: TextStyle( + color: !isSelect || !TranslateLanguage().isSelectFromLanguage.value ? const Color(0xff435561) : const Color(0xff4ECA8C), + fontSize: 14, + fontWeight: FontWeight.w500, + overflow: TextOverflow.ellipsis, + ), + ), + ), + const SizedBox(width: 4), + Image.asset( + Assets.imagesToHomeBottom, + width: 8, + height: 8, + ), + ], + ), + ); + }), + ), + ), + ), + const SizedBox(width: 20), + GestureDetector( + onTap: _exchangeOnTap, + child: Container( + width: 26, + height: 18, + decoration: BoxDecoration( + color: const Color(0xffF1F1F1), + borderRadius: BorderRadius.circular(9), + ), + child: FittedBox( + fit: BoxFit.none, + child: Image.asset( + Assets.imagesSwitchHomeIcon, + width: 12, + height: 12, + ), + ), + ), + ), + const SizedBox(width: 20), + Expanded( + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: onTapTo ?? _onTapTo, + child: Obx(() { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 10), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + const SizedBox(width: 8), + Flexible( + child: Text( + TranslateLanguage().toLanguageEntity.value.languageName, + style: TextStyle( + color: !isSelect || TranslateLanguage().isSelectFromLanguage.value ? const Color(0xff435561) : const Color(0xff4ECA8C), + fontSize: 14, + fontWeight: FontWeight.w500, + overflow: TextOverflow.ellipsis, + ), + ), + ), + const SizedBox(width: 4), + Image.asset( + Assets.imagesToHomeBottom, + width: 8, + height: 8, + ), + ], + ), + ); + }), + ), + ), + ), + ], + ); + } + + void _exchangeOnTap() { + var tempLanguageCode = TranslateLanguage().toLanguageEntity.value.languageCode; + var tempLanguageName = TranslateLanguage().toLanguageEntity.value.languageName; + + TranslateLanguage().toLanguageEntity.update((fn) { + fn?.languageCode = TranslateLanguage().fromLanguageEntity.value.languageCode; + fn?.languageName = TranslateLanguage().fromLanguageEntity.value.languageName; + }); + TranslateLanguage().fromLanguageEntity.update((fn) { + fn?.languageCode = tempLanguageCode; + fn?.languageName = tempLanguageName; + }); + } + + void _onTapFrom() { + TranslateLanguage().isSelectFromLanguage.value = true; + Get.bottomSheet( + isScrollControlled: true, + const LanguageBottomSheet(), + ); + } + + void _onTapTo() { + TranslateLanguage().isSelectFromLanguage.value = false; + Get.bottomSheet( + isScrollControlled: true, + const LanguageBottomSheet(), + ); + } +} diff --git a/lib/widget/language_bottom_sheet.dart b/lib/widget/language_bottom_sheet.dart old mode 100755 new mode 100644 index 0a93e5e..1149d2b --- a/lib/widget/language_bottom_sheet.dart +++ b/lib/widget/language_bottom_sheet.dart @@ -1,55 +1,124 @@ -// ignore_for_file: public_member_api_docs, sort_constructors_first -import 'package:country_flags/country_flags.dart'; import 'package:flutter/material.dart'; -import 'package:translator_lux/entity/language_entity.dart'; +import 'package:get/get.dart'; +import 'package:get/get_state_manager/get_state_manager.dart'; +import 'package:trans_lark/entity/language_entity.dart'; +import 'package:trans_lark/generated/assets.dart'; +import 'package:trans_lark/global/translate_language.dart'; +import 'package:trans_lark/widget/base_scrollbar.dart'; +import 'package:trans_lark/widget/language_bar.dart'; -languageBottonSheet( - context, Function(SelectLanguageItemEntity? selectedLanguage) onSelect) { - showModalBottomSheet( - context: context, - builder: (context) { - return Padding( - padding: const EdgeInsets.all(8.0), - child: Column( - children: [ - const TextField( - decoration: InputDecoration( - enabledBorder: - OutlineInputBorder(borderSide: BorderSide(width: 2))), - ), - Expanded( - child: ListView.builder( - itemBuilder: (context, index) { - SelectLanguageItemEntity languageData = - LanguageDataEntity.languageData[index]; - return ListTile( - leading: CountryFlag.fromCountryCode( - languageData.countryCode, - width: 24, - height: 24, - shape: const Circle(), - ), - title: Text(languageData.languageName), - onTap: () { - Navigator.pop(context, languageData); - }, - ); - }, - itemCount: LanguageDataEntity.languageData.length, - )) - ], +class LanguageBottomSheet extends StatelessWidget { + const LanguageBottomSheet({super.key}); + + @override + Widget build(BuildContext context) { + return Container( + width: MediaQuery.of(context).size.width, + height: MediaQuery.of(context).size.height * 0.9, + decoration: const BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(26), topRight: Radius.circular(26), + ), + ), + child: Column( + children: [ + const SizedBox(height: 12), + Container( + width: 28, + height: 4, + decoration: BoxDecoration( + color: const Color(0xffD9D9D9), + borderRadius: BorderRadius.circular(2), + ), ), - ); - }).then( - (selectedLanguage) => onSelect(selectedLanguage), - ); -} + const SizedBox(height: 10), + LanguageBar( + isSelect: true, + onTapFrom: _onTapFrom, + onTapTo: _onTapTo, + ), + const SizedBox(height: 10), + _buildListView(), + ], + ), + ); + } -class LanguageDataEntity { - static List languageData = [ - SelectLanguageItemEntity( - countryCode: "cn", languageName: "Chinese", languageCode: "zh-cn"), - SelectLanguageItemEntity( - countryCode: "us", languageName: "English", languageCode: "en"), - ]; -} + Widget _buildListView() { + return Expanded( + child: BaseScrollbar( + child: ListView.builder( + itemCount: TranslateLanguage().languageList.length, + itemBuilder: (context, index) { + var item = TranslateLanguage().languageList[index]; + return Material( + color: Colors.transparent, + child: InkWell( + onTap: () => _itemOnTap(item), + child: Container( + padding: const EdgeInsets.symmetric(vertical: 13, horizontal: 33), + child: Row( + children: [ + Expanded( + child: Obx(() { + return Text( + item.languageName, + style: TextStyle( + color: TranslateLanguage().isSelectFromLanguage.value + ? (item.languageName == TranslateLanguage().fromLanguageEntity.value.languageName ? const Color(0xff4ECA8C) : const Color(0xff152A3D)) + : (item.languageName == TranslateLanguage().toLanguageEntity.value.languageName ? const Color(0xff4ECA8C) : const Color(0xff152A3D)), + fontSize: 16, + overflow: TextOverflow.ellipsis, + ), + ); + }), + ), + Obx(() { + return Visibility( + visible: TranslateLanguage().isSelectFromLanguage.value + ? (item.languageName == TranslateLanguage().fromLanguageEntity.value.languageName) + : (item.languageName == TranslateLanguage().toLanguageEntity.value.languageName), + child: Padding( + padding: const EdgeInsets.only(left: 10), + child: Image.asset( + Assets.imagesStar, + width: 16, + height: 16, + ), + ), + ); + }), + ], + ), + ), + ), + ); + }, + ), + ), + ); + } + + void _itemOnTap(LanguageEntity item) { + if (TranslateLanguage().isSelectFromLanguage.value) { + TranslateLanguage().fromLanguageEntity.update((fn) { + fn?.languageCode = item.languageCode; + fn?.languageName = item.languageName; + }); + } else { + TranslateLanguage().toLanguageEntity.update((fn) { + fn?.languageCode = item.languageCode; + fn?.languageName = item.languageName; + }); + } + } + + void _onTapFrom() { + TranslateLanguage().isSelectFromLanguage.value = true; + } + + void _onTapTo() { + TranslateLanguage().isSelectFromLanguage.value = false; + } +} \ No newline at end of file diff --git a/lib/widget/language_result_bar.dart b/lib/widget/language_result_bar.dart new file mode 100755 index 0000000..4184f22 --- /dev/null +++ b/lib/widget/language_result_bar.dart @@ -0,0 +1,83 @@ +import 'package:flutter/material.dart'; +import 'package:trans_lark/generated/assets.dart'; + +class LanguageResultBar extends StatelessWidget { + const LanguageResultBar({ + super.key, + required this.fromLanguage, + required this.toLanguage, + }); + + final String fromLanguage; + final String toLanguage; + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 10), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + const SizedBox(width: 8), + Flexible( + child: Text( + fromLanguage, + style: const TextStyle( + color: Color(0xff435561), + fontSize: 14, + fontWeight: FontWeight.w500, + overflow: TextOverflow.ellipsis, + ), + ), + ), + ], + ), + ), + ), + const SizedBox(width: 20), + Container( + width: 26, + height: 18, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(9), + ), + child: FittedBox( + fit: BoxFit.none, + child: Image.asset( + Assets.imagesSwitchHomeIcon, + width: 12, + height: 12, + ), + ), + ), + const SizedBox(width: 20), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 10), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + const SizedBox(width: 8), + Flexible( + child: Text( + toLanguage, + style: const TextStyle( + color: Color(0xff435561), + fontSize: 14, + fontWeight: FontWeight.w500, + overflow: TextOverflow.ellipsis, + ), + ), + ), + ], + ), + ), + ), + ], + ); + } +} diff --git a/lib/widget/language_scene_bar.dart b/lib/widget/language_scene_bar.dart new file mode 100755 index 0000000..da36bda --- /dev/null +++ b/lib/widget/language_scene_bar.dart @@ -0,0 +1,134 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:trans_lark/entity/language_entity.dart'; +import 'package:trans_lark/generated/assets.dart'; + +class LanguageSceneBar extends StatelessWidget { + const LanguageSceneBar({ + super.key, + required this.fromLanguage, + required this.toLanguage, + required this.onTapFrom, + required this.onTapTo, + }); + + final Rx fromLanguage; + final Rx toLanguage; + final Function()? onTapFrom; + final Function()? onTapTo; + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Expanded( + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: onTapFrom, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 10), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + const SizedBox(width: 8), + Flexible( + child: Obx(() { + return Text( + fromLanguage.value.languageName, + style: const TextStyle( + color: Color(0xff435561), + fontSize: 14, + fontWeight: FontWeight.w500, + overflow: TextOverflow.ellipsis, + ), + ); + }), + ), + const SizedBox(width: 4), + Image.asset( + Assets.imagesToHomeBottom, + width: 8, + height: 8, + ), + ], + ), + ), + ), + ), + ), + const SizedBox(width: 20), + GestureDetector( + onTap: _exchangeOnTap, + child: Container( + width: 26, + height: 18, + decoration: BoxDecoration( + color: const Color(0xffF1F1F1), + borderRadius: BorderRadius.circular(9), + ), + child: FittedBox( + fit: BoxFit.none, + child: Image.asset( + Assets.imagesSwitchHomeIcon, + width: 12, + height: 12, + ), + ), + ), + ), + const SizedBox(width: 20), + Expanded( + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: onTapTo, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 10), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + const SizedBox(width: 8), + Flexible( + child: Obx(() { + return Text( + toLanguage.value.languageName, + style: const TextStyle( + color: Color(0xff435561), + fontSize: 14, + fontWeight: FontWeight.w500, + overflow: TextOverflow.ellipsis, + ), + ); + }), + ), + const SizedBox(width: 4), + Image.asset( + Assets.imagesToHomeBottom, + width: 8, + height: 8, + ), + ], + ), + ), + ), + ), + ), + ], + ); + } + + void _exchangeOnTap() { + var tempLanguageCode = toLanguage.value.languageCode; + var tempLanguageName = toLanguage.value.languageName; + + toLanguage.update((fn) { + fn?.languageCode = fromLanguage.value.languageCode; + fn?.languageName = fromLanguage.value.languageName; + }); + fromLanguage.update((fn) { + fn?.languageCode = tempLanguageCode; + fn?.languageName = tempLanguageName; + }); + } +} diff --git a/lib/widget/language_scene_bottom_sheet.dart b/lib/widget/language_scene_bottom_sheet.dart new file mode 100644 index 0000000..4f5fd72 --- /dev/null +++ b/lib/widget/language_scene_bottom_sheet.dart @@ -0,0 +1,100 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:trans_lark/entity/language_entity.dart'; +import 'package:trans_lark/generated/assets.dart'; +import 'package:trans_lark/widget/base_scrollbar.dart'; + +class LanguageSceneBottomSheet extends StatelessWidget { + const LanguageSceneBottomSheet({super.key, required this.languageScene, required this.selectedLanguage}); + + final List languageScene; + final Rx selectedLanguage; + + @override + Widget build(BuildContext context) { + return Container( + width: MediaQuery.of(context).size.width, + height: MediaQuery.of(context).size.height * 0.6, + decoration: const BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(26), topRight: Radius.circular(26), + ), + ), + child: Column( + children: [ + const SizedBox(height: 12), + Container( + width: 28, + height: 4, + decoration: BoxDecoration( + color: const Color(0xffD9D9D9), + borderRadius: BorderRadius.circular(2), + ), + ), + const SizedBox(height: 10), + _buildListView(), + ], + ), + ); + } + + Widget _buildListView() { + return Expanded( + child: BaseScrollbar( + child: ListView.builder( + itemCount: languageScene.length, + itemBuilder: (context, index) { + var item = languageScene[index]; + return Material( + color: Colors.transparent, + child: InkWell( + onTap: () => _itemOnTap(item), + child: Container( + padding: const EdgeInsets.symmetric(vertical: 13, horizontal: 33), + child: Row( + children: [ + Expanded( + child: Obx(() { + return Text( + item.languageName, + style: TextStyle( + color: item.languageName == selectedLanguage.value.languageName ? const Color(0xff4ECA8C) : const Color(0xff152A3D), + fontSize: 16, + overflow: TextOverflow.ellipsis, + ), + ); + }), + ), + Obx(() { + return Visibility( + visible: item.languageName == selectedLanguage.value.languageName, + child: Padding( + padding: const EdgeInsets.only(left: 10), + child: Image.asset( + Assets.imagesStar, + width: 16, + height: 16, + ), + ), + ); + }), + ], + ), + ), + ), + ); + }, + ), + ), + ); + } + + void _itemOnTap(LanguageEntity item) { + selectedLanguage.update((fn) { + fn?.languageCode = item.languageCode; + fn?.languageName = item.languageName; + }); + Get.back(); + } +} \ No newline at end of file diff --git a/lib/widget/photo_picker_bottom_sheet.dart b/lib/widget/photo_picker_bottom_sheet.dart new file mode 100644 index 0000000..38141c3 --- /dev/null +++ b/lib/widget/photo_picker_bottom_sheet.dart @@ -0,0 +1,63 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:trans_lark/widget/divider_widget.dart'; + +class PhotoPickerBottomSheet extends StatelessWidget { + const PhotoPickerBottomSheet({super.key, required this.funCamera, required this.funGallery}); + + final Function() funCamera; + final Function() funGallery; + + @override + Widget build(BuildContext context) { + return SafeArea( + child: Container( + width: MediaQuery.of(context).size.width, + height: 120, + clipBehavior: Clip.antiAlias, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(26), + ), + child: Column( + children: [ + Expanded( + child: _buildItem('Open camera', funCamera), + ), + const DividerWidget(), + Expanded( + child: _buildItem('Open gallery', funGallery), + ), + ], + ), + ), + ); + } + + Widget _buildItem(String text, Function() function) { + return Material( + color: Colors.transparent, + child: InkWell( + onTap: () { + Get.back(); + function(); + }, + child: SizedBox( + width: double.infinity, + child: Center( + child: Text( + text, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + color: Color(0xFF333333), + fontSize: 16, + fontWeight: FontWeight.w600, + ) + ), + ), + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/widget/remind_dialog.dart b/lib/widget/remind_dialog.dart new file mode 100644 index 0000000..508061f --- /dev/null +++ b/lib/widget/remind_dialog.dart @@ -0,0 +1,122 @@ +// Author: fengshengxiong +// Date: 2024/5/11 +// Description: 提示框 + +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +class RemindDialog extends StatelessWidget { + const RemindDialog({ + super.key, + this.title, + this.content, + this.showCancelBtn = true, + this.cancelText, + this.confirmText, + this.confirmOnTap, + this.popScope = false, + }); + + final String? title; + final String? content; + final bool? showCancelBtn; + final String? cancelText; + final String? confirmText; + final Function()? confirmOnTap; + final bool popScope; + + @override + Widget build(BuildContext context) { + return PopScope( + canPop: popScope, + child: Center( + child: Container( + width: MediaQuery.of(context).size.width * 0.8, + clipBehavior: Clip.antiAlias, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: IntrinsicHeight( + child: Column( + children: [ + const SizedBox(height: 10), + Text( + title ?? 'Reminder', + textAlign: TextAlign.center, + style: const TextStyle( + color: Colors.black, + fontSize: 18, + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(height: 12), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 10), + child: Text( + content ?? '', + textAlign: TextAlign.center, + style: const TextStyle( + color: Color(0xFF333333), + fontSize: 16, + ), + ), + ), + const SizedBox(height: 12), + const Divider( + height: 1, + thickness: 1, + color: Color(0xFFE5E5E5), + ), + SizedBox( + height: 52, + child: Row( + children: [ + if (showCancelBtn!) ...[ + _optionButton(cancelText ?? 'Cancel', false), + Container( + width: 1, + height: double.infinity, + color: const Color(0xFFE5E5E5), + ), + ], + _optionButton(confirmText ?? 'Confirm', true), + ], + ), + ), + ], + ), + ), + ), + ), + ); + } + + Widget _optionButton(String label, bool isConfirm) { + return Expanded( + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: () { + Get.back(); + if (isConfirm && confirmOnTap != null) confirmOnTap!(); + }, + child: SizedBox( + height: double.infinity, + child: Center( + child: Text( + label, + textAlign: TextAlign.center, + style: TextStyle( + color: isConfirm ? Colors.black : const Color(0xFF666666), + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + ), + ), + ), + ), + ); + } +} diff --git a/lib/widget/speak_dialog.dart b/lib/widget/speak_dialog.dart new file mode 100644 index 0000000..ea67d12 --- /dev/null +++ b/lib/widget/speak_dialog.dart @@ -0,0 +1,129 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:get/get.dart'; +import 'package:trans_lark/generated/assets.dart'; +import 'package:trans_lark/global/translate_language.dart'; + +class SpeakDialog extends StatelessWidget { + const SpeakDialog({ + super.key, + required this.isListening, + required this.onTap, + }); + + final Rx isListening; + final Function() onTap; + + @override + Widget build(BuildContext context) { + return Stack( + alignment: Alignment.bottomCenter, + children: [ + Positioned( + bottom: 320, + child: Row( + children: [ + SvgPicture.asset( + Assets.svgEars, + width: 38, + height: 59, + ), + const SizedBox(width: 4), + SvgPicture.asset( + Assets.svgEars, + width: 38, + height: 59, + ), + ], + ), + ), + Container( + width: MediaQuery.of(context).size.width, + height: 340, + padding: const EdgeInsets.symmetric(horizontal: 10), + clipBehavior: Clip.antiAlias, + decoration: const BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.only(topLeft: Radius.circular(20), topRight: Radius.circular(20)), + ), + child: Column( + children: [ + Image.asset( + Assets.imagesSpeakFace, + width: 215, + height: 155, + // color: Colors.white, + ), + const SizedBox(height: 4), + Column( + children: [ + Obx(() { + return Visibility( + visible: isListening.value, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text( + 'Please speak ', + style: TextStyle( + color: Color(0xff999999), + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + Flexible( + child: Text( + TranslateLanguage().fromLanguageEntity.value.languageName, + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + color: Colors.black, + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ), + ); + }), + Obx(() { + return Text( + isListening.value ? 'I am listening, Click the button to end and translate' : 'Click the button to start speaking', + maxLines: 2, + overflow: TextOverflow.ellipsis, + textAlign: TextAlign.center, + style: const TextStyle( + color: Color(0xff999999), + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ); + }), + ], + ), + const SizedBox(height: 20), + ClipOval( + child: GestureDetector( + onTap: onTap, + child: Container( + width: 60, + height: 60, + color: const Color(0xff87ECB3), + child: Obx(() { + return Icon( + isListening.value ? Icons.pause : Icons.mic, + color: Colors.white, + size: 36, + ); + }), + ), + ), + ), + ], + ), + ), + ], + ); + } +} diff --git a/lib/widget/tranlator_from_to.dart b/lib/widget/tranlator_from_to.dart deleted file mode 100755 index 0c5bd84..0000000 --- a/lib/widget/tranlator_from_to.dart +++ /dev/null @@ -1,145 +0,0 @@ -import 'package:country_flags/country_flags.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/widgets.dart'; -import 'package:get/get.dart'; -import 'package:translator_lux/global/global_state.dart'; -import 'package:translator_lux/util/image_asset.dart'; -import 'package:translator_lux/widget/language_bottom_sheet.dart'; - -class TranslatorFromTo extends StatelessWidget { - const TranslatorFromTo({ - super.key, - }); - - @override - Widget build(BuildContext context) { - return Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - GestureDetector( - onTap: () { - languageBottonSheet(context, (language) { - if (language != null) { - GlobalState.fromCountryName.value = language.languageName; - GlobalState.fromCountryCode.value = language.countryCode; - GlobalState.fromLanguageCode.value = language.languageCode; - } else { - debugPrint("no such country"); - } - }); - }, - child: Obx( - () => SizedBox( - height: 25, - child: Row( - children: [ - CountryFlag.fromCountryCode( - GlobalState.fromCountryCode.value, - width: 24, - height: 24, - shape: const Circle(), - ), - Padding( - padding: const EdgeInsets.only(left: 10), - child: SizedBox( - width: 55, - child: Text( - GlobalState.fromCountryName.value, - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, - overflow: TextOverflow.ellipsis, - ), - ), - ), - ), - Padding( - padding: const EdgeInsets.only(left: 4), - child: Image.asset( - ImageAsset.toHomeBottom, - width: 12, - height: 12, - ), - ) - ], - ), - ), - ), - ), - GestureDetector( - onTap: () { - var tempCountryName = GlobalState.toCountryName.value; - var tempCountryCode = GlobalState.toCountryCode.value; - var tempLanguageName = GlobalState.toLanguageCode.value; - - GlobalState.toCountryName.value = GlobalState.fromCountryName.value; - GlobalState.toCountryCode.value = GlobalState.fromCountryCode.value; - GlobalState.toLanguageCode.value = - GlobalState.fromLanguageCode.value; - - GlobalState.fromCountryName.value = tempCountryName; - GlobalState.fromCountryCode.value = tempCountryCode; - GlobalState.fromLanguageCode.value = tempLanguageName; - }, - child: Container( - width: 26, - height: 16, - decoration: BoxDecoration( - color: const Color(0xffF1F1F1), - borderRadius: BorderRadius.circular(8)), - child: Image.asset(ImageAsset.switchHomeIcon), - ), - ), - GestureDetector( - onTap: () { - languageBottonSheet(context, (language) { - if (language != null) { - GlobalState.toCountryName.value = language.languageName; - GlobalState.toCountryCode.value = language.countryCode; - GlobalState.toLanguageCode.value = language.languageCode; - } else { - debugPrint("no such country"); - } - }); - }, - child: Obx( - () => SizedBox( - height: 25, - child: Row( - children: [ - CountryFlag.fromCountryCode( - GlobalState.toCountryCode.value, - width: 24, - height: 24, - shape: const Circle(), - ), - Padding( - padding: const EdgeInsets.only(left: 10), - child: SizedBox( - width: 55, - child: Text( - GlobalState.toCountryName.value, - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, - overflow: TextOverflow.ellipsis), - ), - ), - ), - Padding( - padding: const EdgeInsets.only(left: 4), - child: Image.asset( - ImageAsset.toHomeBottom, - width: 12, - height: 12, - ), - ) - ], - ), - ), - ), - ), - ], - ); - } -} diff --git a/lib/widget/translate_text_full_screen_page.dart b/lib/widget/translate_text_full_screen_page.dart index 339b1ee..13e69a6 100755 --- a/lib/widget/translate_text_full_screen_page.dart +++ b/lib/widget/translate_text_full_screen_page.dart @@ -2,8 +2,9 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_svg/svg.dart'; import 'package:get/get.dart'; -import 'package:translator_lux/util/svg_asset.dart'; -import 'package:translator_lux/util/tts_util.dart'; +import 'package:trans_lark/generated/assets.dart'; +import 'package:trans_lark/global/translate_language.dart'; +import 'package:trans_lark/global/tts_manager.dart'; class TranslateTextFullScreenPage extends StatefulWidget { const TranslateTextFullScreenPage({super.key}); @@ -42,43 +43,48 @@ class _TranslateTextFullScreenPageState body: Container( width: double.infinity, height: double.infinity, - color: const Color.fromARGB(202, 78, 202, 140), + color: const Color(0xff4ECA8C), child: SafeArea( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - child: ListView(children: [ - Text( - showData, - style: const TextStyle( - fontSize: 64, + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: ListView( + padding: const EdgeInsets.all(10), + children: [ + Text( + showData, + style: const TextStyle( + fontSize: 48, + color: Colors.white, + height: 1.0, + ), + ), + ], + ), + ), + GestureDetector( + onTap: () { + TtsManager().translatorTtsPlay(showData, TranslateLanguage().toLanguageEntity.value.languageCode); + }, + child: Padding( + padding: const EdgeInsets.only(top: 20), + child: ClipOval( + child: Container( + width: 32, + height: 32, color: Colors.white, - fontWeight: FontWeight.w400, + child: FittedBox( + fit: BoxFit.none, + child: SvgPicture.asset( + Assets.svgSpeaker, + ), + ), ), ), - ])), - GestureDetector( - onTap: () { - TtsUtil.translatorTtsPlay(showData); - }, - child: Container( - width: 32, - height: 32, - padding: const EdgeInsets.all(5), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(16), - color: Colors.white, - ), - child: SvgPicture.asset( - SvgAsset.speaker, - ), - ), - ) - ], - ), + ), + ) + ], ), ), ), diff --git a/lib/widget/view_state_widget.dart b/lib/widget/view_state_widget.dart new file mode 100644 index 0000000..ceaa559 --- /dev/null +++ b/lib/widget/view_state_widget.dart @@ -0,0 +1,78 @@ +// Author: fengshengxiong +// Date: 2024/5/7 +// Description: 状态视图 + +import 'package:flutter/material.dart'; + +/// 四种视图状态 +enum ViewState { normal, error, loading, empty } + +class ViewStateWidget extends StatelessWidget { + const ViewStateWidget({ + super.key, + required this.viewState, + required this.child, + }); + + final ViewState viewState; + final Widget child; + + @override + Widget build(BuildContext context) { + switch (viewState) { + case ViewState.normal: + return child; + case ViewState.loading: + return loadingView(); + case ViewState.empty: + return emptyView(); + case ViewState.error: + return errorView(); + } + } +} + +/// 加载中视图 +Widget loadingView({ + Color? color, + Animation? valueColor, + Color? backgroundColor, + double? value, +}) { + return Center( + child: CircularProgressIndicator( + color: const Color(0xff4ECA8C), + valueColor: valueColor, + backgroundColor: backgroundColor ?? Colors.white, + value: value, + ), + ); +} + +/// 空视图 +Widget emptyView({String? msg, Color? textColor}) { + return Center( + child: Text( + msg ?? 'No data', + textAlign: TextAlign.center, + style: TextStyle( + color: textColor ?? Colors.white, + fontSize: 15, + ), + ), + ); +} + +/// 错误视图 +Widget errorView({String? msg, Color? textColor}) { + return Center( + child: Text( + msg ?? 'An error occurred, please try again later', + textAlign: TextAlign.center, + style: TextStyle( + color: textColor ?? Colors.white, + fontSize: 15, + ), + ), + ); +} diff --git a/linux/.gitignore b/linux/.gitignore deleted file mode 100755 index d3896c9..0000000 --- a/linux/.gitignore +++ /dev/null @@ -1 +0,0 @@ -flutter/ephemeral diff --git a/linux/CMakeLists.txt b/linux/CMakeLists.txt deleted file mode 100755 index 832c0bd..0000000 --- a/linux/CMakeLists.txt +++ /dev/null @@ -1,145 +0,0 @@ -# Project-level configuration. -cmake_minimum_required(VERSION 3.10) -project(runner LANGUAGES CXX) - -# The name of the executable created for the application. Change this to change -# the on-disk name of your application. -set(BINARY_NAME "translator_lux") -# The unique GTK application identifier for this application. See: -# https://wiki.gnome.org/HowDoI/ChooseApplicationID -set(APPLICATION_ID "com.lux.translator.translator_lux") - -# Explicitly opt in to modern CMake behaviors to avoid warnings with recent -# versions of CMake. -cmake_policy(SET CMP0063 NEW) - -# Load bundled libraries from the lib/ directory relative to the binary. -set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") - -# Root filesystem for cross-building. -if(FLUTTER_TARGET_PLATFORM_SYSROOT) - set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) - set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) - set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) - set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) - set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) - set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) -endif() - -# Define build configuration options. -if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) - set(CMAKE_BUILD_TYPE "Debug" CACHE - STRING "Flutter build mode" FORCE) - set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS - "Debug" "Profile" "Release") -endif() - -# Compilation settings that should be applied to most targets. -# -# Be cautious about adding new options here, as plugins use this function by -# default. In most cases, you should add new options to specific targets instead -# of modifying this function. -function(APPLY_STANDARD_SETTINGS TARGET) - target_compile_features(${TARGET} PUBLIC cxx_std_14) - target_compile_options(${TARGET} PRIVATE -Wall -Werror) - target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") - target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") -endfunction() - -# Flutter library and tool build rules. -set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") -add_subdirectory(${FLUTTER_MANAGED_DIR}) - -# System-level dependencies. -find_package(PkgConfig REQUIRED) -pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) - -add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") - -# Define the application target. To change its name, change BINARY_NAME above, -# not the value here, or `flutter run` will no longer work. -# -# Any new source files that you add to the application should be added here. -add_executable(${BINARY_NAME} - "main.cc" - "my_application.cc" - "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" -) - -# Apply the standard set of build settings. This can be removed for applications -# that need different build settings. -apply_standard_settings(${BINARY_NAME}) - -# Add dependency libraries. Add any application-specific dependencies here. -target_link_libraries(${BINARY_NAME} PRIVATE flutter) -target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) - -# Run the Flutter tool portions of the build. This must not be removed. -add_dependencies(${BINARY_NAME} flutter_assemble) - -# Only the install-generated bundle's copy of the executable will launch -# correctly, since the resources must in the right relative locations. To avoid -# people trying to run the unbundled copy, put it in a subdirectory instead of -# the default top-level location. -set_target_properties(${BINARY_NAME} - PROPERTIES - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" -) - - -# Generated plugin build rules, which manage building the plugins and adding -# them to the application. -include(flutter/generated_plugins.cmake) - - -# === Installation === -# By default, "installing" just makes a relocatable bundle in the build -# directory. -set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") -if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) - set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) -endif() - -# Start with a clean build bundle directory every time. -install(CODE " - file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") - " COMPONENT Runtime) - -set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") -set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") - -install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" - COMPONENT Runtime) - -install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" - COMPONENT Runtime) - -install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" - COMPONENT Runtime) - -foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) - install(FILES "${bundled_library}" - DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" - COMPONENT Runtime) -endforeach(bundled_library) - -# Copy the native assets provided by the build.dart from all packages. -set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/") -install(DIRECTORY "${NATIVE_ASSETS_DIR}" - DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" - COMPONENT Runtime) - -# Fully re-copy the assets directory on each build to avoid having stale files -# from a previous install. -set(FLUTTER_ASSET_DIR_NAME "flutter_assets") -install(CODE " - file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") - " COMPONENT Runtime) -install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" - DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) - -# Install the AOT library on non-Debug builds only. -if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") - install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" - COMPONENT Runtime) -endif() diff --git a/linux/flutter/CMakeLists.txt b/linux/flutter/CMakeLists.txt deleted file mode 100755 index d5bd016..0000000 --- a/linux/flutter/CMakeLists.txt +++ /dev/null @@ -1,88 +0,0 @@ -# This file controls Flutter-level build steps. It should not be edited. -cmake_minimum_required(VERSION 3.10) - -set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") - -# Configuration provided via flutter tool. -include(${EPHEMERAL_DIR}/generated_config.cmake) - -# TODO: Move the rest of this into files in ephemeral. See -# https://github.com/flutter/flutter/issues/57146. - -# Serves the same purpose as list(TRANSFORM ... PREPEND ...), -# which isn't available in 3.10. -function(list_prepend LIST_NAME PREFIX) - set(NEW_LIST "") - foreach(element ${${LIST_NAME}}) - list(APPEND NEW_LIST "${PREFIX}${element}") - endforeach(element) - set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) -endfunction() - -# === Flutter Library === -# System-level dependencies. -find_package(PkgConfig REQUIRED) -pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) -pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) -pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) - -set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") - -# Published to parent scope for install step. -set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) -set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) -set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) -set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) - -list(APPEND FLUTTER_LIBRARY_HEADERS - "fl_basic_message_channel.h" - "fl_binary_codec.h" - "fl_binary_messenger.h" - "fl_dart_project.h" - "fl_engine.h" - "fl_json_message_codec.h" - "fl_json_method_codec.h" - "fl_message_codec.h" - "fl_method_call.h" - "fl_method_channel.h" - "fl_method_codec.h" - "fl_method_response.h" - "fl_plugin_registrar.h" - "fl_plugin_registry.h" - "fl_standard_message_codec.h" - "fl_standard_method_codec.h" - "fl_string_codec.h" - "fl_value.h" - "fl_view.h" - "flutter_linux.h" -) -list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") -add_library(flutter INTERFACE) -target_include_directories(flutter INTERFACE - "${EPHEMERAL_DIR}" -) -target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") -target_link_libraries(flutter INTERFACE - PkgConfig::GTK - PkgConfig::GLIB - PkgConfig::GIO -) -add_dependencies(flutter flutter_assemble) - -# === Flutter tool backend === -# _phony_ is a non-existent file to force this command to run every time, -# since currently there's no way to get a full input/output list from the -# flutter tool. -add_custom_command( - OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} - ${CMAKE_CURRENT_BINARY_DIR}/_phony_ - COMMAND ${CMAKE_COMMAND} -E env - ${FLUTTER_TOOL_ENVIRONMENT} - "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" - ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} - VERBATIM -) -add_custom_target(flutter_assemble DEPENDS - "${FLUTTER_LIBRARY}" - ${FLUTTER_LIBRARY_HEADERS} -) diff --git a/linux/main.cc b/linux/main.cc deleted file mode 100755 index e7c5c54..0000000 --- a/linux/main.cc +++ /dev/null @@ -1,6 +0,0 @@ -#include "my_application.h" - -int main(int argc, char** argv) { - g_autoptr(MyApplication) app = my_application_new(); - return g_application_run(G_APPLICATION(app), argc, argv); -} diff --git a/linux/my_application.cc b/linux/my_application.cc deleted file mode 100755 index 7dd93ac..0000000 --- a/linux/my_application.cc +++ /dev/null @@ -1,124 +0,0 @@ -#include "my_application.h" - -#include -#ifdef GDK_WINDOWING_X11 -#include -#endif - -#include "flutter/generated_plugin_registrant.h" - -struct _MyApplication { - GtkApplication parent_instance; - char** dart_entrypoint_arguments; -}; - -G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) - -// Implements GApplication::activate. -static void my_application_activate(GApplication* application) { - MyApplication* self = MY_APPLICATION(application); - GtkWindow* window = - GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); - - // Use a header bar when running in GNOME as this is the common style used - // by applications and is the setup most users will be using (e.g. Ubuntu - // desktop). - // If running on X and not using GNOME then just use a traditional title bar - // in case the window manager does more exotic layout, e.g. tiling. - // If running on Wayland assume the header bar will work (may need changing - // if future cases occur). - gboolean use_header_bar = TRUE; -#ifdef GDK_WINDOWING_X11 - GdkScreen* screen = gtk_window_get_screen(window); - if (GDK_IS_X11_SCREEN(screen)) { - const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); - if (g_strcmp0(wm_name, "GNOME Shell") != 0) { - use_header_bar = FALSE; - } - } -#endif - if (use_header_bar) { - GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); - gtk_widget_show(GTK_WIDGET(header_bar)); - gtk_header_bar_set_title(header_bar, "translator_lux"); - gtk_header_bar_set_show_close_button(header_bar, TRUE); - gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); - } else { - gtk_window_set_title(window, "translator_lux"); - } - - gtk_window_set_default_size(window, 1280, 720); - gtk_widget_show(GTK_WIDGET(window)); - - g_autoptr(FlDartProject) project = fl_dart_project_new(); - fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); - - FlView* view = fl_view_new(project); - gtk_widget_show(GTK_WIDGET(view)); - gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); - - fl_register_plugins(FL_PLUGIN_REGISTRY(view)); - - gtk_widget_grab_focus(GTK_WIDGET(view)); -} - -// Implements GApplication::local_command_line. -static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { - MyApplication* self = MY_APPLICATION(application); - // Strip out the first argument as it is the binary name. - self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); - - g_autoptr(GError) error = nullptr; - if (!g_application_register(application, nullptr, &error)) { - g_warning("Failed to register: %s", error->message); - *exit_status = 1; - return TRUE; - } - - g_application_activate(application); - *exit_status = 0; - - return TRUE; -} - -// Implements GApplication::startup. -static void my_application_startup(GApplication* application) { - //MyApplication* self = MY_APPLICATION(object); - - // Perform any actions required at application startup. - - G_APPLICATION_CLASS(my_application_parent_class)->startup(application); -} - -// Implements GApplication::shutdown. -static void my_application_shutdown(GApplication* application) { - //MyApplication* self = MY_APPLICATION(object); - - // Perform any actions required at application shutdown. - - G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application); -} - -// Implements GObject::dispose. -static void my_application_dispose(GObject* object) { - MyApplication* self = MY_APPLICATION(object); - g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); - G_OBJECT_CLASS(my_application_parent_class)->dispose(object); -} - -static void my_application_class_init(MyApplicationClass* klass) { - G_APPLICATION_CLASS(klass)->activate = my_application_activate; - G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; - G_APPLICATION_CLASS(klass)->startup = my_application_startup; - G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown; - G_OBJECT_CLASS(klass)->dispose = my_application_dispose; -} - -static void my_application_init(MyApplication* self) {} - -MyApplication* my_application_new() { - return MY_APPLICATION(g_object_new(my_application_get_type(), - "application-id", APPLICATION_ID, - "flags", G_APPLICATION_NON_UNIQUE, - nullptr)); -} diff --git a/linux/my_application.h b/linux/my_application.h deleted file mode 100755 index 72271d5..0000000 --- a/linux/my_application.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef FLUTTER_MY_APPLICATION_H_ -#define FLUTTER_MY_APPLICATION_H_ - -#include - -G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, - GtkApplication) - -/** - * my_application_new: - * - * Creates a new Flutter-based application. - * - * Returns: a new #MyApplication. - */ -MyApplication* my_application_new(); - -#endif // FLUTTER_MY_APPLICATION_H_ diff --git a/macos/.gitignore b/macos/.gitignore deleted file mode 100755 index 746adbb..0000000 --- a/macos/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -# Flutter-related -**/Flutter/ephemeral/ -**/Pods/ - -# Xcode-related -**/dgph -**/xcuserdata/ diff --git a/macos/Flutter/Flutter-Debug.xcconfig b/macos/Flutter/Flutter-Debug.xcconfig deleted file mode 100755 index c2efd0b..0000000 --- a/macos/Flutter/Flutter-Debug.xcconfig +++ /dev/null @@ -1 +0,0 @@ -#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/macos/Flutter/Flutter-Release.xcconfig b/macos/Flutter/Flutter-Release.xcconfig deleted file mode 100755 index c2efd0b..0000000 --- a/macos/Flutter/Flutter-Release.xcconfig +++ /dev/null @@ -1 +0,0 @@ -#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj deleted file mode 100755 index 307b0f6..0000000 --- a/macos/Runner.xcodeproj/project.pbxproj +++ /dev/null @@ -1,705 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 54; - objects = { - -/* Begin PBXAggregateTarget section */ - 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { - isa = PBXAggregateTarget; - buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; - buildPhases = ( - 33CC111E2044C6BF0003C045 /* ShellScript */, - ); - dependencies = ( - ); - name = "Flutter Assemble"; - productName = FLX; - }; -/* End PBXAggregateTarget section */ - -/* Begin PBXBuildFile section */ - 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; - 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; - 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; - 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; - 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; - 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; -/* End PBXBuildFile section */ - -/* Begin PBXContainerItemProxy section */ - 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 33CC10E52044A3C60003C045 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 33CC10EC2044A3C60003C045; - remoteInfo = Runner; - }; - 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 33CC10E52044A3C60003C045 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 33CC111A2044C6BA0003C045; - remoteInfo = FLX; - }; -/* End PBXContainerItemProxy section */ - -/* Begin PBXCopyFilesBuildPhase section */ - 33CC110E2044A8840003C045 /* Bundle Framework */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - ); - name = "Bundle Framework"; - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - -/* Begin PBXFileReference section */ - 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; - 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; - 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; - 33CC10ED2044A3C60003C045 /* translator_lux.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "translator_lux.app"; sourceTree = BUILT_PRODUCTS_DIR; }; - 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; - 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; - 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; - 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; - 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; - 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; - 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; - 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; - 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; - 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; - 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; - 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 331C80D2294CF70F00263BE5 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 33CC10EA2044A3C60003C045 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 331C80D6294CF71000263BE5 /* RunnerTests */ = { - isa = PBXGroup; - children = ( - 331C80D7294CF71000263BE5 /* RunnerTests.swift */, - ); - path = RunnerTests; - sourceTree = ""; - }; - 33BA886A226E78AF003329D5 /* Configs */ = { - isa = PBXGroup; - children = ( - 33E5194F232828860026EE4D /* AppInfo.xcconfig */, - 9740EEB21CF90195004384FC /* Debug.xcconfig */, - 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, - 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, - ); - path = Configs; - sourceTree = ""; - }; - 33CC10E42044A3C60003C045 = { - isa = PBXGroup; - children = ( - 33FAB671232836740065AC1E /* Runner */, - 33CEB47122A05771004F2AC0 /* Flutter */, - 331C80D6294CF71000263BE5 /* RunnerTests */, - 33CC10EE2044A3C60003C045 /* Products */, - D73912EC22F37F3D000D13A0 /* Frameworks */, - ); - sourceTree = ""; - }; - 33CC10EE2044A3C60003C045 /* Products */ = { - isa = PBXGroup; - children = ( - 33CC10ED2044A3C60003C045 /* translator_lux.app */, - 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, - ); - name = Products; - sourceTree = ""; - }; - 33CC11242044D66E0003C045 /* Resources */ = { - isa = PBXGroup; - children = ( - 33CC10F22044A3C60003C045 /* Assets.xcassets */, - 33CC10F42044A3C60003C045 /* MainMenu.xib */, - 33CC10F72044A3C60003C045 /* Info.plist */, - ); - name = Resources; - path = ..; - sourceTree = ""; - }; - 33CEB47122A05771004F2AC0 /* Flutter */ = { - isa = PBXGroup; - children = ( - 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, - 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, - 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, - 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, - ); - path = Flutter; - sourceTree = ""; - }; - 33FAB671232836740065AC1E /* Runner */ = { - isa = PBXGroup; - children = ( - 33CC10F02044A3C60003C045 /* AppDelegate.swift */, - 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, - 33E51913231747F40026EE4D /* DebugProfile.entitlements */, - 33E51914231749380026EE4D /* Release.entitlements */, - 33CC11242044D66E0003C045 /* Resources */, - 33BA886A226E78AF003329D5 /* Configs */, - ); - path = Runner; - sourceTree = ""; - }; - D73912EC22F37F3D000D13A0 /* Frameworks */ = { - isa = PBXGroup; - children = ( - ); - name = Frameworks; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 331C80D4294CF70F00263BE5 /* RunnerTests */ = { - isa = PBXNativeTarget; - buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; - buildPhases = ( - 331C80D1294CF70F00263BE5 /* Sources */, - 331C80D2294CF70F00263BE5 /* Frameworks */, - 331C80D3294CF70F00263BE5 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - 331C80DA294CF71000263BE5 /* PBXTargetDependency */, - ); - name = RunnerTests; - productName = RunnerTests; - productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; - 33CC10EC2044A3C60003C045 /* Runner */ = { - isa = PBXNativeTarget; - buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; - buildPhases = ( - 33CC10E92044A3C60003C045 /* Sources */, - 33CC10EA2044A3C60003C045 /* Frameworks */, - 33CC10EB2044A3C60003C045 /* Resources */, - 33CC110E2044A8840003C045 /* Bundle Framework */, - 3399D490228B24CF009A79C7 /* ShellScript */, - ); - buildRules = ( - ); - dependencies = ( - 33CC11202044C79F0003C045 /* PBXTargetDependency */, - ); - name = Runner; - productName = Runner; - productReference = 33CC10ED2044A3C60003C045 /* translator_lux.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 33CC10E52044A3C60003C045 /* Project object */ = { - isa = PBXProject; - attributes = { - BuildIndependentTargetsInParallel = YES; - LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 1510; - ORGANIZATIONNAME = ""; - TargetAttributes = { - 331C80D4294CF70F00263BE5 = { - CreatedOnToolsVersion = 14.0; - TestTargetID = 33CC10EC2044A3C60003C045; - }; - 33CC10EC2044A3C60003C045 = { - CreatedOnToolsVersion = 9.2; - LastSwiftMigration = 1100; - ProvisioningStyle = Automatic; - SystemCapabilities = { - com.apple.Sandbox = { - enabled = 1; - }; - }; - }; - 33CC111A2044C6BA0003C045 = { - CreatedOnToolsVersion = 9.2; - ProvisioningStyle = Manual; - }; - }; - }; - buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; - compatibilityVersion = "Xcode 9.3"; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = 33CC10E42044A3C60003C045; - productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 33CC10EC2044A3C60003C045 /* Runner */, - 331C80D4294CF70F00263BE5 /* RunnerTests */, - 33CC111A2044C6BA0003C045 /* Flutter Assemble */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 331C80D3294CF70F00263BE5 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 33CC10EB2044A3C60003C045 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, - 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - 3399D490228B24CF009A79C7 /* ShellScript */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; - }; - 33CC111E2044C6BF0003C045 /* ShellScript */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - Flutter/ephemeral/FlutterInputs.xcfilelist, - ); - inputPaths = ( - Flutter/ephemeral/tripwire, - ); - outputFileListPaths = ( - Flutter/ephemeral/FlutterOutputs.xcfilelist, - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; - }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 331C80D1294CF70F00263BE5 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 33CC10E92044A3C60003C045 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, - 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, - 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXTargetDependency section */ - 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 33CC10EC2044A3C60003C045 /* Runner */; - targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; - }; - 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; - targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; - }; -/* End PBXTargetDependency section */ - -/* Begin PBXVariantGroup section */ - 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { - isa = PBXVariantGroup; - children = ( - 33CC10F52044A3C60003C045 /* Base */, - ); - name = MainMenu.xib; - path = Runner; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - -/* Begin XCBuildConfiguration section */ - 331C80DB294CF71000263BE5 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.lux.translator.translatorLux.RunnerTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/translator_lux.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/translator_lux"; - }; - name = Debug; - }; - 331C80DC294CF71000263BE5 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.lux.translator.translatorLux.RunnerTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/translator_lux.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/translator_lux"; - }; - name = Release; - }; - 331C80DD294CF71000263BE5 /* Profile */ = { - isa = XCBuildConfiguration; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.lux.translator.translatorLux.RunnerTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/translator_lux.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/translator_lux"; - }; - name = Profile; - }; - 338D0CE9231458BD00FA5F75 /* Profile */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CODE_SIGN_IDENTITY = "-"; - COPY_PHASE_STRIP = NO; - DEAD_CODE_STRIPPING = YES; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_USER_SCRIPT_SANDBOXING = NO; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.14; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = macosx; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; - }; - name = Profile; - }; - 338D0CEA231458BD00FA5F75 /* Profile */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; - CODE_SIGN_STYLE = Automatic; - COMBINE_HIDPI_IMAGES = YES; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - ); - PROVISIONING_PROFILE_SPECIFIER = ""; - SWIFT_VERSION = 5.0; - }; - name = Profile; - }; - 338D0CEB231458BD00FA5F75 /* Profile */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_STYLE = Manual; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Profile; - }; - 33CC10F92044A3C60003C045 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CODE_SIGN_IDENTITY = "-"; - COPY_PHASE_STRIP = NO; - DEAD_CODE_STRIPPING = YES; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - ENABLE_USER_SCRIPT_SANDBOXING = NO; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.14; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = macosx; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - }; - name = Debug; - }; - 33CC10FA2044A3C60003C045 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CODE_SIGN_IDENTITY = "-"; - COPY_PHASE_STRIP = NO; - DEAD_CODE_STRIPPING = YES; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_USER_SCRIPT_SANDBOXING = NO; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.14; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = macosx; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; - }; - name = Release; - }; - 33CC10FC2044A3C60003C045 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; - CODE_SIGN_STYLE = Automatic; - COMBINE_HIDPI_IMAGES = YES; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - ); - PROVISIONING_PROFILE_SPECIFIER = ""; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - }; - name = Debug; - }; - 33CC10FD2044A3C60003C045 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; - CODE_SIGN_STYLE = Automatic; - COMBINE_HIDPI_IMAGES = YES; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - ); - PROVISIONING_PROFILE_SPECIFIER = ""; - SWIFT_VERSION = 5.0; - }; - name = Release; - }; - 33CC111C2044C6BA0003C045 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_STYLE = Manual; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Debug; - }; - 33CC111D2044C6BA0003C045 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_STYLE = Automatic; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 331C80DB294CF71000263BE5 /* Debug */, - 331C80DC294CF71000263BE5 /* Release */, - 331C80DD294CF71000263BE5 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 33CC10F92044A3C60003C045 /* Debug */, - 33CC10FA2044A3C60003C045 /* Release */, - 338D0CE9231458BD00FA5F75 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 33CC10FC2044A3C60003C045 /* Debug */, - 33CC10FD2044A3C60003C045 /* Release */, - 338D0CEA231458BD00FA5F75 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 33CC111C2044C6BA0003C045 /* Debug */, - 33CC111D2044C6BA0003C045 /* Release */, - 338D0CEB231458BD00FA5F75 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 33CC10E52044A3C60003C045 /* Project object */; -} diff --git a/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100755 index 18d9810..0000000 --- a/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme deleted file mode 100755 index 8828fa4..0000000 --- a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ /dev/null @@ -1,98 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/macos/Runner.xcworkspace/contents.xcworkspacedata b/macos/Runner.xcworkspace/contents.xcworkspacedata deleted file mode 100755 index 1d526a1..0000000 --- a/macos/Runner.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100755 index 18d9810..0000000 --- a/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/macos/Runner/AppDelegate.swift b/macos/Runner/AppDelegate.swift deleted file mode 100755 index d53ef64..0000000 --- a/macos/Runner/AppDelegate.swift +++ /dev/null @@ -1,9 +0,0 @@ -import Cocoa -import FlutterMacOS - -@NSApplicationMain -class AppDelegate: FlutterAppDelegate { - override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { - return true - } -} diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100755 index a2ec33f..0000000 --- a/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "images" : [ - { - "size" : "16x16", - "idiom" : "mac", - "filename" : "app_icon_16.png", - "scale" : "1x" - }, - { - "size" : "16x16", - "idiom" : "mac", - "filename" : "app_icon_32.png", - "scale" : "2x" - }, - { - "size" : "32x32", - "idiom" : "mac", - "filename" : "app_icon_32.png", - "scale" : "1x" - }, - { - "size" : "32x32", - "idiom" : "mac", - "filename" : "app_icon_64.png", - "scale" : "2x" - }, - { - "size" : "128x128", - "idiom" : "mac", - "filename" : "app_icon_128.png", - "scale" : "1x" - }, - { - "size" : "128x128", - "idiom" : "mac", - "filename" : "app_icon_256.png", - "scale" : "2x" - }, - { - "size" : "256x256", - "idiom" : "mac", - "filename" : "app_icon_256.png", - "scale" : "1x" - }, - { - "size" : "256x256", - "idiom" : "mac", - "filename" : "app_icon_512.png", - "scale" : "2x" - }, - { - "size" : "512x512", - "idiom" : "mac", - "filename" : "app_icon_512.png", - "scale" : "1x" - }, - { - "size" : "512x512", - "idiom" : "mac", - "filename" : "app_icon_1024.png", - "scale" : "2x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png deleted file mode 100755 index 82b6f9d..0000000 Binary files a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png and /dev/null differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png deleted file mode 100755 index 13b35eb..0000000 Binary files a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png and /dev/null differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png deleted file mode 100755 index 0a3f5fa..0000000 Binary files a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png and /dev/null differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png deleted file mode 100755 index bdb5722..0000000 Binary files a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png and /dev/null differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png deleted file mode 100755 index f083318..0000000 Binary files a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png and /dev/null differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png deleted file mode 100755 index 326c0e7..0000000 Binary files a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png and /dev/null differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png deleted file mode 100755 index 2f1632c..0000000 Binary files a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png and /dev/null differ diff --git a/macos/Runner/Base.lproj/MainMenu.xib b/macos/Runner/Base.lproj/MainMenu.xib deleted file mode 100755 index 80e867a..0000000 --- a/macos/Runner/Base.lproj/MainMenu.xib +++ /dev/null @@ -1,343 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/macos/Runner/Configs/AppInfo.xcconfig b/macos/Runner/Configs/AppInfo.xcconfig deleted file mode 100755 index 6a2fdc9..0000000 --- a/macos/Runner/Configs/AppInfo.xcconfig +++ /dev/null @@ -1,14 +0,0 @@ -// Application-level settings for the Runner target. -// -// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the -// future. If not, the values below would default to using the project name when this becomes a -// 'flutter create' template. - -// The application's name. By default this is also the title of the Flutter window. -PRODUCT_NAME = translator_lux - -// The application's bundle identifier -PRODUCT_BUNDLE_IDENTIFIER = com.lux.translator.translatorLux - -// The copyright displayed in application information -PRODUCT_COPYRIGHT = Copyright © 2024 com.lux.translator. All rights reserved. diff --git a/macos/Runner/Configs/Debug.xcconfig b/macos/Runner/Configs/Debug.xcconfig deleted file mode 100755 index 36b0fd9..0000000 --- a/macos/Runner/Configs/Debug.xcconfig +++ /dev/null @@ -1,2 +0,0 @@ -#include "../../Flutter/Flutter-Debug.xcconfig" -#include "Warnings.xcconfig" diff --git a/macos/Runner/Configs/Release.xcconfig b/macos/Runner/Configs/Release.xcconfig deleted file mode 100755 index dff4f49..0000000 --- a/macos/Runner/Configs/Release.xcconfig +++ /dev/null @@ -1,2 +0,0 @@ -#include "../../Flutter/Flutter-Release.xcconfig" -#include "Warnings.xcconfig" diff --git a/macos/Runner/Configs/Warnings.xcconfig b/macos/Runner/Configs/Warnings.xcconfig deleted file mode 100755 index 42bcbf4..0000000 --- a/macos/Runner/Configs/Warnings.xcconfig +++ /dev/null @@ -1,13 +0,0 @@ -WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings -GCC_WARN_UNDECLARED_SELECTOR = YES -CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES -CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE -CLANG_WARN__DUPLICATE_METHOD_MATCH = YES -CLANG_WARN_PRAGMA_PACK = YES -CLANG_WARN_STRICT_PROTOTYPES = YES -CLANG_WARN_COMMA = YES -GCC_WARN_STRICT_SELECTOR_MATCH = YES -CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES -CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES -GCC_WARN_SHADOW = YES -CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/macos/Runner/DebugProfile.entitlements b/macos/Runner/DebugProfile.entitlements deleted file mode 100755 index dddb8a3..0000000 --- a/macos/Runner/DebugProfile.entitlements +++ /dev/null @@ -1,12 +0,0 @@ - - - - - com.apple.security.app-sandbox - - com.apple.security.cs.allow-jit - - com.apple.security.network.server - - - diff --git a/macos/Runner/Info.plist b/macos/Runner/Info.plist deleted file mode 100755 index 4789daa..0000000 --- a/macos/Runner/Info.plist +++ /dev/null @@ -1,32 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIconFile - - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - APPL - CFBundleShortVersionString - $(FLUTTER_BUILD_NAME) - CFBundleVersion - $(FLUTTER_BUILD_NUMBER) - LSMinimumSystemVersion - $(MACOSX_DEPLOYMENT_TARGET) - NSHumanReadableCopyright - $(PRODUCT_COPYRIGHT) - NSMainNibFile - MainMenu - NSPrincipalClass - NSApplication - - diff --git a/macos/Runner/MainFlutterWindow.swift b/macos/Runner/MainFlutterWindow.swift deleted file mode 100755 index 3cc05eb..0000000 --- a/macos/Runner/MainFlutterWindow.swift +++ /dev/null @@ -1,15 +0,0 @@ -import Cocoa -import FlutterMacOS - -class MainFlutterWindow: NSWindow { - override func awakeFromNib() { - let flutterViewController = FlutterViewController() - let windowFrame = self.frame - self.contentViewController = flutterViewController - self.setFrame(windowFrame, display: true) - - RegisterGeneratedPlugins(registry: flutterViewController) - - super.awakeFromNib() - } -} diff --git a/macos/Runner/Release.entitlements b/macos/Runner/Release.entitlements deleted file mode 100755 index 852fa1a..0000000 --- a/macos/Runner/Release.entitlements +++ /dev/null @@ -1,8 +0,0 @@ - - - - - com.apple.security.app-sandbox - - - diff --git a/macos/RunnerTests/RunnerTests.swift b/macos/RunnerTests/RunnerTests.swift deleted file mode 100755 index 5418c9f..0000000 --- a/macos/RunnerTests/RunnerTests.swift +++ /dev/null @@ -1,12 +0,0 @@ -import FlutterMacOS -import Cocoa -import XCTest - -class RunnerTests: XCTestCase { - - func testExample() { - // If you add code to the Runner application, consider adding tests here. - // See https://developer.apple.com/documentation/xctest for more information about using XCTest. - } - -} diff --git a/pubspec.yaml b/pubspec.yaml index 8b0ddfb..239caf8 100755 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,4 +1,4 @@ -name: translator_lux +name: trans_lark description: "A new Flutter project." # The following line prevents the package from being accidentally published to # pub.dev using `flutter pub publish`. This is preferred for private packages. @@ -35,17 +35,61 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.6 - camera: ^0.11.0+1 get: ^4.6.6 flutter_tts: ^4.0.2 flutter_svg: ^2.0.10+1 - country_picker: ^2.0.26 - country_flags: ^3.0.0 +# country_picker: ^2.0.26 +# country_flags: ^3.0.0 translator: ^1.0.0 - video_player: ^2.9.0 - sqflite: ^2.3.3+1 - uuid: ^4.4.0 + +# sqflite: ^2.3.3+1 +# uuid: ^4.4.0 intl: ^0.19.0 + # camera: ^0.11.0+1 + # video_player: ^2.9.0 + image_picker: ^1.1.2 + flutter_text_detect_area: ^0.0.1+5 + flutter_easyloading: ^3.0.5 + + # 纯 Dart 编写的轻量级且速度极快的键值数据库 + hive: ^2.2.3 + hive_flutter: ^1.1.0 + + # 包含dart:collection样式的实用程序函数和类,使使用集合更容易。 + collection: ^1.18.0 + + # 对键盘可见性变化做出反应 + flutter_keyboard_visibility: ^6.0.0 + + custom_pop_up_menu: ^1.2.4 + + speech_to_text: ^6.6.2 + + # 获取设备当前语言 + devicelocale: ^0.7.1 + + # 权限处理 + permission_handler: ^11.3.1 + + # att + app_tracking_transparency: ^2.0.5 + + # webview + webview_flutter: ^4.8.0 + + wave: ^0.2.2 + + # 查询应用程序包信息 + package_info_plus: ^8.0.0 + + # 获取当前设备信息 + device_info_plus: ^10.1.0 + +flutter_launcher_icons: + android: "launcher_icon" + ios: true + image_path: "assets/launcher_icon/launcher_icon.png" + min_sdk_android: 23 # android min sdk min:16, default 21 dev_dependencies: flutter_test: @@ -57,6 +101,9 @@ dev_dependencies: # package. See that file for information about deactivating specific lint # rules and activating additional ones. flutter_lints: ^3.0.0 + hive_generator: ^2.0.1 + build_runner: ^2.4.11 + flutter_launcher_icons: ^0.13.1 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec @@ -71,8 +118,10 @@ flutter: # To add assets to your application, add an assets section, like this: assets: - - assets/images/png/ + - assets/launcher_icon/ + - assets/images/ - assets/svg/ + - assets/json/ # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware diff --git a/test/widget_test.dart b/test/widget_test.dart index 08b20c9..6c0b0be 100755 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -8,7 +8,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:translator_lux/main.dart'; +import 'package:trans_lark/main.dart'; void main() { testWidgets('Counter increments smoke test', (WidgetTester tester) async { diff --git a/web/favicon.png b/web/favicon.png deleted file mode 100755 index 8aaa46a..0000000 Binary files a/web/favicon.png and /dev/null differ diff --git a/web/icons/Icon-192.png b/web/icons/Icon-192.png deleted file mode 100755 index b749bfe..0000000 Binary files a/web/icons/Icon-192.png and /dev/null differ diff --git a/web/icons/Icon-512.png b/web/icons/Icon-512.png deleted file mode 100755 index 88cfd48..0000000 Binary files a/web/icons/Icon-512.png and /dev/null differ diff --git a/web/icons/Icon-maskable-192.png b/web/icons/Icon-maskable-192.png deleted file mode 100755 index eb9b4d7..0000000 Binary files a/web/icons/Icon-maskable-192.png and /dev/null differ diff --git a/web/icons/Icon-maskable-512.png b/web/icons/Icon-maskable-512.png deleted file mode 100755 index d69c566..0000000 Binary files a/web/icons/Icon-maskable-512.png and /dev/null differ diff --git a/web/index.html b/web/index.html deleted file mode 100755 index d20f9f4..0000000 --- a/web/index.html +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - - - - - - - - - - - - - - - translator_lux - - - - - - - - - - diff --git a/web/manifest.json b/web/manifest.json deleted file mode 100755 index 96589a2..0000000 --- a/web/manifest.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "translator_lux", - "short_name": "translator_lux", - "start_url": ".", - "display": "standalone", - "background_color": "#0175C2", - "theme_color": "#0175C2", - "description": "A new Flutter project.", - "orientation": "portrait-primary", - "prefer_related_applications": false, - "icons": [ - { - "src": "icons/Icon-192.png", - "sizes": "192x192", - "type": "image/png" - }, - { - "src": "icons/Icon-512.png", - "sizes": "512x512", - "type": "image/png" - }, - { - "src": "icons/Icon-maskable-192.png", - "sizes": "192x192", - "type": "image/png", - "purpose": "maskable" - }, - { - "src": "icons/Icon-maskable-512.png", - "sizes": "512x512", - "type": "image/png", - "purpose": "maskable" - } - ] -} diff --git a/windows/.gitignore b/windows/.gitignore deleted file mode 100755 index d492d0d..0000000 --- a/windows/.gitignore +++ /dev/null @@ -1,17 +0,0 @@ -flutter/ephemeral/ - -# Visual Studio user-specific files. -*.suo -*.user -*.userosscache -*.sln.docstates - -# Visual Studio build-related files. -x64/ -x86/ - -# Visual Studio cache files -# files ending in .cache can be ignored -*.[Cc]ache -# but keep track of directories ending in .cache -!*.[Cc]ache/ diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt deleted file mode 100755 index 12fb1a1..0000000 --- a/windows/CMakeLists.txt +++ /dev/null @@ -1,108 +0,0 @@ -# Project-level configuration. -cmake_minimum_required(VERSION 3.14) -project(translator_lux LANGUAGES CXX) - -# The name of the executable created for the application. Change this to change -# the on-disk name of your application. -set(BINARY_NAME "translator_lux") - -# Explicitly opt in to modern CMake behaviors to avoid warnings with recent -# versions of CMake. -cmake_policy(VERSION 3.14...3.25) - -# Define build configuration option. -get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) -if(IS_MULTICONFIG) - set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" - CACHE STRING "" FORCE) -else() - if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) - set(CMAKE_BUILD_TYPE "Debug" CACHE - STRING "Flutter build mode" FORCE) - set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS - "Debug" "Profile" "Release") - endif() -endif() -# Define settings for the Profile build mode. -set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") -set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") -set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") -set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") - -# Use Unicode for all projects. -add_definitions(-DUNICODE -D_UNICODE) - -# Compilation settings that should be applied to most targets. -# -# Be cautious about adding new options here, as plugins use this function by -# default. In most cases, you should add new options to specific targets instead -# of modifying this function. -function(APPLY_STANDARD_SETTINGS TARGET) - target_compile_features(${TARGET} PUBLIC cxx_std_17) - target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") - target_compile_options(${TARGET} PRIVATE /EHsc) - target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") - target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") -endfunction() - -# Flutter library and tool build rules. -set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") -add_subdirectory(${FLUTTER_MANAGED_DIR}) - -# Application build; see runner/CMakeLists.txt. -add_subdirectory("runner") - - -# Generated plugin build rules, which manage building the plugins and adding -# them to the application. -include(flutter/generated_plugins.cmake) - - -# === Installation === -# Support files are copied into place next to the executable, so that it can -# run in place. This is done instead of making a separate bundle (as on Linux) -# so that building and running from within Visual Studio will work. -set(BUILD_BUNDLE_DIR "$") -# Make the "install" step default, as it's required to run. -set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) -if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) - set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) -endif() - -set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") -set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") - -install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" - COMPONENT Runtime) - -install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" - COMPONENT Runtime) - -install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" - COMPONENT Runtime) - -if(PLUGIN_BUNDLED_LIBRARIES) - install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" - DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" - COMPONENT Runtime) -endif() - -# Copy the native assets provided by the build.dart from all packages. -set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") -install(DIRECTORY "${NATIVE_ASSETS_DIR}" - DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" - COMPONENT Runtime) - -# Fully re-copy the assets directory on each build to avoid having stale files -# from a previous install. -set(FLUTTER_ASSET_DIR_NAME "flutter_assets") -install(CODE " - file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") - " COMPONENT Runtime) -install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" - DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) - -# Install the AOT library on non-Debug builds only. -install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" - CONFIGURATIONS Profile;Release - COMPONENT Runtime) diff --git a/windows/flutter/CMakeLists.txt b/windows/flutter/CMakeLists.txt deleted file mode 100755 index 903f489..0000000 --- a/windows/flutter/CMakeLists.txt +++ /dev/null @@ -1,109 +0,0 @@ -# This file controls Flutter-level build steps. It should not be edited. -cmake_minimum_required(VERSION 3.14) - -set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") - -# Configuration provided via flutter tool. -include(${EPHEMERAL_DIR}/generated_config.cmake) - -# TODO: Move the rest of this into files in ephemeral. See -# https://github.com/flutter/flutter/issues/57146. -set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") - -# Set fallback configurations for older versions of the flutter tool. -if (NOT DEFINED FLUTTER_TARGET_PLATFORM) - set(FLUTTER_TARGET_PLATFORM "windows-x64") -endif() - -# === Flutter Library === -set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") - -# Published to parent scope for install step. -set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) -set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) -set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) -set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) - -list(APPEND FLUTTER_LIBRARY_HEADERS - "flutter_export.h" - "flutter_windows.h" - "flutter_messenger.h" - "flutter_plugin_registrar.h" - "flutter_texture_registrar.h" -) -list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") -add_library(flutter INTERFACE) -target_include_directories(flutter INTERFACE - "${EPHEMERAL_DIR}" -) -target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") -add_dependencies(flutter flutter_assemble) - -# === Wrapper === -list(APPEND CPP_WRAPPER_SOURCES_CORE - "core_implementations.cc" - "standard_codec.cc" -) -list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") -list(APPEND CPP_WRAPPER_SOURCES_PLUGIN - "plugin_registrar.cc" -) -list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") -list(APPEND CPP_WRAPPER_SOURCES_APP - "flutter_engine.cc" - "flutter_view_controller.cc" -) -list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") - -# Wrapper sources needed for a plugin. -add_library(flutter_wrapper_plugin STATIC - ${CPP_WRAPPER_SOURCES_CORE} - ${CPP_WRAPPER_SOURCES_PLUGIN} -) -apply_standard_settings(flutter_wrapper_plugin) -set_target_properties(flutter_wrapper_plugin PROPERTIES - POSITION_INDEPENDENT_CODE ON) -set_target_properties(flutter_wrapper_plugin PROPERTIES - CXX_VISIBILITY_PRESET hidden) -target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) -target_include_directories(flutter_wrapper_plugin PUBLIC - "${WRAPPER_ROOT}/include" -) -add_dependencies(flutter_wrapper_plugin flutter_assemble) - -# Wrapper sources needed for the runner. -add_library(flutter_wrapper_app STATIC - ${CPP_WRAPPER_SOURCES_CORE} - ${CPP_WRAPPER_SOURCES_APP} -) -apply_standard_settings(flutter_wrapper_app) -target_link_libraries(flutter_wrapper_app PUBLIC flutter) -target_include_directories(flutter_wrapper_app PUBLIC - "${WRAPPER_ROOT}/include" -) -add_dependencies(flutter_wrapper_app flutter_assemble) - -# === Flutter tool backend === -# _phony_ is a non-existent file to force this command to run every time, -# since currently there's no way to get a full input/output list from the -# flutter tool. -set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") -set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) -add_custom_command( - OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} - ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} - ${CPP_WRAPPER_SOURCES_APP} - ${PHONY_OUTPUT} - COMMAND ${CMAKE_COMMAND} -E env - ${FLUTTER_TOOL_ENVIRONMENT} - "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" - ${FLUTTER_TARGET_PLATFORM} $ - VERBATIM -) -add_custom_target(flutter_assemble DEPENDS - "${FLUTTER_LIBRARY}" - ${FLUTTER_LIBRARY_HEADERS} - ${CPP_WRAPPER_SOURCES_CORE} - ${CPP_WRAPPER_SOURCES_PLUGIN} - ${CPP_WRAPPER_SOURCES_APP} -) diff --git a/windows/runner/CMakeLists.txt b/windows/runner/CMakeLists.txt deleted file mode 100755 index 394917c..0000000 --- a/windows/runner/CMakeLists.txt +++ /dev/null @@ -1,40 +0,0 @@ -cmake_minimum_required(VERSION 3.14) -project(runner LANGUAGES CXX) - -# Define the application target. To change its name, change BINARY_NAME in the -# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer -# work. -# -# Any new source files that you add to the application should be added here. -add_executable(${BINARY_NAME} WIN32 - "flutter_window.cpp" - "main.cpp" - "utils.cpp" - "win32_window.cpp" - "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" - "Runner.rc" - "runner.exe.manifest" -) - -# Apply the standard set of build settings. This can be removed for applications -# that need different build settings. -apply_standard_settings(${BINARY_NAME}) - -# Add preprocessor definitions for the build version. -target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") -target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") -target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") -target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") -target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") - -# Disable Windows macros that collide with C++ standard library functions. -target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") - -# Add dependency libraries and include directories. Add any application-specific -# dependencies here. -target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) -target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") -target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") - -# Run the Flutter tool portions of the build. This must not be removed. -add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/windows/runner/Runner.rc b/windows/runner/Runner.rc deleted file mode 100755 index ddcf922..0000000 --- a/windows/runner/Runner.rc +++ /dev/null @@ -1,121 +0,0 @@ -// Microsoft Visual C++ generated resource script. -// -#pragma code_page(65001) -#include "resource.h" - -#define APSTUDIO_READONLY_SYMBOLS -///////////////////////////////////////////////////////////////////////////// -// -// Generated from the TEXTINCLUDE 2 resource. -// -#include "winres.h" - -///////////////////////////////////////////////////////////////////////////// -#undef APSTUDIO_READONLY_SYMBOLS - -///////////////////////////////////////////////////////////////////////////// -// English (United States) resources - -#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) -LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US - -#ifdef APSTUDIO_INVOKED -///////////////////////////////////////////////////////////////////////////// -// -// TEXTINCLUDE -// - -1 TEXTINCLUDE -BEGIN - "resource.h\0" -END - -2 TEXTINCLUDE -BEGIN - "#include ""winres.h""\r\n" - "\0" -END - -3 TEXTINCLUDE -BEGIN - "\r\n" - "\0" -END - -#endif // APSTUDIO_INVOKED - - -///////////////////////////////////////////////////////////////////////////// -// -// Icon -// - -// Icon with lowest ID value placed first to ensure application icon -// remains consistent on all systems. -IDI_APP_ICON ICON "resources\\app_icon.ico" - - -///////////////////////////////////////////////////////////////////////////// -// -// Version -// - -#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) -#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD -#else -#define VERSION_AS_NUMBER 1,0,0,0 -#endif - -#if defined(FLUTTER_VERSION) -#define VERSION_AS_STRING FLUTTER_VERSION -#else -#define VERSION_AS_STRING "1.0.0" -#endif - -VS_VERSION_INFO VERSIONINFO - FILEVERSION VERSION_AS_NUMBER - PRODUCTVERSION VERSION_AS_NUMBER - FILEFLAGSMASK VS_FFI_FILEFLAGSMASK -#ifdef _DEBUG - FILEFLAGS VS_FF_DEBUG -#else - FILEFLAGS 0x0L -#endif - FILEOS VOS__WINDOWS32 - FILETYPE VFT_APP - FILESUBTYPE 0x0L -BEGIN - BLOCK "StringFileInfo" - BEGIN - BLOCK "040904e4" - BEGIN - VALUE "CompanyName", "com.lux.translator" "\0" - VALUE "FileDescription", "translator_lux" "\0" - VALUE "FileVersion", VERSION_AS_STRING "\0" - VALUE "InternalName", "translator_lux" "\0" - VALUE "LegalCopyright", "Copyright (C) 2024 com.lux.translator. All rights reserved." "\0" - VALUE "OriginalFilename", "translator_lux.exe" "\0" - VALUE "ProductName", "translator_lux" "\0" - VALUE "ProductVersion", VERSION_AS_STRING "\0" - END - END - BLOCK "VarFileInfo" - BEGIN - VALUE "Translation", 0x409, 1252 - END -END - -#endif // English (United States) resources -///////////////////////////////////////////////////////////////////////////// - - - -#ifndef APSTUDIO_INVOKED -///////////////////////////////////////////////////////////////////////////// -// -// Generated from the TEXTINCLUDE 3 resource. -// - - -///////////////////////////////////////////////////////////////////////////// -#endif // not APSTUDIO_INVOKED diff --git a/windows/runner/flutter_window.cpp b/windows/runner/flutter_window.cpp deleted file mode 100755 index 955ee30..0000000 --- a/windows/runner/flutter_window.cpp +++ /dev/null @@ -1,71 +0,0 @@ -#include "flutter_window.h" - -#include - -#include "flutter/generated_plugin_registrant.h" - -FlutterWindow::FlutterWindow(const flutter::DartProject& project) - : project_(project) {} - -FlutterWindow::~FlutterWindow() {} - -bool FlutterWindow::OnCreate() { - if (!Win32Window::OnCreate()) { - return false; - } - - RECT frame = GetClientArea(); - - // The size here must match the window dimensions to avoid unnecessary surface - // creation / destruction in the startup path. - flutter_controller_ = std::make_unique( - frame.right - frame.left, frame.bottom - frame.top, project_); - // Ensure that basic setup of the controller was successful. - if (!flutter_controller_->engine() || !flutter_controller_->view()) { - return false; - } - RegisterPlugins(flutter_controller_->engine()); - SetChildContent(flutter_controller_->view()->GetNativeWindow()); - - flutter_controller_->engine()->SetNextFrameCallback([&]() { - this->Show(); - }); - - // Flutter can complete the first frame before the "show window" callback is - // registered. The following call ensures a frame is pending to ensure the - // window is shown. It is a no-op if the first frame hasn't completed yet. - flutter_controller_->ForceRedraw(); - - return true; -} - -void FlutterWindow::OnDestroy() { - if (flutter_controller_) { - flutter_controller_ = nullptr; - } - - Win32Window::OnDestroy(); -} - -LRESULT -FlutterWindow::MessageHandler(HWND hwnd, UINT const message, - WPARAM const wparam, - LPARAM const lparam) noexcept { - // Give Flutter, including plugins, an opportunity to handle window messages. - if (flutter_controller_) { - std::optional result = - flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, - lparam); - if (result) { - return *result; - } - } - - switch (message) { - case WM_FONTCHANGE: - flutter_controller_->engine()->ReloadSystemFonts(); - break; - } - - return Win32Window::MessageHandler(hwnd, message, wparam, lparam); -} diff --git a/windows/runner/flutter_window.h b/windows/runner/flutter_window.h deleted file mode 100755 index 6da0652..0000000 --- a/windows/runner/flutter_window.h +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef RUNNER_FLUTTER_WINDOW_H_ -#define RUNNER_FLUTTER_WINDOW_H_ - -#include -#include - -#include - -#include "win32_window.h" - -// A window that does nothing but host a Flutter view. -class FlutterWindow : public Win32Window { - public: - // Creates a new FlutterWindow hosting a Flutter view running |project|. - explicit FlutterWindow(const flutter::DartProject& project); - virtual ~FlutterWindow(); - - protected: - // Win32Window: - bool OnCreate() override; - void OnDestroy() override; - LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, - LPARAM const lparam) noexcept override; - - private: - // The project to run. - flutter::DartProject project_; - - // The Flutter instance hosted by this window. - std::unique_ptr flutter_controller_; -}; - -#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/windows/runner/main.cpp b/windows/runner/main.cpp deleted file mode 100755 index 8ae3121..0000000 --- a/windows/runner/main.cpp +++ /dev/null @@ -1,43 +0,0 @@ -#include -#include -#include - -#include "flutter_window.h" -#include "utils.h" - -int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, - _In_ wchar_t *command_line, _In_ int show_command) { - // Attach to console when present (e.g., 'flutter run') or create a - // new console when running with a debugger. - if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { - CreateAndAttachConsole(); - } - - // Initialize COM, so that it is available for use in the library and/or - // plugins. - ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); - - flutter::DartProject project(L"data"); - - std::vector command_line_arguments = - GetCommandLineArguments(); - - project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); - - FlutterWindow window(project); - Win32Window::Point origin(10, 10); - Win32Window::Size size(1280, 720); - if (!window.Create(L"translator_lux", origin, size)) { - return EXIT_FAILURE; - } - window.SetQuitOnClose(true); - - ::MSG msg; - while (::GetMessage(&msg, nullptr, 0, 0)) { - ::TranslateMessage(&msg); - ::DispatchMessage(&msg); - } - - ::CoUninitialize(); - return EXIT_SUCCESS; -} diff --git a/windows/runner/resource.h b/windows/runner/resource.h deleted file mode 100755 index 66a65d1..0000000 --- a/windows/runner/resource.h +++ /dev/null @@ -1,16 +0,0 @@ -//{{NO_DEPENDENCIES}} -// Microsoft Visual C++ generated include file. -// Used by Runner.rc -// -#define IDI_APP_ICON 101 - -// Next default values for new objects -// -#ifdef APSTUDIO_INVOKED -#ifndef APSTUDIO_READONLY_SYMBOLS -#define _APS_NEXT_RESOURCE_VALUE 102 -#define _APS_NEXT_COMMAND_VALUE 40001 -#define _APS_NEXT_CONTROL_VALUE 1001 -#define _APS_NEXT_SYMED_VALUE 101 -#endif -#endif diff --git a/windows/runner/resources/app_icon.ico b/windows/runner/resources/app_icon.ico deleted file mode 100755 index c04e20c..0000000 Binary files a/windows/runner/resources/app_icon.ico and /dev/null differ diff --git a/windows/runner/runner.exe.manifest b/windows/runner/runner.exe.manifest deleted file mode 100755 index a42ea76..0000000 --- a/windows/runner/runner.exe.manifest +++ /dev/null @@ -1,20 +0,0 @@ - - - - - PerMonitorV2 - - - - - - - - - - - - - - - diff --git a/windows/runner/utils.cpp b/windows/runner/utils.cpp deleted file mode 100755 index b2b0873..0000000 --- a/windows/runner/utils.cpp +++ /dev/null @@ -1,65 +0,0 @@ -#include "utils.h" - -#include -#include -#include -#include - -#include - -void CreateAndAttachConsole() { - if (::AllocConsole()) { - FILE *unused; - if (freopen_s(&unused, "CONOUT$", "w", stdout)) { - _dup2(_fileno(stdout), 1); - } - if (freopen_s(&unused, "CONOUT$", "w", stderr)) { - _dup2(_fileno(stdout), 2); - } - std::ios::sync_with_stdio(); - FlutterDesktopResyncOutputStreams(); - } -} - -std::vector GetCommandLineArguments() { - // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. - int argc; - wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); - if (argv == nullptr) { - return std::vector(); - } - - std::vector command_line_arguments; - - // Skip the first argument as it's the binary name. - for (int i = 1; i < argc; i++) { - command_line_arguments.push_back(Utf8FromUtf16(argv[i])); - } - - ::LocalFree(argv); - - return command_line_arguments; -} - -std::string Utf8FromUtf16(const wchar_t* utf16_string) { - if (utf16_string == nullptr) { - return std::string(); - } - int target_length = ::WideCharToMultiByte( - CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, - -1, nullptr, 0, nullptr, nullptr) - -1; // remove the trailing null character - int input_length = (int)wcslen(utf16_string); - std::string utf8_string; - if (target_length <= 0 || target_length > utf8_string.max_size()) { - return utf8_string; - } - utf8_string.resize(target_length); - int converted_length = ::WideCharToMultiByte( - CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, - input_length, utf8_string.data(), target_length, nullptr, nullptr); - if (converted_length == 0) { - return std::string(); - } - return utf8_string; -} diff --git a/windows/runner/utils.h b/windows/runner/utils.h deleted file mode 100755 index 3879d54..0000000 --- a/windows/runner/utils.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef RUNNER_UTILS_H_ -#define RUNNER_UTILS_H_ - -#include -#include - -// Creates a console for the process, and redirects stdout and stderr to -// it for both the runner and the Flutter library. -void CreateAndAttachConsole(); - -// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string -// encoded in UTF-8. Returns an empty std::string on failure. -std::string Utf8FromUtf16(const wchar_t* utf16_string); - -// Gets the command line arguments passed in as a std::vector, -// encoded in UTF-8. Returns an empty std::vector on failure. -std::vector GetCommandLineArguments(); - -#endif // RUNNER_UTILS_H_ diff --git a/windows/runner/win32_window.cpp b/windows/runner/win32_window.cpp deleted file mode 100755 index 60608d0..0000000 --- a/windows/runner/win32_window.cpp +++ /dev/null @@ -1,288 +0,0 @@ -#include "win32_window.h" - -#include -#include - -#include "resource.h" - -namespace { - -/// Window attribute that enables dark mode window decorations. -/// -/// Redefined in case the developer's machine has a Windows SDK older than -/// version 10.0.22000.0. -/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute -#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE -#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 -#endif - -constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; - -/// Registry key for app theme preference. -/// -/// A value of 0 indicates apps should use dark mode. A non-zero or missing -/// value indicates apps should use light mode. -constexpr const wchar_t kGetPreferredBrightnessRegKey[] = - L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; -constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; - -// The number of Win32Window objects that currently exist. -static int g_active_window_count = 0; - -using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); - -// Scale helper to convert logical scaler values to physical using passed in -// scale factor -int Scale(int source, double scale_factor) { - return static_cast(source * scale_factor); -} - -// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. -// This API is only needed for PerMonitor V1 awareness mode. -void EnableFullDpiSupportIfAvailable(HWND hwnd) { - HMODULE user32_module = LoadLibraryA("User32.dll"); - if (!user32_module) { - return; - } - auto enable_non_client_dpi_scaling = - reinterpret_cast( - GetProcAddress(user32_module, "EnableNonClientDpiScaling")); - if (enable_non_client_dpi_scaling != nullptr) { - enable_non_client_dpi_scaling(hwnd); - } - FreeLibrary(user32_module); -} - -} // namespace - -// Manages the Win32Window's window class registration. -class WindowClassRegistrar { - public: - ~WindowClassRegistrar() = default; - - // Returns the singleton registrar instance. - static WindowClassRegistrar* GetInstance() { - if (!instance_) { - instance_ = new WindowClassRegistrar(); - } - return instance_; - } - - // Returns the name of the window class, registering the class if it hasn't - // previously been registered. - const wchar_t* GetWindowClass(); - - // Unregisters the window class. Should only be called if there are no - // instances of the window. - void UnregisterWindowClass(); - - private: - WindowClassRegistrar() = default; - - static WindowClassRegistrar* instance_; - - bool class_registered_ = false; -}; - -WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; - -const wchar_t* WindowClassRegistrar::GetWindowClass() { - if (!class_registered_) { - WNDCLASS window_class{}; - window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); - window_class.lpszClassName = kWindowClassName; - window_class.style = CS_HREDRAW | CS_VREDRAW; - window_class.cbClsExtra = 0; - window_class.cbWndExtra = 0; - window_class.hInstance = GetModuleHandle(nullptr); - window_class.hIcon = - LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); - window_class.hbrBackground = 0; - window_class.lpszMenuName = nullptr; - window_class.lpfnWndProc = Win32Window::WndProc; - RegisterClass(&window_class); - class_registered_ = true; - } - return kWindowClassName; -} - -void WindowClassRegistrar::UnregisterWindowClass() { - UnregisterClass(kWindowClassName, nullptr); - class_registered_ = false; -} - -Win32Window::Win32Window() { - ++g_active_window_count; -} - -Win32Window::~Win32Window() { - --g_active_window_count; - Destroy(); -} - -bool Win32Window::Create(const std::wstring& title, - const Point& origin, - const Size& size) { - Destroy(); - - const wchar_t* window_class = - WindowClassRegistrar::GetInstance()->GetWindowClass(); - - const POINT target_point = {static_cast(origin.x), - static_cast(origin.y)}; - HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); - UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); - double scale_factor = dpi / 96.0; - - HWND window = CreateWindow( - window_class, title.c_str(), WS_OVERLAPPEDWINDOW, - Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), - Scale(size.width, scale_factor), Scale(size.height, scale_factor), - nullptr, nullptr, GetModuleHandle(nullptr), this); - - if (!window) { - return false; - } - - UpdateTheme(window); - - return OnCreate(); -} - -bool Win32Window::Show() { - return ShowWindow(window_handle_, SW_SHOWNORMAL); -} - -// static -LRESULT CALLBACK Win32Window::WndProc(HWND const window, - UINT const message, - WPARAM const wparam, - LPARAM const lparam) noexcept { - if (message == WM_NCCREATE) { - auto window_struct = reinterpret_cast(lparam); - SetWindowLongPtr(window, GWLP_USERDATA, - reinterpret_cast(window_struct->lpCreateParams)); - - auto that = static_cast(window_struct->lpCreateParams); - EnableFullDpiSupportIfAvailable(window); - that->window_handle_ = window; - } else if (Win32Window* that = GetThisFromHandle(window)) { - return that->MessageHandler(window, message, wparam, lparam); - } - - return DefWindowProc(window, message, wparam, lparam); -} - -LRESULT -Win32Window::MessageHandler(HWND hwnd, - UINT const message, - WPARAM const wparam, - LPARAM const lparam) noexcept { - switch (message) { - case WM_DESTROY: - window_handle_ = nullptr; - Destroy(); - if (quit_on_close_) { - PostQuitMessage(0); - } - return 0; - - case WM_DPICHANGED: { - auto newRectSize = reinterpret_cast(lparam); - LONG newWidth = newRectSize->right - newRectSize->left; - LONG newHeight = newRectSize->bottom - newRectSize->top; - - SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, - newHeight, SWP_NOZORDER | SWP_NOACTIVATE); - - return 0; - } - case WM_SIZE: { - RECT rect = GetClientArea(); - if (child_content_ != nullptr) { - // Size and position the child window. - MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, - rect.bottom - rect.top, TRUE); - } - return 0; - } - - case WM_ACTIVATE: - if (child_content_ != nullptr) { - SetFocus(child_content_); - } - return 0; - - case WM_DWMCOLORIZATIONCOLORCHANGED: - UpdateTheme(hwnd); - return 0; - } - - return DefWindowProc(window_handle_, message, wparam, lparam); -} - -void Win32Window::Destroy() { - OnDestroy(); - - if (window_handle_) { - DestroyWindow(window_handle_); - window_handle_ = nullptr; - } - if (g_active_window_count == 0) { - WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); - } -} - -Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { - return reinterpret_cast( - GetWindowLongPtr(window, GWLP_USERDATA)); -} - -void Win32Window::SetChildContent(HWND content) { - child_content_ = content; - SetParent(content, window_handle_); - RECT frame = GetClientArea(); - - MoveWindow(content, frame.left, frame.top, frame.right - frame.left, - frame.bottom - frame.top, true); - - SetFocus(child_content_); -} - -RECT Win32Window::GetClientArea() { - RECT frame; - GetClientRect(window_handle_, &frame); - return frame; -} - -HWND Win32Window::GetHandle() { - return window_handle_; -} - -void Win32Window::SetQuitOnClose(bool quit_on_close) { - quit_on_close_ = quit_on_close; -} - -bool Win32Window::OnCreate() { - // No-op; provided for subclasses. - return true; -} - -void Win32Window::OnDestroy() { - // No-op; provided for subclasses. -} - -void Win32Window::UpdateTheme(HWND const window) { - DWORD light_mode; - DWORD light_mode_size = sizeof(light_mode); - LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, - kGetPreferredBrightnessRegValue, - RRF_RT_REG_DWORD, nullptr, &light_mode, - &light_mode_size); - - if (result == ERROR_SUCCESS) { - BOOL enable_dark_mode = light_mode == 0; - DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, - &enable_dark_mode, sizeof(enable_dark_mode)); - } -} diff --git a/windows/runner/win32_window.h b/windows/runner/win32_window.h deleted file mode 100755 index e901dde..0000000 --- a/windows/runner/win32_window.h +++ /dev/null @@ -1,102 +0,0 @@ -#ifndef RUNNER_WIN32_WINDOW_H_ -#define RUNNER_WIN32_WINDOW_H_ - -#include - -#include -#include -#include - -// A class abstraction for a high DPI-aware Win32 Window. Intended to be -// inherited from by classes that wish to specialize with custom -// rendering and input handling -class Win32Window { - public: - struct Point { - unsigned int x; - unsigned int y; - Point(unsigned int x, unsigned int y) : x(x), y(y) {} - }; - - struct Size { - unsigned int width; - unsigned int height; - Size(unsigned int width, unsigned int height) - : width(width), height(height) {} - }; - - Win32Window(); - virtual ~Win32Window(); - - // Creates a win32 window with |title| that is positioned and sized using - // |origin| and |size|. New windows are created on the default monitor. Window - // sizes are specified to the OS in physical pixels, hence to ensure a - // consistent size this function will scale the inputted width and height as - // as appropriate for the default monitor. The window is invisible until - // |Show| is called. Returns true if the window was created successfully. - bool Create(const std::wstring& title, const Point& origin, const Size& size); - - // Show the current window. Returns true if the window was successfully shown. - bool Show(); - - // Release OS resources associated with window. - void Destroy(); - - // Inserts |content| into the window tree. - void SetChildContent(HWND content); - - // Returns the backing Window handle to enable clients to set icon and other - // window properties. Returns nullptr if the window has been destroyed. - HWND GetHandle(); - - // If true, closing this window will quit the application. - void SetQuitOnClose(bool quit_on_close); - - // Return a RECT representing the bounds of the current client area. - RECT GetClientArea(); - - protected: - // Processes and route salient window messages for mouse handling, - // size change and DPI. Delegates handling of these to member overloads that - // inheriting classes can handle. - virtual LRESULT MessageHandler(HWND window, - UINT const message, - WPARAM const wparam, - LPARAM const lparam) noexcept; - - // Called when CreateAndShow is called, allowing subclass window-related - // setup. Subclasses should return false if setup fails. - virtual bool OnCreate(); - - // Called when Destroy is called. - virtual void OnDestroy(); - - private: - friend class WindowClassRegistrar; - - // OS callback called by message pump. Handles the WM_NCCREATE message which - // is passed when the non-client area is being created and enables automatic - // non-client DPI scaling so that the non-client area automatically - // responds to changes in DPI. All other messages are handled by - // MessageHandler. - static LRESULT CALLBACK WndProc(HWND const window, - UINT const message, - WPARAM const wparam, - LPARAM const lparam) noexcept; - - // Retrieves a class instance pointer for |window| - static Win32Window* GetThisFromHandle(HWND const window) noexcept; - - // Update the window frame's theme to match the system theme. - static void UpdateTheme(HWND const window); - - bool quit_on_close_ = false; - - // window handle for top level window. - HWND window_handle_ = nullptr; - - // window handle for hosted content. - HWND child_content_ = nullptr; -}; - -#endif // RUNNER_WIN32_WINDOW_H_