main
alatte 2024-01-10 23:39:33 +01:00
parent 73a518733d
commit 7909116d8e
33 changed files with 748 additions and 348 deletions

View File

@ -15,21 +15,6 @@ migration:
- platform: root - platform: root
create_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 create_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9
base_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 base_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9
- platform: android
create_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9
base_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9
- platform: ios
create_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9
base_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9
- platform: linux
create_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9
base_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9
- platform: macos
create_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9
base_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9
- platform: web
create_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9
base_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9
- platform: windows - platform: windows
create_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 create_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9
base_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 base_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9

14
.vscode/launch.json vendored
View File

@ -23,20 +23,6 @@
"request": "launch", "request": "launch",
"type": "dart", "type": "dart",
"flutterMode": "release" "flutterMode": "release"
},
{
"name": "Flutter Web (Chrome)",
"request": "launch",
"type": "dart",
"program": "lib/main.dart",
"args": [
"--web-port", "8080"
],
"flutterMode": "debug",
"disableWebSecurity": true,
"webRenderer": "auto",
"deviceName": "Chrome",
"args2": []
} }
] ]
} }

View File

@ -1,22 +1,12 @@
# optictext # optictext
Ein Tool, das dem Benutzer die Option gibt, OCR auf Bildern anzuwenden oder Text in Bildern zu übersetzen und den übersetzten Text wieder auf das Bild zu setzen. Leider habe ich es nicht hinbekommen meine requests zu mocken.
Deshalb habe ich nur UI Elemente getestet.
Screen1: Wahl zwischen Image tools und Audio Tools(falls ich in der Zukunft weiter daran Arbeiten werde). Falls ich den Code vom Server noch nachreichen soll, kann ich dies machen.
Image tools screen: DoOCR und ImageTranslate.
DoOCR: User kann ein Bild hochladen und OCR darauf anwenden.
ImageTranslate: User kann ein Bild hochladen welches dann übersetzt wird.
Den user die Sprache wählen zu lassen und dann in eine gewählte sprache zu übersetzen ist leicht gemacht,
allerding ist es mir wichtig, das die spracherkennung auch automatisch funktioniert.
Im ersten schritt war es wichtig diese funktionalität zu haben.
Aktuell läuft das auf einem vps, dafür ist der http request da.
Wenn möglich implementiere ich die langauge classfication in dart.
aktuell teste ich auf einem android phone, es soll aber am ende auch auf IOS laufen.
Flutter build apk wirft eine Fehlermeldung aber funktioniert. Bei der Präsentation hatte
ich die release version dabei, die nicht mit "debug" gekennzeichnet war.
## Getting Started ## Getting Started

View File

@ -45,7 +45,7 @@ android {
applicationId "com.example.optictext" applicationId "com.example.optictext"
// You can update the following values to match your application needs. // 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. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
minSdkVersion flutter.minSdkVersion minSdkVersion 21
targetSdkVersion flutter.targetSdkVersion targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger() versionCode flutterVersionCode.toInteger()
versionName flutterVersionName versionName flutterVersionName

View File

@ -1,4 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<application <application
android:label="optictext" android:label="optictext"
android:name="${applicationName}" android:name="${applicationName}"

View File

@ -22,6 +22,7 @@ rootProject.buildDir = '../build'
subprojects { subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}" project.buildDir = "${rootProject.buildDir}/${project.name}"
} }
subprojects { subprojects {
project.evaluationDependsOn(':app') project.evaluationDependsOn(':app')
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 238 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

BIN
assets/schild.png 100644

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,11 +1,9 @@
{ {
"files": [ "files": [
"eng.traineddata", "eng.traineddata",
"ara.traineddata",
"rus.traineddata", "rus.traineddata",
"deu.traineddata", "deu.traineddata",
"jpn.traineddata", "jpn.traineddata",
"ell.traineddata", "fra.traineddata"
"chi_sim.traineddata"
] ]
} }

View File

@ -1,51 +0,0 @@
// ignore_for_file: library_private_types_in_public_api
import 'package:flutter/material.dart';
//might be useless. keep for now
class BottomBar extends StatefulWidget {
const BottomBar({Key? key}) : super(key: key);
@override
_BottomBarState createState() => _BottomBarState();
}
class _BottomBarState extends State<BottomBar> {
int _selectedIndex = 0;
// ignore: unused_field
static const List<Widget> _widgetOptions = <Widget>[
Text('Home Page'),
Text('todo'),
Text('pro'),
];
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
}
@override
Widget build(BuildContext context) {
return BottomNavigationBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(Icons.business),
label: 'todo',
),
BottomNavigationBarItem(
icon: Icon(Icons.school),
label: 'pro',
),
],
currentIndex: _selectedIndex,
selectedItemColor: Colors.amber[800],
onTap: _onItemTapped,
);
}
}

View File

@ -0,0 +1,48 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:image_gallery_saver/image_gallery_saver.dart';
import 'dart:typed_data';
class FileUtils {
static void saveTranslatedImage(Uint8List translatedImage) async {
if (Platform.isAndroid) {
final result = await ImageGallerySaver.saveImage(translatedImage);
if (result['isSuccess']) {
Fluttertoast.showToast(
msg: "Image saved successfully!",
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.BOTTOM,
timeInSecForIosWeb: 1,
backgroundColor: Colors.green,
textColor: Colors.white,
fontSize: 16.0,
);
} else {
Fluttertoast.showToast(
msg: "Failed to save image",
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.BOTTOM,
timeInSecForIosWeb: 1,
backgroundColor: Colors.red,
textColor: Colors.white,
fontSize: 16.0,
);
}
} else {
//vorerst... finde noch was besseres
final file = File('C:/Users/hfggvcb/Desktop/image.png');
await file.writeAsBytes(translatedImage);
Fluttertoast.showToast(
msg: "Image saved successfully on Windows!",
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.BOTTOM,
timeInSecForIosWeb: 1,
backgroundColor: Colors.green,
textColor: Colors.white,
fontSize: 16.0,
);
}
}
}

View File

@ -1,14 +1,21 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:io';
import 'package:cpd_app/language_utils.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:http/http.dart';
import 'package:http_parser/http_parser.dart'; import 'package:http_parser/http_parser.dart';
import 'package:flutter/material.dart';
import 'package:connectivity/connectivity.dart';
import 'dart:async';
class HttpUtils { class HttpUtils {
var client = http.Client(); var client = http.Client();
Future<String> checkLang(Uint8List imageBytes, String imageName) async {
var client = http.Client();
var postUri = Uri.parse("http://130.61.88.150/upload"); Future<String> checkLang(Uint8List imageBytes, String imageName) async {
var postUri = Uri.parse("http://130.61.27.201/upload");
http.MultipartRequest request = http.MultipartRequest("POST", postUri); http.MultipartRequest request = http.MultipartRequest("POST", postUri);
http.MultipartFile multipartFile = http.MultipartFile.fromBytes( http.MultipartFile multipartFile = http.MultipartFile.fromBytes(
@ -19,6 +26,7 @@ class HttpUtils {
); );
request.files.add(multipartFile); request.files.add(multipartFile);
http.StreamedResponse response = await client.send(request); http.StreamedResponse response = await client.send(request);
http.Response finalResponse = await http.Response.fromStream(response); http.Response finalResponse = await http.Response.fromStream(response);
var lang = ""; var lang = "";
@ -30,4 +38,151 @@ class HttpUtils {
} }
return lang; return lang;
} }
Future<String> extractTextKnownSource(
Uint8List imageBytes, String sourceLang) async {
var postUri = Uri.parse("http://130.61.27.201/extractWithKnownSource");
http.MultipartRequest request = http.MultipartRequest("POST", postUri);
http.MultipartFile multipartFile = http.MultipartFile.fromBytes(
'file',
imageBytes,
filename: 'lorem.png',
contentType: MediaType('image', 'png'),
);
MultipartFile source = MultipartFile.fromString(
'sourceLang',
sourceLang,
);
request.files.add(multipartFile);
request.files.add(source);
http.StreamedResponse response = await client.send(request);
http.Response finalResponse = await http.Response.fromStream(response);
var lang = "";
if (finalResponse.statusCode == 200) {
lang = finalResponse.body.toString();
} else {
throw ('Error: ${finalResponse.statusCode}');
}
return lang;
}
Future<String> extractTextUnknownSource(Uint8List imageBytes) async {
var postUri = Uri.parse("http://130.61.27.201/extractWithoutKnownSource");
http.MultipartRequest request = http.MultipartRequest("POST", postUri);
http.MultipartFile multipartFile = http.MultipartFile.fromBytes(
'file',
imageBytes,
filename: 'lorem.png',
contentType: MediaType('image', 'png'),
);
request.files.add(multipartFile);
http.StreamedResponse response = await client.send(request);
http.Response finalResponse = await http.Response.fromStream(response);
var lang = "";
if (finalResponse.statusCode == 200) {
lang = finalResponse.body.toString();
} else {
throw ('Error: ${finalResponse.statusCode}');
}
return lang;
}
Future<Uint8List> translateKnownSource(
Uint8List imageBytes, String sourceLang, String targetLang) async {
var url = Uri.parse("http://130.61.27.201/translKnownSource");
http.MultipartRequest request = http.MultipartRequest("POST", url);
http.MultipartFile multipartFile = http.MultipartFile.fromBytes(
'file',
imageBytes,
filename: 'lorem.png',
contentType: MediaType('image', 'png'),
);
MultipartFile source = MultipartFile.fromString(
'sourceLang',
LanguageUtils.translatorLanguages[sourceLang]!,
);
MultipartFile target = MultipartFile.fromString(
'targetLang',
LanguageUtils.translatorLanguages[targetLang]!,
);
request.files.add(multipartFile);
request.files.add(source);
request.files.add(target);
http.StreamedResponse response = await client.send(request);
http.Response finalResponse = await http.Response.fromStream(response);
Uint8List lang = Uint8List(0);
if (finalResponse.statusCode == 200) {
lang = Uint8List.fromList(finalResponse.bodyBytes);
} else {
throw ('Error: ${finalResponse.statusCode}');
}
return lang;
}
void showProgressBar(BuildContext context) {
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return const Dialog(
backgroundColor: Color.fromARGB(0, 44, 44, 44),
elevation: 0,
child: Center(
child: CircularProgressIndicator(),
),
);
},
);
}
void hideProgressBar(BuildContext context) {
Navigator.pop(context);
}
Future<bool> internetConnectivityCheck() async {
if (Platform.isAndroid) {
var connectivityResult = await Connectivity().checkConnectivity();
return connectivityResult == ConnectivityResult.wifi ||
connectivityResult == ConnectivityResult.mobile;
} else {
final response = await http.get(Uri.parse('http://130.61.27.201'));
return response.statusCode == 200;
}
}
void triggerNoInetToast() {
Fluttertoast.showToast(
msg: "No internet connection",
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.BOTTOM,
timeInSecForIosWeb: 1,
backgroundColor: const Color.fromARGB(255, 161, 120, 17),
textColor: Colors.white,
fontSize: 16.0,
);
}
void triggerConnectedToast() {
Fluttertoast.showToast(
msg: "Connected",
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.BOTTOM,
timeInSecForIosWeb: 1,
backgroundColor: const Color.fromARGB(255, 161, 120, 17),
textColor: Colors.white,
fontSize: 16.0,
);
}
} }

View File

@ -1,10 +1,11 @@
import 'dart:io'; import 'dart:io';
import 'package:cpd_app/file_utils.dart';
import 'package:cpd_app/http_utils.dart'; import 'package:cpd_app/http_utils.dart';
import 'package:http/http.dart' as http; import 'package:cpd_app/session_list.dart';
import 'package:cpd_app/image_uploader.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'language_utils.dart'; import 'language_utils.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:image_picker/image_picker.dart'; import 'package:image_picker/image_picker.dart';
class ImageTranslation extends StatefulWidget { class ImageTranslation extends StatefulWidget {
@ -16,64 +17,49 @@ class ImageTranslation extends StatefulWidget {
class _ImageTranslationState extends State<ImageTranslation> { class _ImageTranslationState extends State<ImageTranslation> {
XFile? _selectedImage; XFile? _selectedImage;
String _sourceLanguage = 'Auto'; bool _wasTranslated = false;
String _targetLanguage = 'English'; String _sourceLanguage = 'English';
String _translatedText = ''; String _targetLanguage = 'German';
final ImageUploader _imageUploader = ImageUploader(); Uint8List translatedIamge = Uint8List(0);
final HttpUtils _httpUtils = HttpUtils(); final HttpUtils _httpUtils = HttpUtils();
/*1. Text extracten
2. Text übersetzen Uint8List buildNewImage(
3. Text aus dem alten bild mit farbe überdecken (koordinaten etwas weiter links oben + rechts unten als die erkannten koordinaten) Uint8List imageBytes, String text, String sourceLang, String targetLang) {
4. Übersetzen Text in neues bild einfügen return imageBytes;
*/ }
Future<void> uploadImage( Future<void> uploadImage(
Uint8List imageBytes, Uint8List imageBytes,
String sourceLang, String sourceLang,
String targetLang, String targetLang,
) async { ) async {
setState(() { Uint8List newImageBytes = Uint8List(0);
_translatedText = ''; _httpUtils.showProgressBar(context);
}); try {
newImageBytes = await _httpUtils.translateKnownSource(
//preprocessing imageBytes, sourceLang, targetLang);
String text = ''; } finally {
if (LanguageUtils.tessLanguages[sourceLang] == 'auto') { // ignore: use_build_context_synchronously
String lang = await _httpUtils.checkLang(imageBytes, 'image.jpg'); _httpUtils.hideProgressBar(context);
text = await _imageUploader.performOcr(imageBytes, 'image.jpg', lang);
sourceLang = LanguageUtils.tessLanguages[sourceLang]!;
} else {
String langCode = LanguageUtils.tessLanguages[sourceLang]!;
text = await _imageUploader.performOcr(imageBytes, 'image.jpg', langCode);
} }
if (text != '') { if (newImageBytes.isEmpty) {
Map<String, String> requestBody = { Fluttertoast.showToast(
'source_language': sourceLang, msg: "Image could not be translated",
'target_language': LanguageUtils.translatorLanguages[targetLang]!, toastLength: Toast.LENGTH_SHORT,
'text': text, gravity: ToastGravity.BOTTOM,
}; timeInSecForIosWeb: 1,
backgroundColor: Colors.red,
Map<String, String> headers = { textColor: Colors.white,
'content-type': 'application/x-www-form-urlencoded', fontSize: 16.0,
'X-RapidAPI-Key': 'd0fa3c2f3cmsh1805e7a2fed7cc2p1683ebjsna722e2bffafe',
'X-RapidAPI-Host': 'text-translator2.p.rapidapi.com',
};
final response = await http.post(
Uri.parse('https://text-translator2.p.rapidapi.com/translate'),
headers: headers,
body: requestBody,
); );
} else {
if (response.statusCode == 200) { DateTime now = DateTime.now();
_translatedText = response.body; int milliseconds = now.millisecondsSinceEpoch;
setState(() { ImageList.addImage(milliseconds.toString(), newImageBytes);
_translatedText = text; _wasTranslated = true;
_selectedImage = null; setState(() {
}); translatedIamge = newImageBytes;
} else { });
print('Error: ${response.statusCode}');
}
print(_translatedText);
} }
} }
@ -84,8 +70,22 @@ class _ImageTranslationState extends State<ImageTranslation> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
backgroundColor: const Color.fromARGB(99, 78, 72, 72),
appBar: AppBar( appBar: AppBar(
backgroundColor: const Color.fromARGB(255, 161, 120, 17),
title: const Text('Image Translation'), title: const Text('Image Translation'),
actions: [
IconButton(
icon: const Icon(Icons.wifi),
onPressed: () async {
if (!await _httpUtils.internetConnectivityCheck()) {
_httpUtils.triggerNoInetToast();
} else {
_httpUtils.triggerConnectedToast();
}
},
),
],
), ),
body: Center( body: Center(
child: Column( child: Column(
@ -95,7 +95,6 @@ class _ImageTranslationState extends State<ImageTranslation> {
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[ children: <Widget>[
// From - Subtle blue background
Container( Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.blue.shade100, color: Colors.blue.shade100,
@ -114,6 +113,7 @@ class _ImageTranslationState extends State<ImageTranslation> {
}); });
}, },
items: sourceLanguages items: sourceLanguages
.where((value) => value != 'Auto')
.map<DropdownMenuItem<String>>((String value) { .map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>( return DropdownMenuItem<String>(
value: value, value: value,
@ -124,7 +124,6 @@ class _ImageTranslationState extends State<ImageTranslation> {
], ],
), ),
), ),
// Target - Subtle red background
Container( Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.red.shade100, color: Colors.red.shade100,
@ -143,6 +142,7 @@ class _ImageTranslationState extends State<ImageTranslation> {
}); });
}, },
items: targetLanguages items: targetLanguages
.where((value) => value != 'Auto')
.map<DropdownMenuItem<String>>((String value) { .map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>( return DropdownMenuItem<String>(
value: value, value: value,
@ -158,22 +158,29 @@ class _ImageTranslationState extends State<ImageTranslation> {
const SizedBox(height: 20), const SizedBox(height: 20),
TextButton( TextButton(
onPressed: () async { onPressed: () async {
final ImagePicker picker = ImagePicker(); if (!_wasTranslated) {
final XFile? image = final ImagePicker picker = ImagePicker();
await picker.pickImage(source: ImageSource.gallery); final XFile? image =
if (image != null) { await picker.pickImage(source: ImageSource.gallery);
setState(() { if (image != null) {
_selectedImage = image; setState(() {
}); _selectedImage = image;
});
}
} else {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (BuildContext context) =>
const ImageTranslation(),
),
);
} }
}, },
style: ButtonStyle( style: ElevatedButton.styleFrom(
backgroundColor: MaterialStateProperty.all<Color>(Colors.blue), foregroundColor: const Color.fromARGB(255, 0, 0, 0),
), backgroundColor: const Color.fromARGB(255, 161, 120, 17)),
child: const Text( child: Text(_wasTranslated ? 'Try Another' : 'Select Image'),
'Upload Image',
style: TextStyle(color: Colors.white),
),
), ),
ElevatedButton( ElevatedButton(
onPressed: _selectedImage != null onPressed: _selectedImage != null
@ -186,30 +193,29 @@ class _ImageTranslationState extends State<ImageTranslation> {
} }
} }
: null, : null,
child: const Text('Extract Text'), style: ElevatedButton.styleFrom(
), backgroundColor: const Color.fromARGB(255, 161, 120, 17),
_translatedText.isNotEmpty foregroundColor: const Color.fromARGB(255, 0, 0, 0)),
? Expanded( child: const Text('Translate Image'),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
readOnly: true,
initialValue: _translatedText,
maxLines: null,
decoration: const InputDecoration(
labelText: 'Extracted Text',
border: OutlineInputBorder(),
),
),
),
)
: Container(),
TextButton(
onPressed: () {
Clipboard.setData(ClipboardData(text: _translatedText));
},
child: const Text('Copy to Clipboard'),
), ),
if (translatedIamge.isEmpty && _selectedImage != null)
Image.file(File(_selectedImage!.path)),
if (translatedIamge.isNotEmpty) Image.memory(translatedIamge),
if (translatedIamge.isNotEmpty)
Visibility(
visible: translatedIamge.isNotEmpty,
child: ElevatedButton(
onPressed: () {
if (translatedIamge.isNotEmpty) {
FileUtils.saveTranslatedImage(translatedIamge);
}
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color.fromARGB(255, 161, 120, 17),
foregroundColor: const Color.fromARGB(255, 0, 0, 0)),
child: const Text('Save Translated Image'),
),
),
], ],
), ),
), ),

View File

@ -4,106 +4,21 @@ class LanguageUtils {
'English': 'eng', 'English': 'eng',
'German': 'deu', 'German': 'deu',
'French': 'fra', 'French': 'fra',
'Spanish': 'spa',
'Italian': 'ita',
'Portuguese': 'por',
'Dutch': 'dut',
'Polish': 'pol',
'Russian': 'rus', 'Russian': 'rus',
'Japanese': 'jpn',
'Chinese (Simplified)': 'chi_sim',
'Chinese (Traditional)': 'chi_tra',
'Korean': 'kor',
'Arabic': 'ara',
'Hindi': 'hin',
'Indonesian': 'ind',
'Malay': 'mal',
'Thai': 'tha',
'Vietnamese': 'vie',
'Turkish': 'tur',
'Ukrainian': 'ukr',
'Hungarian': 'hun',
'Czech': 'cze',
'Finnish': 'fin',
'Danish': 'dan',
'Norwegian': 'nor',
'Swedish': 'swe',
'Greek': 'gre',
'Slovak': 'slo',
'Croatian': 'hrv',
'Lithuanian': 'lit',
'Romanian': 'rum',
'Bulgarian': 'bul',
'Latvian': 'lav',
'Estonian': 'est',
'Persian': 'per',
'Hebrew': 'heb',
'Serbian': 'srp',
'Albanian': 'alb',
'Tagalog': 'tgl',
'Azerbaijani': 'aze',
'Basque': 'baq',
'Belarusian': 'bel',
'Bengali': 'ben',
'Bosnian': 'bos',
'Cebuano': 'ceb',
'Esperanto': 'epo',
'Galician': 'glg',
'Georgian': 'geo',
'Gujarati': 'guj',
}; };
static Map<String, String> translatorLanguages = { static Map<String, String> translatorLanguages = {
'Auto': 'auto',
'English': 'en', 'English': 'en',
'Auto': 'auto',
'German': 'de', 'German': 'de',
'French': 'fr', 'French': 'fr',
'Spanish': 'spa', 'Russian': 'ru',
'Italian': 'ita', };
'Portuguese': 'por',
'Dutch': 'dut', static Map<String, String> reMapping = {
'Polish': 'pol', 'deu': 'de',
'Russian': 'rus', 'eng': 'en',
'Japanese': 'jpn', 'fra': 'fr',
'Chinese (Simplified)': 'chi_sim', 'rus': 'ru',
'Chinese (Traditional)': 'chi_tra',
'Korean': 'kor',
'Arabic': 'ara',
'Hindi': 'hin',
'Indonesian': 'ind',
'Malay': 'mal',
'Thai': 'tha',
'Vietnamese': 'vie',
'Turkish': 'tur',
'Ukrainian': 'ukr',
'Hungarian': 'hun',
'Czech': 'cze',
'Finnish': 'fin',
'Danish': 'dan',
'Norwegian': 'nor',
'Swedish': 'swe',
'Greek': 'gre',
'Slovak': 'slo',
'Croatian': 'hrv',
'Lithuanian': 'lit',
'Romanian': 'rum',
'Bulgarian': 'bul',
'Latvian': 'lav',
'Estonian': 'est',
'Persian': 'per',
'Hebrew': 'heb',
'Serbian': 'srp',
'Albanian': 'alb',
'Tagalog': 'tgl',
'Azerbaijani': 'aze',
'Basque': 'baq',
'Belarusian': 'bel',
'Bengali': 'ben',
'Bosnian': 'bos',
'Cebuano': 'ceb',
'Esperanto': 'epo',
'Galician': 'glg',
'Georgian': 'geo',
'Gujarati': 'guj',
}; };
} }

View File

@ -1,10 +1,23 @@
import 'dart:ui';
import 'package:cpd_app/image_translator.dart'; import 'package:cpd_app/image_translator.dart';
import 'package:cpd_app/ocr_page.dart'; import 'package:cpd_app/ocr_page.dart';
import 'package:cpd_app/bottom_bar.dart'; import 'package:cpd_app/session_list_view.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
void main() { Future<void> main() async {
runApp(const MyApp()); FlutterView? flutterView = PlatformDispatcher.instance.views.firstOrNull;
if (flutterView == null || flutterView.physicalSize.isEmpty) {
PlatformDispatcher.instance.onMetricsChanged = () {
flutterView = PlatformDispatcher.instance.views.firstOrNull;
if (flutterView != null && !flutterView!.physicalSize.isEmpty) {
PlatformDispatcher.instance.onMetricsChanged = null;
runApp(const MyApp());
}
};
} else {
runApp(const MyApp());
}
} }
class MyApp extends StatelessWidget { class MyApp extends StatelessWidget {
@ -24,8 +37,9 @@ class OpticText extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
backgroundColor: const Color.fromARGB(99, 78, 72, 72),
appBar: AppBar( appBar: AppBar(
backgroundColor: Colors.blue, backgroundColor: const Color.fromARGB(255, 161, 120, 17),
title: const Text('Optic Text'), title: const Text('Optic Text'),
), ),
body: Center( body: Center(
@ -36,10 +50,12 @@ class OpticText extends StatelessWidget {
MaterialPageRoute(builder: (context) => const ImageToolsPage()), MaterialPageRoute(builder: (context) => const ImageToolsPage()),
); );
}, },
style: ElevatedButton.styleFrom(
backgroundColor: const Color.fromARGB(255, 161, 120, 17),
foregroundColor: const Color.fromARGB(255, 0, 0, 0)),
child: const Text('Image Tools'), child: const Text('Image Tools'),
), ),
), ),
bottomNavigationBar: const BottomBar(),
); );
} }
} }
@ -50,7 +66,9 @@ class ImageToolsPage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
backgroundColor: const Color.fromARGB(99, 78, 72, 72),
appBar: AppBar( appBar: AppBar(
backgroundColor: const Color.fromARGB(255, 161, 120, 17),
title: const Text('Image Tools'), title: const Text('Image Tools'),
), ),
body: Center( body: Center(
@ -58,12 +76,16 @@ class ImageToolsPage extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[ children: <Widget>[
ElevatedButton( ElevatedButton(
key: const Key('ocr_button'),
onPressed: () { onPressed: () {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute(builder: (context) => const OCRPage()), MaterialPageRoute(builder: (context) => const OCRPage()),
); );
}, },
style: ElevatedButton.styleFrom(
backgroundColor: const Color.fromARGB(255, 161, 120, 17),
foregroundColor: const Color.fromARGB(255, 0, 0, 0)),
child: const Text('OCR'), child: const Text('OCR'),
), ),
ElevatedButton( ElevatedButton(
@ -74,8 +96,24 @@ class ImageToolsPage extends StatelessWidget {
builder: (context) => const ImageTranslation()), builder: (context) => const ImageTranslation()),
); );
}, },
style: ElevatedButton.styleFrom(
backgroundColor: const Color.fromARGB(255, 161, 120, 17),
foregroundColor: const Color.fromARGB(255, 0, 0, 0)),
child: const Text('Image Translation'), child: const Text('Image Translation'),
), ),
ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const SessionListView()),
);
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color.fromARGB(255, 161, 120, 17),
foregroundColor: const Color.fromARGB(255, 0, 0, 0)),
child: const Text('Previous Images'),
),
], ],
), ),
), ),

View File

@ -5,48 +5,98 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'language_utils.dart'; import 'language_utils.dart';
import 'package:image_picker/image_picker.dart'; import 'package:image_picker/image_picker.dart';
import 'package:fluttertoast/fluttertoast.dart';
class OCRPage extends StatefulWidget { class OCRPage extends StatefulWidget {
const OCRPage({Key? key}) : super(key: key); const OCRPage({Key? key}) : super(key: key);
@override @override
State<OCRPage> createState() => _OCRPageState(); State<OCRPage> createState() => OCRPageState();
} }
class _OCRPageState extends State<OCRPage> { class OCRPageState extends State<OCRPage> {
String _extractedText = ''; List<String> tessLangCodes = LanguageUtils.tessLanguages.keys.toList();
String extractedText = '';
final ImageUploader _imageUploader = ImageUploader(); final ImageUploader _imageUploader = ImageUploader();
final HttpUtils _httpUtils = HttpUtils(); final HttpUtils _httpUtils = HttpUtils();
String _selectedLanguage = 'Auto'; String _selectedLanguage = 'Auto';
XFile? _selectedImage; XFile? selectedImage;
Future<void> uploadImage( Future<void> getText(Uint8List imageBytes, String selectedLanguage) async {
Uint8List imageBytes, String selectedLanguage) async {
setState(() { setState(() {
_extractedText = ''; extractedText = '';
}); });
//preprocessing, maybe if (!(await _httpUtils.internetConnectivityCheck())) {
String text = ''; _httpUtils.triggerNoInetToast();
if (selectedLanguage == 'Auto') { return;
String lang = await _httpUtils.checkLang(imageBytes, 'image.jpg'); }
text = await _imageUploader.performOcr(imageBytes, 'image.jpg', lang); String text = '';
} else { // ignore: use_build_context_synchronously
String langCode = LanguageUtils.tessLanguages[selectedLanguage]!; _httpUtils.showProgressBar(context);
text = await _imageUploader.performOcr(imageBytes, 'image.jpg', langCode); try {
if (Platform.isWindows) {
if (selectedLanguage == 'Auto') {
String lang = await _httpUtils.checkLang(imageBytes, 'image.jpg');
String mappedLang = LanguageUtils.reMapping[lang]!;
text =
await _httpUtils.extractTextKnownSource(imageBytes, mappedLang);
} else {
String langCode =
LanguageUtils.translatorLanguages[selectedLanguage]!;
text = await _httpUtils.extractTextKnownSource(imageBytes, langCode);
}
} else {
if (selectedLanguage == 'Auto') {
String lang = await _httpUtils.checkLang(imageBytes, 'image.jpg');
text = await _imageUploader.performOcr(imageBytes, 'image.jpg', lang);
} else {
String langCode = LanguageUtils.tessLanguages[selectedLanguage]!;
text = await _imageUploader.performOcr(
imageBytes, 'image.jpg', langCode);
}
}
} finally {
// ignore: use_build_context_synchronously
_httpUtils.hideProgressBar(context);
}
if (text == "") {
Fluttertoast.showToast(
msg: "Text could not be extracted",
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.BOTTOM,
timeInSecForIosWeb: 1,
backgroundColor: const Color.fromARGB(255, 161, 120, 17),
textColor: Colors.white,
fontSize: 16.0,
);
} else {
setState(() {
extractedText = text;
selectedImage = null;
});
} }
setState(() {
_extractedText = text;
_selectedImage = null;
});
} }
List<String> languages = LanguageUtils.tessLanguages.keys.toList();
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
backgroundColor: const Color.fromARGB(99, 78, 72, 72),
appBar: AppBar( appBar: AppBar(
backgroundColor: const Color.fromARGB(255, 161, 120, 17),
title: const Text('OCR'), title: const Text('OCR'),
actions: [
IconButton(
icon: const Icon(Icons.wifi),
onPressed: () async {
if (!await _httpUtils.internetConnectivityCheck()) {
_httpUtils.triggerNoInetToast();
} else {
_httpUtils.triggerConnectedToast();
}
},
),
],
), ),
body: Center( body: Center(
child: Column( child: Column(
@ -54,23 +104,33 @@ class _OCRPageState extends State<OCRPage> {
children: <Widget>[ children: <Widget>[
DropdownButton<String>( DropdownButton<String>(
value: _selectedLanguage, value: _selectedLanguage,
style: const TextStyle(color: Colors.white),
dropdownColor: const Color(0xFF4E4848),
onChanged: (newValue) { onChanged: (newValue) {
setState(() { setState(() {
_selectedLanguage = newValue!; _selectedLanguage = newValue!;
}); });
}, },
items: languages.map<DropdownMenuItem<String>>((String value) { items:
tessLangCodes.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>( return DropdownMenuItem<String>(
value: value, value: value,
child: Text(value), child: Text(value),
); );
}).toList(), }).toList(),
), ),
_selectedImage != null //Image preview hier
? SizedBox( selectedImage != null
? Container(
width: MediaQuery.of(context).size.width * 0.8, width: MediaQuery.of(context).size.width * 0.8,
height: MediaQuery.of(context).size.height * 0.4, height: MediaQuery.of(context).size.height * 0.4,
child: Image.file(File(_selectedImage!.path)), decoration: BoxDecoration(
border: Border.all(
color: const Color.fromARGB(255, 161, 120, 17),
width: 1.0,
),
),
child: Image.file(File(selectedImage!.path)),
) )
: Container(), : Container(),
ElevatedButton( ElevatedButton(
@ -80,33 +140,44 @@ class _OCRPageState extends State<OCRPage> {
await picker.pickImage(source: ImageSource.gallery); await picker.pickImage(source: ImageSource.gallery);
if (image != null) { if (image != null) {
setState(() { setState(() {
_selectedImage = image; selectedImage = image;
}); });
} }
}, },
style: ElevatedButton.styleFrom(
backgroundColor: const Color.fromARGB(255, 161, 120, 17),
foregroundColor: const Color.fromARGB(255, 0, 0, 0)),
child: const Text('Select Image'), child: const Text('Select Image'),
), ),
ElevatedButton( ElevatedButton(
onPressed: _selectedImage != null onPressed: selectedImage != null
? () async { ? () async {
if (_selectedImage != null) { if (selectedImage != null && selectedImage!.path != "") {
File imageFile = File(_selectedImage!.path); File imageFile = File(selectedImage!.path);
List<int> imageBytes = imageFile.readAsBytesSync(); List<int> imageBytes = imageFile.readAsBytesSync();
await uploadImage( await getText(
Uint8List.fromList(imageBytes), _selectedLanguage); Uint8List.fromList(imageBytes), _selectedLanguage);
} else if (selectedImage != null &&
selectedImage!.path == "") {
Uint8List? list = await selectedImage?.readAsBytes();
await getText(list!, _selectedLanguage);
} }
} }
: null, : null,
style: ElevatedButton.styleFrom(
backgroundColor: const Color.fromARGB(255, 161, 120, 17),
foregroundColor: const Color.fromARGB(255, 0, 0, 0)),
child: const Text('Extract Text'), child: const Text('Extract Text'),
), ),
_extractedText.isNotEmpty extractedText.isNotEmpty
? Expanded( ? Expanded(
child: Padding( child: Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: TextFormField( child: TextFormField(
readOnly: true, readOnly: true,
initialValue: _extractedText, initialValue: extractedText,
maxLines: null, maxLines: null,
style: const TextStyle(color: Colors.white),
decoration: const InputDecoration( decoration: const InputDecoration(
labelText: 'Extracted Text', labelText: 'Extracted Text',
border: OutlineInputBorder(), border: OutlineInputBorder(),
@ -115,12 +186,19 @@ class _OCRPageState extends State<OCRPage> {
), ),
) )
: Container(), : Container(),
TextButton(
onPressed: () { extractedText.isNotEmpty
Clipboard.setData(ClipboardData(text: _extractedText)); ? TextButton(
}, onPressed: () {
child: const Text('Copy to Clipboard'), Clipboard.setData(ClipboardData(text: extractedText));
), },
style: TextButton.styleFrom(
backgroundColor: const Color.fromARGB(0, 33, 149, 243),
foregroundColor: const Color.fromARGB(255, 161, 120, 17),
),
child: const Text('Copy to Clipboard'),
)
: const SizedBox(),
], ],
), ),
), ),

View File

@ -0,0 +1,13 @@
import 'dart:typed_data';
class ImageList {
static Map<String, Uint8List> images = {};
static Map<String, Uint8List> getImages() {
return images;
}
static void addImage(String name, Uint8List image) {
images[name] = image;
}
}

View File

@ -0,0 +1,77 @@
import 'dart:typed_data';
import 'package:cpd_app/file_utils.dart';
import 'package:cpd_app/session_list.dart';
import 'package:flutter/material.dart';
class SessionListView extends StatefulWidget {
const SessionListView({super.key});
@override
State<SessionListView> createState() => _SessionListViewState();
}
class _SessionListViewState extends State<SessionListView> {
final Map<String, Uint8List> _images = ImageList.getImages();
String? _selectedImageKey;
bool _showSaveButton = false;
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color.fromARGB(99, 78, 72, 72),
appBar: AppBar(
backgroundColor: const Color.fromARGB(255, 161, 120, 17),
title: const Text('Image Gallery'),
),
body: GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
crossAxisSpacing: 4.0,
mainAxisSpacing: 4.0,
),
itemCount: _images.length,
itemBuilder: (BuildContext context, int index) {
String imageKey = _images.keys.elementAt(index);
Uint8List imageData = _images[imageKey]!;
return GestureDetector(
onTap: () {
setState(() {
_selectedImageKey = imageKey;
_showSaveButton = true;
});
},
child: Container(
decoration: BoxDecoration(
border: Border.all(
color: _selectedImageKey == imageKey
? Colors.blue
: Colors.transparent,
width: 2.0,
),
),
child: Image.memory(
imageData,
fit: BoxFit.cover,
),
),
);
},
),
floatingActionButton: _showSaveButton
? FloatingActionButton(
onPressed: () {
saveImage(_selectedImageKey!);
setState(() {
_showSaveButton = false;
});
},
child: const Icon(Icons.save),
)
: null,
);
}
void saveImage(String imageKey) {
FileUtils.saveTranslatedImage(ImageList.getImages()[imageKey]!);
}
}

View File

@ -5,10 +5,12 @@
import FlutterMacOS import FlutterMacOS
import Foundation import Foundation
import connectivity_macos
import file_selector_macos import file_selector_macos
import path_provider_foundation import path_provider_foundation
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
ConnectivityPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlugin"))
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
} }

View File

@ -97,6 +97,38 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.18.0" version: "1.18.0"
connectivity:
dependency: "direct main"
description:
name: connectivity
sha256: a8e91263cf3e25fb5cc95e19dfde4999e32a648ac3b9e8a558a28165731678f8
url: "https://pub.dev"
source: hosted
version: "3.0.6"
connectivity_for_web:
dependency: transitive
description:
name: connectivity_for_web
sha256: "01a390c1d5adc2ed1fa1f52d120c07fe9fd01166a93f965a832fd6cfc0ea6482"
url: "https://pub.dev"
source: hosted
version: "0.4.0+1"
connectivity_macos:
dependency: transitive
description:
name: connectivity_macos
sha256: "51ae08d5162eca9669b9d8951ed83ce19c5355a81149f94e4dee2740beb93628"
url: "https://pub.dev"
source: hosted
version: "0.2.1+2"
connectivity_platform_interface:
dependency: transitive
description:
name: connectivity_platform_interface
sha256: "2d82e942df9d49f29a24bb07fb5ce085d4a53e47818c62364d2b6deb9e0d7a8e"
url: "https://pub.dev"
source: hosted
version: "2.0.1"
convert: convert:
dependency: transitive dependency: transitive
description: description:
@ -256,6 +288,14 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
fluttertoast:
dependency: "direct main"
description:
name: fluttertoast
sha256: dfdde255317af381bfc1c486ed968d5a43a2ded9c931e87cbecd88767d6a71c1
url: "https://pub.dev"
source: hosted
version: "8.2.4"
glob: glob:
dependency: transitive dependency: transitive
description: description:
@ -280,6 +320,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.0.2" version: "4.0.2"
image_gallery_saver:
dependency: "direct main"
description:
name: image_gallery_saver
sha256: "0aba74216a4d9b0561510cb968015d56b701ba1bd94aace26aacdd8ae5761816"
url: "https://pub.dev"
source: hosted
version: "2.0.3"
image_picker: image_picker:
dependency: "direct main" dependency: "direct main"
description: description:

View File

@ -9,7 +9,7 @@ environment:
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
dio: ^4.0.0 # For making HTTP requests dio: ^4.0.0
file_picker: ^4.1.4 file_picker: ^4.1.4
image_picker: ^0.8.1 image_picker: ^0.8.1
cupertino_icons: ^1.0.2 cupertino_icons: ^1.0.2
@ -17,7 +17,10 @@ dependencies:
http: ^1.1.0 http: ^1.1.0
flutter_tesseract_ocr: flutter_tesseract_ocr:
http_parser: ^4.0.2 http_parser: ^4.0.2
#opencv_4: ^1.0.0 fluttertoast: ^8.0.8
connectivity: ^3.0.6
image_gallery_saver: '^2.0.3'
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
@ -27,14 +30,12 @@ dev_dependencies:
flutter: flutter:
assets: assets:
- assets/ - assets/schild.png
- assets/tessdata_config.json - assets/tessdata_config.json
- assets/tessdata/eng.traineddata - assets/tessdata/eng.traineddata
- assets/tessdata/deu.traineddata - assets/tessdata/deu.traineddata
- assets/tessdata/jpn.traineddata - assets/tessdata/jpn.traineddata
- assets/tessdata/chi_sim.traineddata - assets/tessdata/fra.traineddata
- assets/tessdata/ell.traineddata
- assets/tessdata/ara.traineddata
- assets/tessdata/rus.traineddata - assets/tessdata/rus.traineddata
uses-material-design: true uses-material-design: true

Binary file not shown.

View File

@ -1,21 +1,125 @@
import 'package:cpd_app/image_uploader.dart'; import 'dart:io';
import 'package:cpd_app/session_list.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:cpd_app/main.dart';
import 'package:cpd_app/ocr_page.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:image_picker/image_picker.dart';
Future<Uint8List> loadImageBytesFromAssets(String imagePath) async {
final ByteData data = await rootBundle.load(imagePath);
return data.buffer.asUint8List();
}
void main() { void main() {
TestWidgetsFlutterBinding.ensureInitialized(); setUpAll(() {
HttpOverrides.global = null;
});
testWidgets('Check ocr UI', (WidgetTester tester) async {
await tester.pumpWidget(const MyApp());
test('Test OCR functionality', () async { final buttonsFinder = find.byType(ElevatedButton);
final ImageUploader imageUploader = ImageUploader();
String imageName = "lorem.png";
var img = await imageUploader.buildImageFile(imageName); expect(buttonsFinder, findsWidgets);
assert(img.lengthInBytes > 0); await tester.tap(find.widgetWithText(ElevatedButton, "Image Tools"));
await tester.pumpAndSettle();
expect(find.text('OCR'), findsOneWidget);
// momentan auskommentiert, weil das pathproviderplugin irgentwie gemockt werden muss und ich es noch nicht hinbekommen habe var ocrbtn = find.widgetWithText(ElevatedButton, "OCR");
//mock response, weil man keine http requests in tests machen kann expect(ocrbtn, findsOneWidget);
// String mockResponse = "eng";
// String text = await imageUploader.performOcr(img, imageName, mockResponse); var imgtrans = find.widgetWithText(ElevatedButton, "Image Translation");
// assert(text.contains("Lorem ipsum")); expect(imgtrans, findsOneWidget);
var prev = find.widgetWithText(ElevatedButton, "Previous Images");
expect(prev, findsOneWidget);
await tester.tap(find.widgetWithText(ElevatedButton, "OCR"));
await tester.pumpAndSettle();
expect(find.text('OCR'), findsOneWidget);
expect(find.text('Select Image'), findsOneWidget);
expect(find.widgetWithText(DropdownButton<String>, 'Auto'), findsOneWidget);
await tester.tap(find.widgetWithText(DropdownButton<String>, 'Auto'));
await tester.pumpAndSettle();
expect(find.text('German'), findsOneWidget);
expect(find.text('English'), findsOneWidget);
expect(find.text('French'), findsOneWidget);
expect(find.text('Russian'), findsOneWidget);
await tester.tap(find.widgetWithText(DropdownMenuItem<String>, 'German'));
await tester.tap(find.widgetWithText(ElevatedButton, "Select Image"));
await tester.pump();
final ocrFinder = find.byType(OCRPage);
final ocrState = tester.state<OCRPageState>(ocrFinder);
Uint8List bytes = await loadImageBytesFromAssets('assets/schild.png');
ocrState.setState(() {
var image = XFile.fromData(bytes);
ocrState.selectedImage = image;
});
expect(ocrState.selectedImage, isNotNull);
await tester.pumpAndSettle();
expect(find.widgetWithText(ElevatedButton, "Extract Text"), findsOneWidget);
});
testWidgets('Check imageTranslateUI', (WidgetTester tester) async {
await tester.pumpWidget(const MyApp());
final buttonsFinder = find.byType(ElevatedButton);
expect(buttonsFinder, findsWidgets);
await tester.tap(find.widgetWithText(ElevatedButton, "Image Tools"));
await tester.pumpAndSettle();
expect(find.text('Image Translation'), findsOneWidget);
var btn1 = find.widgetWithText(ElevatedButton, "Image Translation");
expect(btn1, findsOneWidget);
await tester.tap(btn1);
await tester.pumpAndSettle();
expect(find.text('Image Translation'), findsOneWidget);
expect(
find.widgetWithText(DropdownButton<String>, 'English'), findsOneWidget);
await tester.tap(find.widgetWithText(DropdownButton<String>, 'English'));
await tester.pumpAndSettle();
expect(find.text('German'), findsExactly(2));
expect(find.text('English'), findsExactly(2));
expect(find.text('French'), findsExactly(1));
expect(find.text('Russian'), findsExactly(1));
await tester.pumpAndSettle();
await tester.tap(find.widgetWithText(DropdownButton<String>, 'German'));
expect(find.text('German'), findsExactly(2));
expect(find.text('English'), findsExactly(2));
expect(find.text('French'), findsExactly(1));
expect(find.text('Russian'), findsExactly(1));
//
});
testWidgets('Check listPreview', (WidgetTester tester) async {
ImageList.addImage(
"test1", await loadImageBytesFromAssets('assets/schild.png'));
ImageList.addImage(
"test2", await loadImageBytesFromAssets('assets/schild.png'));
ImageList.addImage(
"test3", await loadImageBytesFromAssets('assets/schild.png'));
ImageList.addImage(
"test3", await loadImageBytesFromAssets('assets/schild.png'));
await tester.pumpWidget(const MyApp());
final buttonsFinder = find.byType(ElevatedButton);
expect(buttonsFinder, findsWidgets);
await tester.tap(find.widgetWithText(ElevatedButton, "Image Tools"));
await tester.pumpAndSettle();
var btn1 = find.widgetWithText(ElevatedButton, "Previous Images");
expect(btn1, findsOneWidget);
await tester.tap(btn1);
await tester.pumpAndSettle();
expect(find.text('Image Gallery'), findsOneWidget);
final gridViewFinder = find.byType(GridView);
expect(gridViewFinder, findsOneWidget);
const numberOfEntries = 4;
expect(find.byType(GestureDetector), findsNWidgets(numberOfEntries));
}); });
} }

View File

@ -10,6 +10,11 @@ include(${EPHEMERAL_DIR}/generated_config.cmake)
# https://github.com/flutter/flutter/issues/57146. # https://github.com/flutter/flutter/issues/57146.
set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") 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 === # === Flutter Library ===
set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll")
@ -92,7 +97,7 @@ add_custom_command(
COMMAND ${CMAKE_COMMAND} -E env COMMAND ${CMAKE_COMMAND} -E env
${FLUTTER_TOOL_ENVIRONMENT} ${FLUTTER_TOOL_ENVIRONMENT}
"${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat"
windows-x64 $<CONFIG> ${FLUTTER_TARGET_PLATFORM} $<CONFIG>
VERBATIM VERBATIM
) )
add_custom_target(flutter_assemble DEPENDS add_custom_target(flutter_assemble DEPENDS