Compare commits
2 Commits
a4b630c66e
...
6531d73c93
Author | SHA1 | Date | |
---|---|---|---|
6531d73c93 | |||
51ba649b6e |
@@ -1,12 +1,16 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
import 'package:scanbot_sdk/scanbot_sdk_ui_v2.dart';
|
import 'package:scanbot_sdk/scanbot_sdk_ui_v2.dart';
|
||||||
|
|
||||||
|
import '../../services/storage/expenses.dart';
|
||||||
|
|
||||||
part 'scan_screen.g.dart';
|
part 'scan_screen.g.dart';
|
||||||
|
|
||||||
@riverpod
|
@riverpod
|
||||||
Future<String> _scanDocument(Ref ref) async {
|
Future<String?> _scanDocument(Ref ref) async {
|
||||||
var configuration = DocumentScanningFlow(
|
var configuration = DocumentScanningFlow(
|
||||||
appearance: DocumentFlowAppearanceConfiguration(
|
appearance: DocumentFlowAppearanceConfiguration(
|
||||||
statusBarMode: StatusBarMode.DARK,
|
statusBarMode: StatusBarMode.DARK,
|
||||||
@@ -18,22 +22,93 @@ Future<String> _scanDocument(Ref ref) async {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
var documentResult = await ScanbotSdkUiV2.startDocumentScanner(configuration);
|
var documentResult = await ScanbotSdkUiV2.startDocumentScanner(configuration);
|
||||||
print("@@@");
|
|
||||||
print(documentResult);
|
|
||||||
print("####");
|
|
||||||
|
|
||||||
return "changeme";
|
return documentResult.data?.pdfURI;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ScanScreen extends HookConsumerWidget {
|
class ScanScreen extends HookConsumerWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final boredSuggestion = ref.watch(_scanDocumentProvider);
|
final boredSuggestion = ref.watch(_scanDocumentProvider);
|
||||||
|
|
||||||
|
restartScan() async {
|
||||||
|
try {
|
||||||
|
final val = ref.refresh(_scanDocumentProvider);
|
||||||
|
Logger.root.info("Load again startup result: $val");
|
||||||
|
} catch (e, s) {
|
||||||
|
Logger.root.shout("Failed to try again startup loading! $e $s");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Perform a switch-case on the result to handle loading/error states
|
// Perform a switch-case on the result to handle loading/error states
|
||||||
return switch (boredSuggestion) {
|
return switch (boredSuggestion) {
|
||||||
AsyncData(:final value) => Text('data: $value'),
|
AsyncData(:final value) when value != null => ExpenseEditor(
|
||||||
AsyncError(:final error) => Text('error: $error'),
|
filePath: value,
|
||||||
|
onFinished: (e) {},
|
||||||
|
),
|
||||||
|
|
||||||
|
// No data
|
||||||
|
AsyncData(:final value) when value == null => ScanErrorScreen(
|
||||||
|
message: "No document scanned!",
|
||||||
|
onTryAgain: restartScan,
|
||||||
|
),
|
||||||
|
|
||||||
|
// Error
|
||||||
|
AsyncError(:final error) => ScanErrorScreen(
|
||||||
|
message: error.toString(),
|
||||||
|
onTryAgain: restartScan,
|
||||||
|
),
|
||||||
_ => const Center(child: CircularProgressIndicator()),
|
_ => const Center(child: CircularProgressIndicator()),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ScanErrorScreen extends StatelessWidget {
|
||||||
|
final String message;
|
||||||
|
final Function() onTryAgain;
|
||||||
|
|
||||||
|
const ScanErrorScreen({
|
||||||
|
super.key,
|
||||||
|
required this.message,
|
||||||
|
required this.onTryAgain,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Spacer(flex: 5),
|
||||||
|
Text("An error occurred while scanning"),
|
||||||
|
Spacer(flex: 1),
|
||||||
|
Text(message),
|
||||||
|
Spacer(flex: 1),
|
||||||
|
MaterialButton(
|
||||||
|
onPressed: onTryAgain,
|
||||||
|
child: Text("Try again".toUpperCase()),
|
||||||
|
),
|
||||||
|
Spacer(flex: 5),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ExpenseEditor extends HookWidget {
|
||||||
|
final String filePath;
|
||||||
|
final Function(Expense) onFinished;
|
||||||
|
|
||||||
|
const ExpenseEditor({
|
||||||
|
super.key,
|
||||||
|
required this.filePath,
|
||||||
|
required this.onFinished,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
// TODO: implement build
|
||||||
|
throw UnimplementedError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
117
moneymgr_mobile/lib/services/storage/expenses.dart
Normal file
117
moneymgr_mobile/lib/services/storage/expenses.dart
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:path/path.dart' as p;
|
||||||
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
|
||||||
|
part 'expenses.freezed.dart';
|
||||||
|
part 'expenses.g.dart';
|
||||||
|
|
||||||
|
typedef ExpensesList = List<Expense>;
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class Expense with _$Expense {
|
||||||
|
const Expense._();
|
||||||
|
|
||||||
|
const factory Expense({
|
||||||
|
/// Internal id used to identify the expense
|
||||||
|
required int id,
|
||||||
|
|
||||||
|
/// Label of the expense
|
||||||
|
required String? label,
|
||||||
|
|
||||||
|
/// The cost shall always be a positive value
|
||||||
|
required double cost,
|
||||||
|
|
||||||
|
/// Time associated with the expense
|
||||||
|
required int time,
|
||||||
|
|
||||||
|
/// Associated file mime type
|
||||||
|
required String mimeType,
|
||||||
|
}) = _Expense;
|
||||||
|
|
||||||
|
factory Expense.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$ExpenseFromJson(json);
|
||||||
|
|
||||||
|
/// Get associated expense file name
|
||||||
|
String get localFileName {
|
||||||
|
if (mimeType == "application/pdf") return "$id.pdf";
|
||||||
|
if (mimeType == "image/jpeg") return "$id.jpeg";
|
||||||
|
if (mimeType == "image/png") return "$id.png";
|
||||||
|
return id.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@riverpod
|
||||||
|
Future<ExpensesManager> expenses(Ref ref) async => ExpensesManager.instance();
|
||||||
|
|
||||||
|
class ExpensesManager {
|
||||||
|
final String storagePath;
|
||||||
|
|
||||||
|
ExpensesManager._({required this.storagePath});
|
||||||
|
|
||||||
|
/// Get an instance of this manager
|
||||||
|
static Future<ExpensesManager> instance() async {
|
||||||
|
final appDir = await getApplicationDocumentsDirectory();
|
||||||
|
final subDir = p.join(appDir.absolute.path, "expenses");
|
||||||
|
final result = await Directory(subDir).create(recursive: true);
|
||||||
|
|
||||||
|
return ExpensesManager._(storagePath: result.absolute.path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get expenses list file path
|
||||||
|
File get expenseFile => File(p.join(storagePath, "list.json"));
|
||||||
|
|
||||||
|
/// Get the files storage path
|
||||||
|
Directory get filesStoragePath => Directory(p.join(storagePath, "exp_files"));
|
||||||
|
|
||||||
|
/// Get the current list of expenses
|
||||||
|
Future<ExpensesList> getList() async {
|
||||||
|
final jsonDec = jsonDecode(await expenseFile.readAsString());
|
||||||
|
return List<Expense>.from(jsonDec.map((m) => Expense.fromJson(m)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Save the list of expenses
|
||||||
|
Future<void> saveList(ExpensesList list) async {
|
||||||
|
final jsonDoc = jsonEncode(list.map((t) => t.toJson()));
|
||||||
|
await expenseFile.writeAsString(jsonDoc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a new expense to the list
|
||||||
|
Future<void> add({
|
||||||
|
required String? label,
|
||||||
|
required double cost,
|
||||||
|
required int time,
|
||||||
|
required List<int> fileContent,
|
||||||
|
required String fileMimeType,
|
||||||
|
}) async {
|
||||||
|
final list = await getList();
|
||||||
|
|
||||||
|
final exp = Expense(
|
||||||
|
id: (list.lastOrNull?.id ?? 0) + Random().nextInt(1000),
|
||||||
|
label: label,
|
||||||
|
cost: cost,
|
||||||
|
time: time,
|
||||||
|
mimeType: fileMimeType,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create files storage directory if required
|
||||||
|
if (!await filesStoragePath.exists()) {
|
||||||
|
await filesStoragePath.create(recursive: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save associated file
|
||||||
|
final file = File(
|
||||||
|
p.join(filesStoragePath.absolute.path, exp.localFileName),
|
||||||
|
);
|
||||||
|
await file.writeAsBytes(fileContent);
|
||||||
|
|
||||||
|
// Save the list of expenses
|
||||||
|
list.add(exp);
|
||||||
|
await saveList(list);
|
||||||
|
}
|
||||||
|
}
|
@@ -673,7 +673,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "1.9.1"
|
version: "1.9.1"
|
||||||
path_provider:
|
path_provider:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: path_provider
|
name: path_provider
|
||||||
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
|
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
|
||||||
|
@@ -85,6 +85,9 @@ dependencies:
|
|||||||
# https://developers.google.com/ml-kit/tips/installation-paths
|
# https://developers.google.com/ml-kit/tips/installation-paths
|
||||||
scanbot_sdk: ^7.0.0
|
scanbot_sdk: ^7.0.0
|
||||||
|
|
||||||
|
# Get documents path
|
||||||
|
path_provider: ^2.1.5
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
Reference in New Issue
Block a user