final
parent
73a518733d
commit
7909116d8e
15
.metadata
15
.metadata
|
@ -15,21 +15,6 @@ migration:
|
|||
- platform: root
|
||||
create_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
|
||||
create_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9
|
||||
base_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9
|
||||
|
|
|
@ -23,20 +23,6 @@
|
|||
"request": "launch",
|
||||
"type": "dart",
|
||||
"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": []
|
||||
}
|
||||
]
|
||||
}
|
20
README.md
20
README.md
|
@ -1,22 +1,12 @@
|
|||
# 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).
|
||||
|
||||
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.
|
||||
Falls ich den Code vom Server noch nachreichen soll, kann ich dies machen.
|
||||
|
||||
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
|
||||
|
|
|
@ -45,7 +45,7 @@ android {
|
|||
applicationId "com.example.optictext"
|
||||
// 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 flutter.minSdkVersion
|
||||
minSdkVersion 21
|
||||
targetSdkVersion flutter.targetSdkVersion
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<application
|
||||
android:label="optictext"
|
||||
android:name="${applicationName}"
|
||||
|
|
|
@ -22,6 +22,7 @@ rootProject.buildDir = '../build'
|
|||
subprojects {
|
||||
project.buildDir = "${rootProject.buildDir}/${project.name}"
|
||||
}
|
||||
|
||||
subprojects {
|
||||
project.evaluationDependsOn(':app')
|
||||
}
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 238 KiB |
BIN
assets/lorem.png
BIN
assets/lorem.png
Binary file not shown.
Before Width: | Height: | Size: 11 KiB |
BIN
assets/rus.png
BIN
assets/rus.png
Binary file not shown.
Before Width: | Height: | Size: 97 KiB |
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.
|
@ -1,11 +1,9 @@
|
|||
{
|
||||
"files": [
|
||||
"eng.traineddata",
|
||||
"ara.traineddata",
|
||||
"rus.traineddata",
|
||||
"deu.traineddata",
|
||||
"jpn.traineddata",
|
||||
"ell.traineddata",
|
||||
"chi_sim.traineddata"
|
||||
"fra.traineddata"
|
||||
]
|
||||
}
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,14 +1,21 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'package:cpd_app/language_utils.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:http/http.dart';
|
||||
import 'package:http_parser/http_parser.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:connectivity/connectivity.dart';
|
||||
import 'dart:async';
|
||||
|
||||
class HttpUtils {
|
||||
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.MultipartFile multipartFile = http.MultipartFile.fromBytes(
|
||||
|
@ -19,6 +26,7 @@ class HttpUtils {
|
|||
);
|
||||
|
||||
request.files.add(multipartFile);
|
||||
|
||||
http.StreamedResponse response = await client.send(request);
|
||||
http.Response finalResponse = await http.Response.fromStream(response);
|
||||
var lang = "";
|
||||
|
@ -30,4 +38,151 @@ class HttpUtils {
|
|||
}
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import 'dart:io';
|
||||
import 'package:cpd_app/file_utils.dart';
|
||||
import 'package:cpd_app/http_utils.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:cpd_app/image_uploader.dart';
|
||||
import 'package:cpd_app/session_list.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'language_utils.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
|
||||
class ImageTranslation extends StatefulWidget {
|
||||
|
@ -16,64 +17,49 @@ class ImageTranslation extends StatefulWidget {
|
|||
|
||||
class _ImageTranslationState extends State<ImageTranslation> {
|
||||
XFile? _selectedImage;
|
||||
String _sourceLanguage = 'Auto';
|
||||
String _targetLanguage = 'English';
|
||||
String _translatedText = '';
|
||||
final ImageUploader _imageUploader = ImageUploader();
|
||||
bool _wasTranslated = false;
|
||||
String _sourceLanguage = 'English';
|
||||
String _targetLanguage = 'German';
|
||||
Uint8List translatedIamge = Uint8List(0);
|
||||
final HttpUtils _httpUtils = HttpUtils();
|
||||
/*1. Text extracten
|
||||
2. Text übersetzen
|
||||
3. Text aus dem alten bild mit farbe überdecken (koordinaten etwas weiter links oben + rechts unten als die erkannten koordinaten)
|
||||
4. Übersetzen Text in neues bild einfügen
|
||||
*/
|
||||
|
||||
Uint8List buildNewImage(
|
||||
Uint8List imageBytes, String text, String sourceLang, String targetLang) {
|
||||
return imageBytes;
|
||||
}
|
||||
|
||||
Future<void> uploadImage(
|
||||
Uint8List imageBytes,
|
||||
String sourceLang,
|
||||
String targetLang,
|
||||
) async {
|
||||
setState(() {
|
||||
_translatedText = '';
|
||||
});
|
||||
|
||||
//preprocessing
|
||||
String text = '';
|
||||
if (LanguageUtils.tessLanguages[sourceLang] == 'auto') {
|
||||
String lang = await _httpUtils.checkLang(imageBytes, 'image.jpg');
|
||||
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);
|
||||
Uint8List newImageBytes = Uint8List(0);
|
||||
_httpUtils.showProgressBar(context);
|
||||
try {
|
||||
newImageBytes = await _httpUtils.translateKnownSource(
|
||||
imageBytes, sourceLang, targetLang);
|
||||
} finally {
|
||||
// ignore: use_build_context_synchronously
|
||||
_httpUtils.hideProgressBar(context);
|
||||
}
|
||||
if (text != '') {
|
||||
Map<String, String> requestBody = {
|
||||
'source_language': sourceLang,
|
||||
'target_language': LanguageUtils.translatorLanguages[targetLang]!,
|
||||
'text': text,
|
||||
};
|
||||
|
||||
Map<String, String> headers = {
|
||||
'content-type': 'application/x-www-form-urlencoded',
|
||||
'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,
|
||||
if (newImageBytes.isEmpty) {
|
||||
Fluttertoast.showToast(
|
||||
msg: "Image could not be translated",
|
||||
toastLength: Toast.LENGTH_SHORT,
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
timeInSecForIosWeb: 1,
|
||||
backgroundColor: Colors.red,
|
||||
textColor: Colors.white,
|
||||
fontSize: 16.0,
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
_translatedText = response.body;
|
||||
setState(() {
|
||||
_translatedText = text;
|
||||
_selectedImage = null;
|
||||
});
|
||||
} else {
|
||||
print('Error: ${response.statusCode}');
|
||||
}
|
||||
print(_translatedText);
|
||||
DateTime now = DateTime.now();
|
||||
int milliseconds = now.millisecondsSinceEpoch;
|
||||
ImageList.addImage(milliseconds.toString(), newImageBytes);
|
||||
_wasTranslated = true;
|
||||
setState(() {
|
||||
translatedIamge = newImageBytes;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -84,8 +70,22 @@ class _ImageTranslationState extends State<ImageTranslation> {
|
|||
@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 Translation'),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.wifi),
|
||||
onPressed: () async {
|
||||
if (!await _httpUtils.internetConnectivityCheck()) {
|
||||
_httpUtils.triggerNoInetToast();
|
||||
} else {
|
||||
_httpUtils.triggerConnectedToast();
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Center(
|
||||
child: Column(
|
||||
|
@ -95,7 +95,6 @@ class _ImageTranslationState extends State<ImageTranslation> {
|
|||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: <Widget>[
|
||||
// From - Subtle blue background
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blue.shade100,
|
||||
|
@ -114,6 +113,7 @@ class _ImageTranslationState extends State<ImageTranslation> {
|
|||
});
|
||||
},
|
||||
items: sourceLanguages
|
||||
.where((value) => value != 'Auto')
|
||||
.map<DropdownMenuItem<String>>((String value) {
|
||||
return DropdownMenuItem<String>(
|
||||
value: value,
|
||||
|
@ -124,7 +124,6 @@ class _ImageTranslationState extends State<ImageTranslation> {
|
|||
],
|
||||
),
|
||||
),
|
||||
// Target - Subtle red background
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.red.shade100,
|
||||
|
@ -143,6 +142,7 @@ class _ImageTranslationState extends State<ImageTranslation> {
|
|||
});
|
||||
},
|
||||
items: targetLanguages
|
||||
.where((value) => value != 'Auto')
|
||||
.map<DropdownMenuItem<String>>((String value) {
|
||||
return DropdownMenuItem<String>(
|
||||
value: value,
|
||||
|
@ -158,6 +158,7 @@ class _ImageTranslationState extends State<ImageTranslation> {
|
|||
const SizedBox(height: 20),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
if (!_wasTranslated) {
|
||||
final ImagePicker picker = ImagePicker();
|
||||
final XFile? image =
|
||||
await picker.pickImage(source: ImageSource.gallery);
|
||||
|
@ -166,14 +167,20 @@ class _ImageTranslationState extends State<ImageTranslation> {
|
|||
_selectedImage = image;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
Navigator.pushReplacement(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) =>
|
||||
const ImageTranslation(),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
style: ButtonStyle(
|
||||
backgroundColor: MaterialStateProperty.all<Color>(Colors.blue),
|
||||
),
|
||||
child: const Text(
|
||||
'Upload Image',
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
style: ElevatedButton.styleFrom(
|
||||
foregroundColor: const Color.fromARGB(255, 0, 0, 0),
|
||||
backgroundColor: const Color.fromARGB(255, 161, 120, 17)),
|
||||
child: Text(_wasTranslated ? 'Try Another' : 'Select Image'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: _selectedImage != null
|
||||
|
@ -186,29 +193,28 @@ class _ImageTranslationState extends State<ImageTranslation> {
|
|||
}
|
||||
}
|
||||
: null,
|
||||
child: const Text('Extract Text'),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: const Color.fromARGB(255, 161, 120, 17),
|
||||
foregroundColor: const Color.fromARGB(255, 0, 0, 0)),
|
||||
child: const Text('Translate Image'),
|
||||
),
|
||||
_translatedText.isNotEmpty
|
||||
? Expanded(
|
||||
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(
|
||||
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: () {
|
||||
Clipboard.setData(ClipboardData(text: _translatedText));
|
||||
if (translatedIamge.isNotEmpty) {
|
||||
FileUtils.saveTranslatedImage(translatedIamge);
|
||||
}
|
||||
},
|
||||
child: const Text('Copy to Clipboard'),
|
||||
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'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
@ -4,106 +4,21 @@ class LanguageUtils {
|
|||
'English': 'eng',
|
||||
'German': 'deu',
|
||||
'French': 'fra',
|
||||
'Spanish': 'spa',
|
||||
'Italian': 'ita',
|
||||
'Portuguese': 'por',
|
||||
'Dutch': 'dut',
|
||||
'Polish': 'pol',
|
||||
'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 = {
|
||||
'Auto': 'auto',
|
||||
'English': 'en',
|
||||
'Auto': 'auto',
|
||||
'German': 'de',
|
||||
'French': 'fr',
|
||||
'Spanish': 'spa',
|
||||
'Italian': 'ita',
|
||||
'Portuguese': 'por',
|
||||
'Dutch': 'dut',
|
||||
'Polish': 'pol',
|
||||
'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',
|
||||
'Russian': 'ru',
|
||||
};
|
||||
|
||||
static Map<String, String> reMapping = {
|
||||
'deu': 'de',
|
||||
'eng': 'en',
|
||||
'fra': 'fr',
|
||||
'rus': 'ru',
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,10 +1,23 @@
|
|||
import 'dart:ui';
|
||||
|
||||
import 'package:cpd_app/image_translator.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';
|
||||
|
||||
void main() {
|
||||
Future<void> main() async {
|
||||
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 {
|
||||
|
@ -24,8 +37,9 @@ class OpticText extends StatelessWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: const Color.fromARGB(99, 78, 72, 72),
|
||||
appBar: AppBar(
|
||||
backgroundColor: Colors.blue,
|
||||
backgroundColor: const Color.fromARGB(255, 161, 120, 17),
|
||||
title: const Text('Optic Text'),
|
||||
),
|
||||
body: Center(
|
||||
|
@ -36,10 +50,12 @@ class OpticText extends StatelessWidget {
|
|||
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'),
|
||||
),
|
||||
),
|
||||
bottomNavigationBar: const BottomBar(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -50,7 +66,9 @@ class ImageToolsPage extends StatelessWidget {
|
|||
@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 Tools'),
|
||||
),
|
||||
body: Center(
|
||||
|
@ -58,12 +76,16 @@ class ImageToolsPage extends StatelessWidget {
|
|||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
ElevatedButton(
|
||||
key: const Key('ocr_button'),
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
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'),
|
||||
),
|
||||
ElevatedButton(
|
||||
|
@ -74,8 +96,24 @@ class ImageToolsPage extends StatelessWidget {
|
|||
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'),
|
||||
),
|
||||
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'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
@ -5,48 +5,98 @@ import 'package:flutter/material.dart';
|
|||
import 'package:flutter/services.dart';
|
||||
import 'language_utils.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
|
||||
class OCRPage extends StatefulWidget {
|
||||
const OCRPage({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<OCRPage> createState() => _OCRPageState();
|
||||
State<OCRPage> createState() => OCRPageState();
|
||||
}
|
||||
|
||||
class _OCRPageState extends State<OCRPage> {
|
||||
String _extractedText = '';
|
||||
class OCRPageState extends State<OCRPage> {
|
||||
List<String> tessLangCodes = LanguageUtils.tessLanguages.keys.toList();
|
||||
String extractedText = '';
|
||||
final ImageUploader _imageUploader = ImageUploader();
|
||||
final HttpUtils _httpUtils = HttpUtils();
|
||||
String _selectedLanguage = 'Auto';
|
||||
XFile? _selectedImage;
|
||||
XFile? selectedImage;
|
||||
|
||||
Future<void> uploadImage(
|
||||
Uint8List imageBytes, String selectedLanguage) async {
|
||||
Future<void> getText(Uint8List imageBytes, String selectedLanguage) async {
|
||||
setState(() {
|
||||
_extractedText = '';
|
||||
extractedText = '';
|
||||
});
|
||||
|
||||
//preprocessing, maybe
|
||||
if (!(await _httpUtils.internetConnectivityCheck())) {
|
||||
_httpUtils.triggerNoInetToast();
|
||||
return;
|
||||
}
|
||||
String text = '';
|
||||
// ignore: use_build_context_synchronously
|
||||
_httpUtils.showProgressBar(context);
|
||||
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);
|
||||
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;
|
||||
extractedText = text;
|
||||
selectedImage = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
List<String> languages = LanguageUtils.tessLanguages.keys.toList();
|
||||
@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('OCR'),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.wifi),
|
||||
onPressed: () async {
|
||||
if (!await _httpUtils.internetConnectivityCheck()) {
|
||||
_httpUtils.triggerNoInetToast();
|
||||
} else {
|
||||
_httpUtils.triggerConnectedToast();
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Center(
|
||||
child: Column(
|
||||
|
@ -54,23 +104,33 @@ class _OCRPageState extends State<OCRPage> {
|
|||
children: <Widget>[
|
||||
DropdownButton<String>(
|
||||
value: _selectedLanguage,
|
||||
style: const TextStyle(color: Colors.white),
|
||||
dropdownColor: const Color(0xFF4E4848),
|
||||
onChanged: (newValue) {
|
||||
setState(() {
|
||||
_selectedLanguage = newValue!;
|
||||
});
|
||||
},
|
||||
items: languages.map<DropdownMenuItem<String>>((String value) {
|
||||
items:
|
||||
tessLangCodes.map<DropdownMenuItem<String>>((String value) {
|
||||
return DropdownMenuItem<String>(
|
||||
value: value,
|
||||
child: Text(value),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
_selectedImage != null
|
||||
? SizedBox(
|
||||
//Image preview hier
|
||||
selectedImage != null
|
||||
? Container(
|
||||
width: MediaQuery.of(context).size.width * 0.8,
|
||||
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(),
|
||||
ElevatedButton(
|
||||
|
@ -80,33 +140,44 @@ class _OCRPageState extends State<OCRPage> {
|
|||
await picker.pickImage(source: ImageSource.gallery);
|
||||
if (image != null) {
|
||||
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'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: _selectedImage != null
|
||||
onPressed: selectedImage != null
|
||||
? () async {
|
||||
if (_selectedImage != null) {
|
||||
File imageFile = File(_selectedImage!.path);
|
||||
if (selectedImage != null && selectedImage!.path != "") {
|
||||
File imageFile = File(selectedImage!.path);
|
||||
List<int> imageBytes = imageFile.readAsBytesSync();
|
||||
await uploadImage(
|
||||
await getText(
|
||||
Uint8List.fromList(imageBytes), _selectedLanguage);
|
||||
} else if (selectedImage != null &&
|
||||
selectedImage!.path == "") {
|
||||
Uint8List? list = await selectedImage?.readAsBytes();
|
||||
await getText(list!, _selectedLanguage);
|
||||
}
|
||||
}
|
||||
: 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'),
|
||||
),
|
||||
_extractedText.isNotEmpty
|
||||
extractedText.isNotEmpty
|
||||
? Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: TextFormField(
|
||||
readOnly: true,
|
||||
initialValue: _extractedText,
|
||||
initialValue: extractedText,
|
||||
maxLines: null,
|
||||
style: const TextStyle(color: Colors.white),
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Extracted Text',
|
||||
border: OutlineInputBorder(),
|
||||
|
@ -115,12 +186,19 @@ class _OCRPageState extends State<OCRPage> {
|
|||
),
|
||||
)
|
||||
: Container(),
|
||||
TextButton(
|
||||
|
||||
extractedText.isNotEmpty
|
||||
? TextButton(
|
||||
onPressed: () {
|
||||
Clipboard.setData(ClipboardData(text: _extractedText));
|
||||
Clipboard.setData(ClipboardData(text: extractedText));
|
||||
},
|
||||
child: const Text('Copy to Clipboard'),
|
||||
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(),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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]!);
|
||||
}
|
||||
}
|
|
@ -5,10 +5,12 @@
|
|||
import FlutterMacOS
|
||||
import Foundation
|
||||
|
||||
import connectivity_macos
|
||||
import file_selector_macos
|
||||
import path_provider_foundation
|
||||
|
||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
ConnectivityPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlugin"))
|
||||
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
|
||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||
}
|
||||
|
|
48
pubspec.lock
48
pubspec.lock
|
@ -97,6 +97,38 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -256,6 +288,14 @@ packages:
|
|||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
fluttertoast:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: fluttertoast
|
||||
sha256: dfdde255317af381bfc1c486ed968d5a43a2ded9c931e87cbecd88767d6a71c1
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.2.4"
|
||||
glob:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -280,6 +320,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
13
pubspec.yaml
13
pubspec.yaml
|
@ -9,7 +9,7 @@ environment:
|
|||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
dio: ^4.0.0 # For making HTTP requests
|
||||
dio: ^4.0.0
|
||||
file_picker: ^4.1.4
|
||||
image_picker: ^0.8.1
|
||||
cupertino_icons: ^1.0.2
|
||||
|
@ -17,7 +17,10 @@ dependencies:
|
|||
http: ^1.1.0
|
||||
flutter_tesseract_ocr:
|
||||
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:
|
||||
flutter_test:
|
||||
|
@ -27,14 +30,12 @@ dev_dependencies:
|
|||
|
||||
flutter:
|
||||
assets:
|
||||
- assets/
|
||||
- assets/schild.png
|
||||
- assets/tessdata_config.json
|
||||
- assets/tessdata/eng.traineddata
|
||||
- assets/tessdata/deu.traineddata
|
||||
- assets/tessdata/jpn.traineddata
|
||||
- assets/tessdata/chi_sim.traineddata
|
||||
- assets/tessdata/ell.traineddata
|
||||
- assets/tessdata/ara.traineddata
|
||||
- assets/tessdata/fra.traineddata
|
||||
- assets/tessdata/rus.traineddata
|
||||
|
||||
uses-material-design: true
|
Binary file not shown.
|
@ -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: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() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
setUpAll(() {
|
||||
HttpOverrides.global = null;
|
||||
});
|
||||
testWidgets('Check ocr UI', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(const MyApp());
|
||||
|
||||
test('Test OCR functionality', () async {
|
||||
final ImageUploader imageUploader = ImageUploader();
|
||||
String imageName = "lorem.png";
|
||||
final buttonsFinder = find.byType(ElevatedButton);
|
||||
|
||||
var img = await imageUploader.buildImageFile(imageName);
|
||||
assert(img.lengthInBytes > 0);
|
||||
expect(buttonsFinder, findsWidgets);
|
||||
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
|
||||
//mock response, weil man keine http requests in tests machen kann
|
||||
// String mockResponse = "eng";
|
||||
var ocrbtn = find.widgetWithText(ElevatedButton, "OCR");
|
||||
expect(ocrbtn, findsOneWidget);
|
||||
|
||||
// String text = await imageUploader.performOcr(img, imageName, mockResponse);
|
||||
// assert(text.contains("Lorem ipsum"));
|
||||
var imgtrans = find.widgetWithText(ElevatedButton, "Image Translation");
|
||||
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));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -10,6 +10,11 @@ include(${EPHEMERAL_DIR}/generated_config.cmake)
|
|||
# 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")
|
||||
|
||||
|
@ -92,7 +97,7 @@ add_custom_command(
|
|||
COMMAND ${CMAKE_COMMAND} -E env
|
||||
${FLUTTER_TOOL_ENVIRONMENT}
|
||||
"${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat"
|
||||
windows-x64 $<CONFIG>
|
||||
${FLUTTER_TARGET_PLATFORM} $<CONFIG>
|
||||
VERBATIM
|
||||
)
|
||||
add_custom_target(flutter_assemble DEPENDS
|
||||
|
|
Loading…
Reference in New Issue