Compare commits

..

No commits in common. "d5e3d8b76c35c9f45b087e14cd6302459a3399cc" and "ba04bd5a1cb243ec011d9465562f93f227131b0e" have entirely different histories.

13 changed files with 68 additions and 142 deletions

@ -1 +1 @@
Subproject commit 0d074ced6cd64f39f586aaa115b2e4c993d74cb7 Subproject commit 123453dc41ef48e717939dd3c012db6a23138895

View file

@ -1,6 +1,5 @@
{ {
"conventionalCommits.scopes": [ "conventionalCommits.scopes": [
"ocr", "ocr"
"ui"
] ]
} }

View file

@ -20,4 +20,4 @@ Expense manager
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
``` ```
The base app includes the [tessdata_fast](https://github.com/tesseract-ocr/tessdata_fast) English trained data, ©️ [tessdata_fast / Tesseract contributors](https://github.com/tesseract-ocr/tessdata_fast/graphs/contributors), used under the [Apache 2.0 license](https://github.com/tesseract-ocr/tessdata_fast/blob/main/LICENSE) The base app includes the [tessdata_best](https://github.com/tesseract-ocr/tessdata_best) English trained data, ©️ [tessdata_best / Tesseract contributors](https://github.com/tesseract-ocr/tessdata_best/graphs/contributors), used under the [Apache 2.0 license](https://github.com/tesseract-ocr/tessdata_best/blob/main/LICENSE)

View file

@ -1,6 +1,4 @@
<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="Prašule" android:label="Prašule"
android:name="${applicationName}" android:name="${applicationName}"

Binary file not shown.

View file

@ -1,5 +1,5 @@
{ {
"files":[ "files": [
"eng.traineddata" "eng.traineddata"
] ]
} }

View file

@ -4,7 +4,7 @@
## ##
## Flutter start up script for UN*X ## Flutter start up script for UN*X
## Version: v1.3.1 ## Version: v1.3.1
## Date: 2023-09-28 16:13:48 ## Date: 2023-09-05 17:10:03
## ##
## Use this flutter wrapper to bundle Flutter within your project to make ## Use this flutter wrapper to bundle Flutter within your project to make
## sure everybody builds with the same version. ## sure everybody builds with the same version.

View file

@ -12,7 +12,7 @@ class TessdataApi {
); );
static Future<List<String>> getAvailableData() async { static Future<List<String>> getAvailableData() async {
var res = await _client.get( var res = await _client.get(
"https://git.mnau.xyz/api/v1/repos/hernik/tessdata_fast/contents", "https://git.mnau.xyz/api/v1/repos/hernik/tessdata_best/contents",
options: Options(headers: {"Accept": "application/json"})); options: Options(headers: {"Accept": "application/json"}));
if ((res.statusCode ?? 500) > 399) { if ((res.statusCode ?? 500) > 399) {
return Future.error("The server returned status code ${res.statusCode}"); return Future.error("The server returned status code ${res.statusCode}");
@ -28,36 +28,25 @@ class TessdataApi {
static Future<void> deleteData(String name) async { static Future<void> deleteData(String name) async {
var dataDir = Directory(await FlutterTesseractOcr.getTessdataPath()); var dataDir = Directory(await FlutterTesseractOcr.getTessdataPath());
if (!dataDir.existsSync()) {
dataDir.createSync();
}
var dataFile = File("${dataDir.path}/$name.traineddata"); var dataFile = File("${dataDir.path}/$name.traineddata");
if (!dataFile.existsSync()) return; if (!dataFile.existsSync()) return;
dataFile.deleteSync(); dataFile.deleteSync();
} }
static Future<List<String>> getDownloadedData() async { static Future<List<String>> getDownloadedData() async =>
var tessDir = Directory(await FlutterTesseractOcr.getTessdataPath()); Directory(await FlutterTesseractOcr.getTessdataPath())
if (!tessDir.existsSync()) {
tessDir.createSync();
}
return tessDir
.listSync() .listSync()
.where((element) => element.path.endsWith(".traineddata")) .where((element) => element.path.endsWith(".traineddata"))
.map<String>((e) => e.path.split("/").last) .map<String>((e) => e.path.split("/").last)
.toList(); .toList();
}
static Future<void> downloadData(String isoCode, static Future<void> downloadData(String isoCode,
{void Function(int, int)? callback}) async { {void Function(int, int)? callback}) async {
var tessDir = Directory(await FlutterTesseractOcr.getTessdataPath()); var file = File(
if (!tessDir.existsSync()) { "${(await FlutterTesseractOcr.getTessdataPath())}/$isoCode.traineddata");
tessDir.createSync();
}
var file = File("${tessDir.path}/$isoCode.traineddata");
if (file.existsSync()) return; // TODO: maybe ask to redownload? if (file.existsSync()) return; // TODO: maybe ask to redownload?
var res = await _client.get( var res = await _client.get(
"https://git.mnau.xyz/hernik/tessdata_fast/raw/branch/main/$isoCode.traineddata", "https://git.mnau.xyz/hernik/tessdata_best/raw/branch/main/$isoCode.traineddata",
options: Options(responseType: ResponseType.bytes), options: Options(responseType: ResponseType.bytes),
onReceiveProgress: callback); onReceiveProgress: callback);
if ((res.statusCode ?? 500) > 399) { if ((res.statusCode ?? 500) > 399) {

View file

@ -35,9 +35,7 @@ class PlatformField extends PlatformWidget<TextField, CupertinoTextField> {
controller: controller, controller: controller,
enabled: enabled, enabled: enabled,
obscureText: obscureText, obscureText: obscureText,
decoration: InputDecoration( decoration: InputDecoration(labelText: labelText),
labelText: labelText,
border: OutlineInputBorder(borderRadius: BorderRadius.circular(4))),
autocorrect: autocorrect, autocorrect: autocorrect,
keyboardType: keyboardType, keyboardType: keyboardType,
style: textStyle, style: textStyle,

View file

@ -53,10 +53,10 @@ class _CreateEntryViewState extends State<CreateEntryView> {
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
const Text("Name"),
SizedBox( SizedBox(
width: MediaQuery.of(context).size.width * 0.8, width: MediaQuery.of(context).size.width * 0.8,
child: PlatformField( child: PlatformField(
labelText: "Name",
controller: TextEditingController(text: newEntry.name), controller: TextEditingController(text: newEntry.name),
onChanged: (v) { onChanged: (v) {
newEntry.name = v; newEntry.name = v;
@ -66,10 +66,10 @@ class _CreateEntryViewState extends State<CreateEntryView> {
const SizedBox( const SizedBox(
height: 15, height: 15,
), ),
const Text("Amount"),
SizedBox( SizedBox(
width: MediaQuery.of(context).size.width * 0.8, width: MediaQuery.of(context).size.width * 0.8,
child: PlatformField( child: PlatformField(
labelText: "Amount",
controller: controller:
TextEditingController(text: newEntry.amount.toString()), TextEditingController(text: newEntry.amount.toString()),
keyboardType: keyboardType:

View file

@ -1,7 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_slidable/flutter_slidable.dart'; import 'package:flutter_slidable/flutter_slidable.dart';
import 'package:flutter_speed_dial/flutter_speed_dial.dart'; import 'package:flutter_speed_dial/flutter_speed_dial.dart';
import 'package:flutter_tesseract_ocr/flutter_tesseract_ocr.dart';
import 'package:grouped_list/grouped_list.dart'; import 'package:grouped_list/grouped_list.dart';
import 'package:image_picker/image_picker.dart'; import 'package:image_picker/image_picker.dart';
import 'package:intl/date_symbol_data_local.dart'; import 'package:intl/date_symbol_data_local.dart';
@ -15,7 +14,6 @@ import 'package:prasule/pw/platformbutton.dart';
import 'package:prasule/pw/platformdialog.dart'; import 'package:prasule/pw/platformdialog.dart';
import 'package:prasule/views/create_entry.dart'; import 'package:prasule/views/create_entry.dart';
import 'package:prasule/views/settings/settings.dart'; import 'package:prasule/views/settings/settings.dart';
import 'package:prasule/views/settings/tessdata_list.dart';
import 'package:prasule/views/setup.dart'; import 'package:prasule/views/setup.dart';
class HomeView extends StatefulWidget { class HomeView extends StatefulWidget {
@ -87,8 +85,48 @@ class _HomeViewState extends State<HomeView> {
SpeedDialChild( SpeedDialChild(
child: const Icon(Icons.image), child: const Icon(Icons.image),
label: "Add through saved image", label: "Add through saved image",
onTap: () { onTap: () async {
startOcr(ImageSource.gallery); var availableLanguages = await TessdataApi.getDownloadedData();
if (mounted) {
var selectedLanguages =
List<bool>.filled(availableLanguages.length, false);
selectedLanguages[
availableLanguages.indexOf("eng.traineddata")] = true;
showDialog(
context: context,
builder: (c) => PlatformDialog(
title: "Select languages for OCR",
content: Column(
children: [
...List.generate(
availableLanguages.length,
(index) => Row(
children: [
Checkbox(
value: selectedLanguages[index],
onChanged: (value) {
if (value == null ||
(selectedLanguages
.where((element) => element)
.length <=
1 &&
!value)) return;
selectedLanguages[index] = value;
setState(() {}); // todo: builder
},
),
const SizedBox(
width: 10,
),
Text(availableLanguages[index].split(".").first)
],
),
)
],
),
),
);
}
}, },
), ),
], ],
@ -248,102 +286,6 @@ class _HomeViewState extends State<HomeView> {
); );
} }
Future<void> startOcr(ImageSource imgSrc) async {
var availableLanguages = await TessdataApi.getDownloadedData();
if (availableLanguages.isEmpty) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content:
const Text("You do not have any OCR language data downloaded"),
action: SnackBarAction(
label: "Download",
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (c) => const TessdataListView(),
),
);
},
),
),
);
return;
}
if (!mounted) return;
var selectedLanguages = List<bool>.filled(availableLanguages.length, false);
if (selectedLanguages.length == 1) {
selectedLanguages[0] = true;
}
showDialog(
context: context,
builder: (c) => PlatformDialog(
actions: [
TextButton(
onPressed: () async {
final ImagePicker picker = ImagePicker();
final XFile? media = await picker.pickImage(source: imgSrc);
if (media == null) {
if (mounted) Navigator.of(context).pop();
return;
}
// get selected languages
var selected = availableLanguages
.where((element) =>
selectedLanguages[availableLanguages.indexOf(element)])
.join("+")
.replaceAll(".traineddata", "");
logger.i(selected);
var string = await FlutterTesseractOcr.extractText(media.path,
language: selected,
args: {
//"psm": "4",
"preserve_interword_spaces": "1",
});
logger.i(string);
if (mounted) Navigator.of(context).pop();
return;
},
child: const Text("Ok")),
TextButton(
onPressed: () {
Navigator.of(c).pop();
},
child: const Text("Cancel")),
],
title: "Select languages for OCR",
content: Column(
children: [
...List.generate(
availableLanguages.length,
(index) => Row(
children: [
Checkbox(
value: selectedLanguages[index],
onChanged: (value) {
if (value == null ||
(selectedLanguages
.where((element) => element)
.length <=
1 &&
!value)) return;
selectedLanguages[index] = value;
setState(() {}); // todo: builder
},
),
const SizedBox(
width: 10,
),
Text(availableLanguages[index].split(".").first)
],
),
)
],
),
),
);
}
Future<void> getLostData() async { Future<void> getLostData() async {
final ImagePicker picker = ImagePicker(); final ImagePicker picker = ImagePicker();
final LostDataResponse response = await picker.retrieveLostData(); final LostDataResponse response = await picker.retrieveLostData();

View file

@ -133,7 +133,6 @@ class _TessdataListViewState extends State<TessdataListView> {
/// Used to find which `.traineddata` is already downloaded and which not /// Used to find which `.traineddata` is already downloaded and which not
/// so we can show it to the user /// so we can show it to the user
void loadAllTessdata() async { void loadAllTessdata() async {
var tessDir = Directory(await FlutterTesseractOcr.getTessdataPath());
var d = await TessdataApi.getAvailableData(); var d = await TessdataApi.getAvailableData();
var dataStatus = <Map<String, bool>>[]; var dataStatus = <Map<String, bool>>[];
for (var data in d) { for (var data in d) {
@ -141,7 +140,8 @@ class _TessdataListViewState extends State<TessdataListView> {
e[data] = false; e[data] = false;
dataStatus.add(e); dataStatus.add(e);
} }
var appDir = tessDir.listSync(); var appDir =
Directory(await FlutterTesseractOcr.getTessdataPath()).listSync();
for (var file in appDir) { for (var file in appDir) {
if (file is! File || if (file is! File ||
!file.path.endsWith("traineddata") || !file.path.endsWith("traineddata") ||

View file

@ -779,10 +779,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: platform name: platform
sha256: "57c07bf82207aee366dfaa3867b3164e4f03a238a461a11b0e8a3a510d51203d" sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.1" version: "3.1.0"
plugin_platform_interface: plugin_platform_interface:
dependency: transitive dependency: transitive
description: description:
@ -1024,10 +1024,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: vm_service name: vm_service
sha256: c538be99af830f478718b51630ec1b6bee5e74e52c8a802d328d9e71d35d2583 sha256: "0fae432c85c4ea880b33b497d32824b97795b04cdaa74d270219572a1f50268d"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "11.10.0" version: "11.9.0"
watcher: watcher:
dependency: transitive dependency: transitive
description: description: