From 70023242e9a28c335e0d8d888f6e610610d44ba2 Mon Sep 17 00:00:00 2001 From: Pierre HUBERT Date: Mon, 14 Jul 2025 09:42:42 +0200 Subject: [PATCH] Start to build expense editor screen --- .../lib/routes/scan/scan_screen.dart | 109 ++++++++++++++---- moneymgr_mobile/lib/utils/time_utils.dart | 7 +- moneymgr_mobile/pubspec.lock | 2 +- moneymgr_mobile/pubspec.yaml | 1 + 4 files changed, 94 insertions(+), 25 deletions(-) diff --git a/moneymgr_mobile/lib/routes/scan/scan_screen.dart b/moneymgr_mobile/lib/routes/scan/scan_screen.dart index 756351c..ff33ce0 100644 --- a/moneymgr_mobile/lib/routes/scan/scan_screen.dart +++ b/moneymgr_mobile/lib/routes/scan/scan_screen.dart @@ -7,17 +7,17 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_pdfview/flutter_pdfview.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:logging/logging.dart'; +import 'package:moneymgr_mobile/services/storage/expenses.dart'; +import 'package:moneymgr_mobile/services/storage/prefs.dart'; +import 'package:moneymgr_mobile/utils/time_utils.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:scanbot_sdk/scanbot_sdk.dart'; -import 'package:scanbot_sdk/scanbot_sdk_ui_v2.dart'; - -import '../../services/storage/expenses.dart'; +import 'package:scanbot_sdk/scanbot_sdk_ui_v2.dart' hide IconButton, EdgeInsets; part 'scan_screen.g.dart'; @riverpod Future _scanDocument(Ref ref) async { - DocumentDataExtractionResult d; var configuration = DocumentScanningFlow( appearance: DocumentFlowAppearanceConfiguration( statusBarMode: StatusBarMode.DARK, @@ -46,6 +46,8 @@ Future _scanDocument(Ref ref) async { } class ScanScreen extends HookConsumerWidget { + const ScanScreen({super.key}); + @override Widget build(BuildContext context, WidgetRef ref) { final boredSuggestion = ref.watch(_scanDocumentProvider); @@ -60,25 +62,28 @@ class ScanScreen extends HookConsumerWidget { } // Perform a switch-case on the result to handle loading/error states - return switch (boredSuggestion) { - AsyncData(:final value) when value != null => ExpenseEditor( - file: value, - onFinished: (e) {}, - ), + return Padding( + padding: const EdgeInsets.all(8.0), + child: switch (boredSuggestion) { + AsyncData(:final value) when value != null => ExpenseEditor( + file: value, + onFinished: (e) {}, + ), - // No data - AsyncData(:final value) when value == null => ScanErrorScreen( - message: "No document scanned!", - onTryAgain: restartScan, - ), + // 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()), - }; + // Error + AsyncError(:final error) => ScanErrorScreen( + message: error.toString(), + onTryAgain: restartScan, + ), + _ => const Center(child: CircularProgressIndicator()), + }, + ); } } @@ -115,7 +120,7 @@ class ScanErrorScreen extends StatelessWidget { } } -class ExpenseEditor extends HookWidget { +class ExpenseEditor extends HookConsumerWidget { final Uint8List file; final Function(Expense) onFinished; @@ -126,9 +131,27 @@ class ExpenseEditor extends HookWidget { }); @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { + final serverConfig = ref.watch(prefsProvider).requireValue.serverConfig()!; + + final labelController = useTextEditingController(); + final costController = useTextEditingController(); + final timeController = useState(DateTime.now()); + + // Pick a new date + handlePickDate() async { + final date = await showDatePicker( + context: context, + firstDate: DateTime(2000), + lastDate: DateTime(2099), + initialDate: timeController.value, + ); + if (date != null) timeController.value = date; + } + return ListView( children: [ + // Expense preview SizedBox( height: 200, child: PDFView( @@ -140,6 +163,46 @@ class ExpenseEditor extends HookWidget { fitPolicy: FitPolicy.BOTH, ), ), + + SizedBox(height: 10), + + // Cost + TextField( + controller: costController, + keyboardType: TextInputType.number, + decoration: const InputDecoration(labelText: 'Cost'), + textInputAction: TextInputAction.done, + ), + + SizedBox(height: 10), + + // Label + TextField( + controller: labelController, + keyboardType: TextInputType.text, + decoration: const InputDecoration(labelText: 'Label'), + textInputAction: TextInputAction.done, + maxLength: serverConfig.constraints.inbox_entry_label.max, + ), + + SizedBox(height: 10), + + // Date + TextField( + enabled: true, + readOnly: true, + controller: TextEditingController( + text: timeController.value.simpleDate, + ), + keyboardType: TextInputType.datetime, + decoration: InputDecoration( + labelText: 'Date', + suffixIcon: IconButton( + onPressed: handlePickDate, + icon: const Icon(Icons.date_range), + ), + ), + ), ], ); } diff --git a/moneymgr_mobile/lib/utils/time_utils.dart b/moneymgr_mobile/lib/utils/time_utils.dart index 2cb08e6..9287aa9 100644 --- a/moneymgr_mobile/lib/utils/time_utils.dart +++ b/moneymgr_mobile/lib/utils/time_utils.dart @@ -1,3 +1,8 @@ int secondsSinceEpoch(DateTime time) { return time.millisecondsSinceEpoch ~/ 1000; -} \ No newline at end of file +} + +extension SimpleDateFormatting on DateTime { + String get simpleDate => + "${day.toString().padLeft(2, "0")}/${month.toString().padLeft(2, '0')}/$year"; +} diff --git a/moneymgr_mobile/pubspec.lock b/moneymgr_mobile/pubspec.lock index e519b9e..a3f6975 100644 --- a/moneymgr_mobile/pubspec.lock +++ b/moneymgr_mobile/pubspec.lock @@ -673,7 +673,7 @@ packages: source: hosted version: "2.2.0" path: - dependency: transitive + dependency: "direct main" description: name: path sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" diff --git a/moneymgr_mobile/pubspec.yaml b/moneymgr_mobile/pubspec.yaml index 4052832..906ed78 100644 --- a/moneymgr_mobile/pubspec.yaml +++ b/moneymgr_mobile/pubspec.yaml @@ -87,6 +87,7 @@ dependencies: # Get documents path path_provider: ^2.1.5 + path: ^1.9.1 # PDF viewer flutter_pdfview: ^1.4.1+1