Start to build expense editor screen
This commit is contained in:
		@@ -7,17 +7,17 @@ import 'package:flutter_hooks/flutter_hooks.dart';
 | 
				
			|||||||
import 'package:flutter_pdfview/flutter_pdfview.dart';
 | 
					import 'package:flutter_pdfview/flutter_pdfview.dart';
 | 
				
			||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
					import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
				
			||||||
import 'package:logging/logging.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:riverpod_annotation/riverpod_annotation.dart';
 | 
				
			||||||
import 'package:scanbot_sdk/scanbot_sdk.dart';
 | 
					import 'package:scanbot_sdk/scanbot_sdk.dart';
 | 
				
			||||||
import 'package:scanbot_sdk/scanbot_sdk_ui_v2.dart';
 | 
					import 'package:scanbot_sdk/scanbot_sdk_ui_v2.dart' hide IconButton, EdgeInsets;
 | 
				
			||||||
 | 
					 | 
				
			||||||
import '../../services/storage/expenses.dart';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
part 'scan_screen.g.dart';
 | 
					part 'scan_screen.g.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@riverpod
 | 
					@riverpod
 | 
				
			||||||
Future<Uint8List?> _scanDocument(Ref ref) async {
 | 
					Future<Uint8List?> _scanDocument(Ref ref) async {
 | 
				
			||||||
  DocumentDataExtractionResult d;
 | 
					 | 
				
			||||||
  var configuration = DocumentScanningFlow(
 | 
					  var configuration = DocumentScanningFlow(
 | 
				
			||||||
    appearance: DocumentFlowAppearanceConfiguration(
 | 
					    appearance: DocumentFlowAppearanceConfiguration(
 | 
				
			||||||
      statusBarMode: StatusBarMode.DARK,
 | 
					      statusBarMode: StatusBarMode.DARK,
 | 
				
			||||||
@@ -46,6 +46,8 @@ Future<Uint8List?> _scanDocument(Ref ref) async {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ScanScreen extends HookConsumerWidget {
 | 
					class ScanScreen extends HookConsumerWidget {
 | 
				
			||||||
 | 
					  const ScanScreen({super.key});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
  Widget build(BuildContext context, WidgetRef ref) {
 | 
					  Widget build(BuildContext context, WidgetRef ref) {
 | 
				
			||||||
    final boredSuggestion = ref.watch(_scanDocumentProvider);
 | 
					    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
 | 
					    // Perform a switch-case on the result to handle loading/error states
 | 
				
			||||||
    return switch (boredSuggestion) {
 | 
					    return Padding(
 | 
				
			||||||
      AsyncData(:final value) when value != null => ExpenseEditor(
 | 
					      padding: const EdgeInsets.all(8.0),
 | 
				
			||||||
        file: value,
 | 
					      child: switch (boredSuggestion) {
 | 
				
			||||||
        onFinished: (e) {},
 | 
					        AsyncData(:final value) when value != null => ExpenseEditor(
 | 
				
			||||||
      ),
 | 
					          file: value,
 | 
				
			||||||
 | 
					          onFinished: (e) {},
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // No data
 | 
					        // No data
 | 
				
			||||||
      AsyncData(:final value) when value == null => ScanErrorScreen(
 | 
					        AsyncData(:final value) when value == null => ScanErrorScreen(
 | 
				
			||||||
        message: "No document scanned!",
 | 
					          message: "No document scanned!",
 | 
				
			||||||
        onTryAgain: restartScan,
 | 
					          onTryAgain: restartScan,
 | 
				
			||||||
      ),
 | 
					        ),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // Error
 | 
					        // Error
 | 
				
			||||||
      AsyncError(:final error) => ScanErrorScreen(
 | 
					        AsyncError(:final error) => ScanErrorScreen(
 | 
				
			||||||
        message: error.toString(),
 | 
					          message: error.toString(),
 | 
				
			||||||
        onTryAgain: restartScan,
 | 
					          onTryAgain: restartScan,
 | 
				
			||||||
      ),
 | 
					        ),
 | 
				
			||||||
      _ => const Center(child: CircularProgressIndicator()),
 | 
					        _ => const Center(child: CircularProgressIndicator()),
 | 
				
			||||||
    };
 | 
					      },
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -115,7 +120,7 @@ class ScanErrorScreen extends StatelessWidget {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ExpenseEditor extends HookWidget {
 | 
					class ExpenseEditor extends HookConsumerWidget {
 | 
				
			||||||
  final Uint8List file;
 | 
					  final Uint8List file;
 | 
				
			||||||
  final Function(Expense) onFinished;
 | 
					  final Function(Expense) onFinished;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -126,9 +131,27 @@ class ExpenseEditor extends HookWidget {
 | 
				
			|||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @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(
 | 
					    return ListView(
 | 
				
			||||||
      children: [
 | 
					      children: [
 | 
				
			||||||
 | 
					        // Expense preview
 | 
				
			||||||
        SizedBox(
 | 
					        SizedBox(
 | 
				
			||||||
          height: 200,
 | 
					          height: 200,
 | 
				
			||||||
          child: PDFView(
 | 
					          child: PDFView(
 | 
				
			||||||
@@ -140,6 +163,46 @@ class ExpenseEditor extends HookWidget {
 | 
				
			|||||||
            fitPolicy: FitPolicy.BOTH,
 | 
					            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),
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
      ],
 | 
					      ],
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,3 +1,8 @@
 | 
				
			|||||||
int secondsSinceEpoch(DateTime time) {
 | 
					int secondsSinceEpoch(DateTime time) {
 | 
				
			||||||
  return time.millisecondsSinceEpoch ~/ 1000;
 | 
					  return time.millisecondsSinceEpoch ~/ 1000;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					extension SimpleDateFormatting on DateTime {
 | 
				
			||||||
 | 
					  String get simpleDate =>
 | 
				
			||||||
 | 
					      "${day.toString().padLeft(2, "0")}/${month.toString().padLeft(2, '0')}/$year";
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -673,7 +673,7 @@ packages:
 | 
				
			|||||||
    source: hosted
 | 
					    source: hosted
 | 
				
			||||||
    version: "2.2.0"
 | 
					    version: "2.2.0"
 | 
				
			||||||
  path:
 | 
					  path:
 | 
				
			||||||
    dependency: transitive
 | 
					    dependency: "direct main"
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
      name: path
 | 
					      name: path
 | 
				
			||||||
      sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
 | 
					      sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -87,6 +87,7 @@ dependencies:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  # Get documents path
 | 
					  # Get documents path
 | 
				
			||||||
  path_provider: ^2.1.5
 | 
					  path_provider: ^2.1.5
 | 
				
			||||||
 | 
					  path: ^1.9.1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  # PDF viewer
 | 
					  # PDF viewer
 | 
				
			||||||
  flutter_pdfview: ^1.4.1+1
 | 
					  flutter_pdfview: ^1.4.1+1
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user