From 6f3d987d19d070ccb7dc4b1f40226c91e9daa9ab Mon Sep 17 00:00:00 2001 From: DragonSlayer_14 Date: Mon, 5 Jan 2026 01:07:23 +0100 Subject: [PATCH] =?UTF-8?q?Feat:=20F=C3=BCgt=20Benachrichtigungen=20f?= =?UTF-8?q?=C3=BCr=20nicht=20=C3=BCberpr=C3=BCfte=20Transaktionen=20hinzu?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../background_task_controller.dart | 22 +- lib/Controller/local_notifications.dart | 204 ++++++++++++ lib/Controller/transaction_controller.dart | 40 ++- lib/Entities/drift_database.dart | 9 + lib/Entities/drift_database.g.dart | 294 ++++++++++++++++-- lib/Repositories/transaction_repository.dart | 43 ++- lib/Services/initializer.dart | 25 ++ lib/Services/transaction_service.dart | 29 ++ .../background_init.dart | 2 +- .../background_init_web.dart | 0 .../{ => BackgroundHandler}/workers.dart | 5 +- .../workmanager_workers.dart | 0 lib/Tasks/show_notifications_task.dart | 28 ++ lib/main.dart | 6 + 14 files changed, 676 insertions(+), 31 deletions(-) create mode 100644 lib/Controller/local_notifications.dart create mode 100644 lib/Services/initializer.dart create mode 100644 lib/Services/transaction_service.dart rename lib/Tasks/{ => BackgroundHandler}/background_init.dart (91%) rename lib/Tasks/{ => BackgroundHandler}/background_init_web.dart (100%) rename lib/Tasks/{ => BackgroundHandler}/workers.dart (85%) rename lib/Tasks/{ => BackgroundHandler}/workmanager_workers.dart (100%) create mode 100644 lib/Tasks/show_notifications_task.dart diff --git a/lib/Controller/background_task_controller.dart b/lib/Controller/background_task_controller.dart index 9b0fae8..f210263 100644 --- a/lib/Controller/background_task_controller.dart +++ b/lib/Controller/background_task_controller.dart @@ -5,8 +5,8 @@ import 'package:flutter/foundation.dart'; import 'package:isolate_manager/isolate_manager.dart'; import 'package:workmanager/workmanager.dart'; -import '../Tasks/workers.dart'; -import '../Tasks/workmanager_workers.dart'; +import '../Tasks/BackgroundHandler/workers.dart'; +import '../Tasks/BackgroundHandler/workmanager_workers.dart'; /// Erstellt Hintergrundtasks und führt diese aus class BackgroundTaskController { @@ -22,6 +22,14 @@ class BackgroundTaskController { initialDelay: const Duration(minutes: 1), ), ); + unawaited( + Workmanager().registerPeriodicTask( + 'show-notifications', + 'show_notifications', + frequency: const Duration(minutes: 120), + initialDelay: const Duration(minutes: 5), + ), + ); } else { unawaited( IsolateManager.runFunction(runTask, { @@ -30,6 +38,16 @@ class BackgroundTaskController { 'frequencyMinutes': 30, }), ); + + if (!kIsWeb) { + unawaited( + IsolateManager.runFunction(runTask, { + 'taskName': 'show_notifications', + 'initialDelayMinutes': 5, + 'frequencyMinutes': 120, + }), + ); + } } } } diff --git a/lib/Controller/local_notifications.dart b/lib/Controller/local_notifications.dart new file mode 100644 index 0000000..8e322d4 --- /dev/null +++ b/lib/Controller/local_notifications.dart @@ -0,0 +1,204 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; + +import '../Entities/drift_database.dart'; +import '../Repositories/transaction_repository.dart'; +import '../Services/date_service.dart'; +import '../Services/initializer.dart'; +import '../Services/transaction_service.dart'; +import 'port_controller.dart'; +import 'transaction_controller.dart'; + +/// Kümmert sich um die Verwendung von Benachrichtigungen auf verschiedenen +/// Plattformen +class LocalNotifications { + /// Gibt die aktuell gültige Instanz dieser Klasse zurück + factory LocalNotifications() => _instance; + + LocalNotifications._internal() { + unawaited(_initialise()); + } + + static final _instance = LocalNotifications._internal(); + + final FlutterLocalNotificationsPlugin _localNotificationsPlugin = + FlutterLocalNotificationsPlugin(); + final Initializer _initializer = Initializer(); + + Future _initialise() async { + if (_initializer.initialized) { + return; + } + + await _localNotificationsPlugin + .resolvePlatformSpecificImplementation< + AndroidFlutterLocalNotificationsPlugin + >() + ?.requestNotificationsPermission(); + + const AndroidInitializationSettings androidInitializationSettings = + AndroidInitializationSettings('app_icon'); + const LinuxInitializationSettings linuxInitializationSettings = + LinuxInitializationSettings(defaultActionName: 'Öffnen'); + + const InitializationSettings initializationSettings = + InitializationSettings( + android: androidInitializationSettings, + linux: linuxInitializationSettings, + ); + + await _localNotificationsPlugin.initialize( + initializationSettings, + onDidReceiveNotificationResponse: _onDidReceiveNotificationResponse, + onDidReceiveBackgroundNotificationResponse: notificationTapBackground, + ); + + if (!kIsWeb && !Platform.isLinux) { + final NotificationAppLaunchDetails? notificationAppLaunchDetails = + await _localNotificationsPlugin.getNotificationAppLaunchDetails(); + if (notificationAppLaunchDetails?.didNotificationLaunchApp ?? false) { + _onDidReceiveNotificationResponse( + NotificationResponse( + notificationResponseType: + NotificationResponseType.selectedNotification, + payload: + notificationAppLaunchDetails!.notificationResponse?.payload, + ), + ); + } + } + + _initializer.setInitialized(); + } + + /// Zeigt eine Benachrichtigung an, die einen dazu auffordert, + /// automatisch generierte Transaktionen zu überprüfen + Future showTransactionsToCheckNotification( + final List transactions, + ) async { + await _initializer.waitUntilInitialized(); + + const AndroidNotificationDetails androidNotificationDetails = + AndroidNotificationDetails( + 'transactions_to_check', + 'Transaktionen zu prüfen', + channelDescription: + 'Zeigt an, dass es zu prüfende Transaktionen gibt', + actions: [ + AndroidNotificationAction('mark_checked', 'Als geprüft markieren'), + AndroidNotificationAction( + 'show_transactions', + 'Anzeigen', + showsUserInterface: true, + ), + // TODO: Prüfen, ob die App geöffnet wird + ], + ); + const LinuxNotificationDetails linuxNotificationDetails = + LinuxNotificationDetails( + actions: [ + LinuxNotificationAction( + key: 'mark_checked', + label: 'Als geprüft markieren', + ), + LinuxNotificationAction( + key: 'show_transactions', + label: 'Anzeigen', + ), + ], + ); + const NotificationDetails notificationDetails = NotificationDetails( + android: androidNotificationDetails, + linux: linuxNotificationDetails, + ); + + final String title; + final String body; + + if (transactions.length == 1) { + title = 'Transaktion prüfen'; + body = + 'Es wurde eine neue Transaktion anhand einer ' + 'wiederkehrenden Transaktion erstellt:\n' + '${transactions[0].name} - ' + '${DateService.dateFormat.format(transactions[0].date!)} - ' + '${transactions[0].amount} €\n\n' + 'Diese muss überprüft werden!'; + } else { + int counter = 0; + final List transactionsToShow = []; + + for (final Transaction transaction in transactions) { + if (counter >= 10) { + break; + } + + transactionsToShow.add( + '${transaction.name} - ' + '${DateService.dateFormat.format(transaction.date!)} - ' + '${transaction.amount}', + ); + counter++; + } + + title = 'Transaktionen prüfen'; + body = + 'Es wurden neue Transaktionen anhand ' + 'wiederkehrender Transaktionen erstellt:\n' + '${transactionsToShow.join('\n')}\n\n' + 'Diese müssen überprüft werden!'; + } + + await _localNotificationsPlugin.show( + 0, + title, + body, + notificationDetails, + payload: TransactionService.transactionsToString(transactions), + ); + } + + void _onDidReceiveNotificationResponse( + final NotificationResponse notificationResponse, + ) { + if (notificationResponse.actionId == 'mark_checked') { + TransactionRepository().markTransactionsAsChecked( + TransactionService.transactionsFromString(notificationResponse.payload), + ); + + if (kIsWeb) { + unawaited(TransactionController().updateTransactions()); + } else { + PortController().getPort('update-transactions')?.send('ready'); + } + } else { + if (kIsWeb) { + TransactionController().goToTransactions( + transactions: TransactionService.transactionsFromString( + notificationResponse.payload, + ), + ); + } else { + PortController() + .getPort('go-to-transactions') + ?.send(notificationResponse.payload); + } + } + } + + /// Wird von den LocalNotifications aufgerufen, + /// wenn eine Aktion im Hintergrund abgehandelt werden soll + @pragma('vm:entry-point') + static void notificationTapBackground( + final NotificationResponse notificationResponse, + ) { + if (notificationResponse.actionId == 'mark_checked') { + TransactionRepository().markTransactionsAsChecked( + TransactionService.transactionsFromString(notificationResponse.payload), + ); + } + } +} diff --git a/lib/Controller/transaction_controller.dart b/lib/Controller/transaction_controller.dart index 9c56ddc..f730577 100644 --- a/lib/Controller/transaction_controller.dart +++ b/lib/Controller/transaction_controller.dart @@ -5,6 +5,7 @@ import 'package:drift/drift.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:logger/logger.dart'; +import 'package:routemaster/routemaster.dart'; import '../Entities/drift_database.dart'; import '../Pages/Dialog/dialog_action.dart'; @@ -13,6 +14,8 @@ import '../Pages/Dialog/dialog_input_field_type_enum.dart'; import '../Pages/Dialog/dialog_type_enum.dart'; import '../Pages/Dialog/dynamic_dialog.dart'; import '../Repositories/transaction_repository.dart'; +import '../Services/navigation_service.dart'; +import '../Services/transaction_service.dart'; import 'account_controller.dart'; import 'port_controller.dart'; @@ -72,13 +75,31 @@ class TransactionController { }); if (!kIsWeb) { - final ReceivePort receivePort = ReceivePort() + final ReceivePort updateTransactionsReceivePort = ReceivePort() ..listen((_) { Logger().i('Received update-transactions signal'); unawaited(updateTransactions()); }); - PortController().addPort(receivePort.sendPort, 'update-transactions'); + PortController().addPort( + updateTransactionsReceivePort.sendPort, + 'update-transactions', + ); + + final ReceivePort gotToTransactionsReceivePort = ReceivePort() + ..listen((final value) { + Logger().i('Received go-to-transactions signal'); + + final List transactions = + TransactionService.transactionsFromString(value); + + goToTransactions(transactions: transactions); + }); + + PortController().addPort( + gotToTransactionsReceivePort.sendPort, + 'go-to-transactions', + ); } unawaited(updateTransactions()); @@ -121,6 +142,21 @@ class TransactionController { } } + /// Wechselt zur Übersicht über die Transaktionen + void goToTransactions({final List? transactions}) { + final BuildContext? context = NavigationService.getCurrentBuildContext(); + + if (context != null && transactions != null) { + if (transactions.length == 1) { + Routemaster.of( + context, + ).push('/trend', queryParameters: {'name': transactions.first.name}); + } + + Routemaster.of(context).replace('/trend'); + } + } + /// Startet den Prozess, um eine neue Transaktion anzulegen void newTransactionHandler() { unawaited(_newTransactionDialog?.show()); diff --git a/lib/Entities/drift_database.dart b/lib/Entities/drift_database.dart index 35457ca..28c874b 100644 --- a/lib/Entities/drift_database.dart +++ b/lib/Entities/drift_database.dart @@ -14,6 +14,9 @@ class Accounts extends Table { /// Name des Kontos TextColumn get name => text().withDefault(const Constant(''))(); + /// Der externe Identifier, wenn woanders gespeichert + IntColumn get externalIdentifier => integer().nullable()(); + /// Wann das Konto das letzte mal geupdated wurde DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)(); } @@ -43,6 +46,9 @@ class Transactions extends Table { IntColumn get recurringTransactionId => integer().nullable().references(RecurringTransactions, #id)(); + /// Der externe Identifier, wenn woanders gespeichert + IntColumn get externalIdentifier => integer().nullable()(); + /// Wann die Transaktion das letzte mal geupdated wurde DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)(); } @@ -67,6 +73,9 @@ class RecurringTransactions extends Table { /// Fremdschlüssel zum zugehörigen Konto IntColumn get accountId => integer().references(Accounts, #id)(); + /// Der externe Identifier, wenn woanders gespeichert + IntColumn get externalIdentifier => integer().nullable()(); + /// Wann die wiederkehrende Transaktion das letzte mal geupdated wurde DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)(); } diff --git a/lib/Entities/drift_database.g.dart b/lib/Entities/drift_database.g.dart index 4876442..9363ab8 100644 --- a/lib/Entities/drift_database.g.dart +++ b/lib/Entities/drift_database.g.dart @@ -31,6 +31,16 @@ class $AccountsTable extends Accounts with TableInfo<$AccountsTable, Account> { requiredDuringInsert: false, defaultValue: const Constant(''), ); + static const VerificationMeta _externalIdentifierMeta = + const VerificationMeta('externalIdentifier'); + @override + late final GeneratedColumn externalIdentifier = GeneratedColumn( + 'external_identifier', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); static const VerificationMeta _updatedAtMeta = const VerificationMeta( 'updatedAt', ); @@ -44,7 +54,12 @@ class $AccountsTable extends Accounts with TableInfo<$AccountsTable, Account> { defaultValue: currentDateAndTime, ); @override - List get $columns => [id, name, updatedAt]; + List get $columns => [ + id, + name, + externalIdentifier, + updatedAt, + ]; @override String get aliasedName => _alias ?? actualTableName; @override @@ -66,6 +81,15 @@ class $AccountsTable extends Accounts with TableInfo<$AccountsTable, Account> { name.isAcceptableOrUnknown(data['name']!, _nameMeta), ); } + if (data.containsKey('external_identifier')) { + context.handle( + _externalIdentifierMeta, + externalIdentifier.isAcceptableOrUnknown( + data['external_identifier']!, + _externalIdentifierMeta, + ), + ); + } if (data.containsKey('updated_at')) { context.handle( _updatedAtMeta, @@ -89,6 +113,10 @@ class $AccountsTable extends Accounts with TableInfo<$AccountsTable, Account> { DriftSqlType.string, data['${effectivePrefix}name'], )!, + externalIdentifier: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}external_identifier'], + ), updatedAt: attachedDatabase.typeMapping.read( DriftSqlType.dateTime, data['${effectivePrefix}updated_at'], @@ -109,11 +137,15 @@ class Account extends DataClass implements Insertable { /// Name des Kontos final String name; + /// Der externe Identifier, wenn woanders gespeichert + final int? externalIdentifier; + /// Wann das Konto das letzte mal geupdated wurde final DateTime updatedAt; const Account({ required this.id, required this.name, + this.externalIdentifier, required this.updatedAt, }); @override @@ -121,6 +153,9 @@ class Account extends DataClass implements Insertable { final map = {}; map['id'] = Variable(id); map['name'] = Variable(name); + if (!nullToAbsent || externalIdentifier != null) { + map['external_identifier'] = Variable(externalIdentifier); + } map['updated_at'] = Variable(updatedAt); return map; } @@ -129,6 +164,9 @@ class Account extends DataClass implements Insertable { return AccountsCompanion( id: Value(id), name: Value(name), + externalIdentifier: externalIdentifier == null && nullToAbsent + ? const Value.absent() + : Value(externalIdentifier), updatedAt: Value(updatedAt), ); } @@ -141,6 +179,7 @@ class Account extends DataClass implements Insertable { return Account( id: serializer.fromJson(json['id']), name: serializer.fromJson(json['name']), + externalIdentifier: serializer.fromJson(json['externalIdentifier']), updatedAt: serializer.fromJson(json['updatedAt']), ); } @@ -150,19 +189,31 @@ class Account extends DataClass implements Insertable { return { 'id': serializer.toJson(id), 'name': serializer.toJson(name), + 'externalIdentifier': serializer.toJson(externalIdentifier), 'updatedAt': serializer.toJson(updatedAt), }; } - Account copyWith({int? id, String? name, DateTime? updatedAt}) => Account( + Account copyWith({ + int? id, + String? name, + Value externalIdentifier = const Value.absent(), + DateTime? updatedAt, + }) => Account( id: id ?? this.id, name: name ?? this.name, + externalIdentifier: externalIdentifier.present + ? externalIdentifier.value + : this.externalIdentifier, updatedAt: updatedAt ?? this.updatedAt, ); Account copyWithCompanion(AccountsCompanion data) { return Account( id: data.id.present ? data.id.value : this.id, name: data.name.present ? data.name.value : this.name, + externalIdentifier: data.externalIdentifier.present + ? data.externalIdentifier.value + : this.externalIdentifier, updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, ); } @@ -171,45 +222,52 @@ class Account extends DataClass implements Insertable { String toString() { return (StringBuffer('Account(') ..write('id: $id, ') - ..write('name: $name, ') + ..write('name: $name, ')..write( + 'externalIdentifier: $externalIdentifier, ') ..write('updatedAt: $updatedAt') ..write(')')) .toString(); } @override - int get hashCode => Object.hash(id, name, updatedAt); + int get hashCode => Object.hash(id, name, externalIdentifier, updatedAt); @override bool operator ==(Object other) => identical(this, other) || (other is Account && other.id == this.id && other.name == this.name && + other.externalIdentifier == this.externalIdentifier && other.updatedAt == this.updatedAt); } class AccountsCompanion extends UpdateCompanion { final Value id; final Value name; + final Value externalIdentifier; final Value updatedAt; const AccountsCompanion({ this.id = const Value.absent(), this.name = const Value.absent(), + this.externalIdentifier = const Value.absent(), this.updatedAt = const Value.absent(), }); AccountsCompanion.insert({ this.id = const Value.absent(), this.name = const Value.absent(), + this.externalIdentifier = const Value.absent(), this.updatedAt = const Value.absent(), }); static Insertable custom({ Expression? id, Expression? name, + Expression? externalIdentifier, Expression? updatedAt, }) { return RawValuesInsertable({ if (id != null) 'id': id, if (name != null) 'name': name, + if (externalIdentifier != null) 'external_identifier': externalIdentifier, if (updatedAt != null) 'updated_at': updatedAt, }); } @@ -217,11 +275,13 @@ class AccountsCompanion extends UpdateCompanion { AccountsCompanion copyWith({ Value? id, Value? name, + Value? externalIdentifier, Value? updatedAt, }) { return AccountsCompanion( id: id ?? this.id, name: name ?? this.name, + externalIdentifier: externalIdentifier ?? this.externalIdentifier, updatedAt: updatedAt ?? this.updatedAt, ); } @@ -235,6 +295,9 @@ class AccountsCompanion extends UpdateCompanion { if (name.present) { map['name'] = Variable(name.value); } + if (externalIdentifier.present) { + map['external_identifier'] = Variable(externalIdentifier.value); + } if (updatedAt.present) { map['updated_at'] = Variable(updatedAt.value); } @@ -245,7 +308,8 @@ class AccountsCompanion extends UpdateCompanion { String toString() { return (StringBuffer('AccountsCompanion(') ..write('id: $id, ') - ..write('name: $name, ') + ..write('name: $name, ')..write( + 'externalIdentifier: $externalIdentifier, ') ..write('updatedAt: $updatedAt') ..write(')')) .toString(); @@ -327,6 +391,16 @@ class $RecurringTransactionsTable extends RecurringTransactions 'REFERENCES accounts (id)', ), ); + static const VerificationMeta _externalIdentifierMeta = + const VerificationMeta('externalIdentifier'); + @override + late final GeneratedColumn externalIdentifier = GeneratedColumn( + 'external_identifier', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); static const VerificationMeta _updatedAtMeta = const VerificationMeta( 'updatedAt', ); @@ -347,6 +421,7 @@ class $RecurringTransactionsTable extends RecurringTransactions timeFrame, amount, accountId, + externalIdentifier, updatedAt, ]; @override @@ -390,6 +465,15 @@ class $RecurringTransactionsTable extends RecurringTransactions } else if (isInserting) { context.missing(_accountIdMeta); } + if (data.containsKey('external_identifier')) { + context.handle( + _externalIdentifierMeta, + externalIdentifier.isAcceptableOrUnknown( + data['external_identifier']!, + _externalIdentifierMeta, + ), + ); + } if (data.containsKey('updated_at')) { context.handle( _updatedAtMeta, @@ -431,6 +515,10 @@ class $RecurringTransactionsTable extends RecurringTransactions DriftSqlType.int, data['${effectivePrefix}account_id'], )!, + externalIdentifier: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}external_identifier'], + ), updatedAt: attachedDatabase.typeMapping.read( DriftSqlType.dateTime, data['${effectivePrefix}updated_at'], @@ -467,6 +555,9 @@ class RecurringTransaction extends DataClass /// Fremdschlüssel zum zugehörigen Konto final int accountId; + /// Der externe Identifier, wenn woanders gespeichert + final int? externalIdentifier; + /// Wann die wiederkehrende Transaktion das letzte mal geupdated wurde final DateTime updatedAt; const RecurringTransaction({ @@ -476,6 +567,7 @@ class RecurringTransaction extends DataClass required this.timeFrame, required this.amount, required this.accountId, + this.externalIdentifier, required this.updatedAt, }); @override @@ -493,6 +585,9 @@ class RecurringTransaction extends DataClass } map['amount'] = Variable(amount); map['account_id'] = Variable(accountId); + if (!nullToAbsent || externalIdentifier != null) { + map['external_identifier'] = Variable(externalIdentifier); + } map['updated_at'] = Variable(updatedAt); return map; } @@ -507,6 +602,9 @@ class RecurringTransaction extends DataClass timeFrame: Value(timeFrame), amount: Value(amount), accountId: Value(accountId), + externalIdentifier: externalIdentifier == null && nullToAbsent + ? const Value.absent() + : Value(externalIdentifier), updatedAt: Value(updatedAt), ); } @@ -525,6 +623,7 @@ class RecurringTransaction extends DataClass ), amount: serializer.fromJson(json['amount']), accountId: serializer.fromJson(json['accountId']), + externalIdentifier: serializer.fromJson(json['externalIdentifier']), updatedAt: serializer.fromJson(json['updatedAt']), ); } @@ -540,6 +639,7 @@ class RecurringTransaction extends DataClass ), 'amount': serializer.toJson(amount), 'accountId': serializer.toJson(accountId), + 'externalIdentifier': serializer.toJson(externalIdentifier), 'updatedAt': serializer.toJson(updatedAt), }; } @@ -551,6 +651,7 @@ class RecurringTransaction extends DataClass TimeFrameEnum? timeFrame, double? amount, int? accountId, + Value externalIdentifier = const Value.absent(), DateTime? updatedAt, }) => RecurringTransaction( id: id ?? this.id, @@ -559,6 +660,9 @@ class RecurringTransaction extends DataClass timeFrame: timeFrame ?? this.timeFrame, amount: amount ?? this.amount, accountId: accountId ?? this.accountId, + externalIdentifier: externalIdentifier.present + ? externalIdentifier.value + : this.externalIdentifier, updatedAt: updatedAt ?? this.updatedAt, ); RecurringTransaction copyWithCompanion(RecurringTransactionsCompanion data) { @@ -569,6 +673,9 @@ class RecurringTransaction extends DataClass timeFrame: data.timeFrame.present ? data.timeFrame.value : this.timeFrame, amount: data.amount.present ? data.amount.value : this.amount, accountId: data.accountId.present ? data.accountId.value : this.accountId, + externalIdentifier: data.externalIdentifier.present + ? data.externalIdentifier.value + : this.externalIdentifier, updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, ); } @@ -576,17 +683,29 @@ class RecurringTransaction extends DataClass @override String toString() { return (StringBuffer('RecurringTransaction(') - ..write('id: $id, ')..write('name: $name, ')..write( - 'startDate: $startDate, ')..write('timeFrame: $timeFrame, ')..write( - 'amount: $amount, ')..write('accountId: $accountId, ')..write( - 'updatedAt: $updatedAt') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('startDate: $startDate, ') + ..write('timeFrame: $timeFrame, ') + ..write('amount: $amount, ') + ..write('accountId: $accountId, ') + ..write('externalIdentifier: $externalIdentifier, ') + ..write('updatedAt: $updatedAt') ..write(')')) .toString(); } @override - int get hashCode => - Object.hash(id, name, startDate, timeFrame, amount, accountId, updatedAt); + int get hashCode => Object.hash( + id, + name, + startDate, + timeFrame, + amount, + accountId, + externalIdentifier, + updatedAt, + ); @override bool operator ==(Object other) => identical(this, other) || @@ -597,6 +716,7 @@ class RecurringTransaction extends DataClass other.timeFrame == this.timeFrame && other.amount == this.amount && other.accountId == this.accountId && + other.externalIdentifier == this.externalIdentifier && other.updatedAt == this.updatedAt); } @@ -608,6 +728,7 @@ class RecurringTransactionsCompanion final Value timeFrame; final Value amount; final Value accountId; + final Value externalIdentifier; final Value updatedAt; const RecurringTransactionsCompanion({ this.id = const Value.absent(), @@ -616,6 +737,7 @@ class RecurringTransactionsCompanion this.timeFrame = const Value.absent(), this.amount = const Value.absent(), this.accountId = const Value.absent(), + this.externalIdentifier = const Value.absent(), this.updatedAt = const Value.absent(), }); RecurringTransactionsCompanion.insert({ @@ -625,6 +747,7 @@ class RecurringTransactionsCompanion required TimeFrameEnum timeFrame, this.amount = const Value.absent(), required int accountId, + this.externalIdentifier = const Value.absent(), this.updatedAt = const Value.absent(), }) : timeFrame = Value(timeFrame), accountId = Value(accountId); @@ -635,6 +758,7 @@ class RecurringTransactionsCompanion Expression? timeFrame, Expression? amount, Expression? accountId, + Expression? externalIdentifier, Expression? updatedAt, }) { return RawValuesInsertable({ @@ -644,6 +768,7 @@ class RecurringTransactionsCompanion if (timeFrame != null) 'time_frame': timeFrame, if (amount != null) 'amount': amount, if (accountId != null) 'account_id': accountId, + if (externalIdentifier != null) 'external_identifier': externalIdentifier, if (updatedAt != null) 'updated_at': updatedAt, }); } @@ -655,6 +780,7 @@ class RecurringTransactionsCompanion Value? timeFrame, Value? amount, Value? accountId, + Value? externalIdentifier, Value? updatedAt, }) { return RecurringTransactionsCompanion( @@ -664,6 +790,7 @@ class RecurringTransactionsCompanion timeFrame: timeFrame ?? this.timeFrame, amount: amount ?? this.amount, accountId: accountId ?? this.accountId, + externalIdentifier: externalIdentifier ?? this.externalIdentifier, updatedAt: updatedAt ?? this.updatedAt, ); } @@ -691,6 +818,9 @@ class RecurringTransactionsCompanion if (accountId.present) { map['account_id'] = Variable(accountId.value); } + if (externalIdentifier.present) { + map['external_identifier'] = Variable(externalIdentifier.value); + } if (updatedAt.present) { map['updated_at'] = Variable(updatedAt.value); } @@ -700,10 +830,14 @@ class RecurringTransactionsCompanion @override String toString() { return (StringBuffer('RecurringTransactionsCompanion(') - ..write('id: $id, ')..write('name: $name, ')..write( - 'startDate: $startDate, ')..write('timeFrame: $timeFrame, ')..write( - 'amount: $amount, ')..write('accountId: $accountId, ')..write( - 'updatedAt: $updatedAt') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('startDate: $startDate, ') + ..write('timeFrame: $timeFrame, ') + ..write('amount: $amount, ') + ..write('accountId: $accountId, ') + ..write('externalIdentifier: $externalIdentifier, ') + ..write('updatedAt: $updatedAt') ..write(')')) .toString(); } @@ -799,6 +933,16 @@ class $TransactionsTable extends Transactions 'REFERENCES recurring_transactions (id)', ), ); + static const VerificationMeta _externalIdentifierMeta = + const VerificationMeta('externalIdentifier'); + @override + late final GeneratedColumn externalIdentifier = GeneratedColumn( + 'external_identifier', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); static const VerificationMeta _updatedAtMeta = const VerificationMeta( 'updatedAt', ); @@ -820,6 +964,7 @@ class $TransactionsTable extends Transactions checked, accountId, recurringTransactionId, + externalIdentifier, updatedAt, ]; @override @@ -878,6 +1023,15 @@ class $TransactionsTable extends Transactions ), ); } + if (data.containsKey('external_identifier')) { + context.handle( + _externalIdentifierMeta, + externalIdentifier.isAcceptableOrUnknown( + data['external_identifier']!, + _externalIdentifierMeta, + ), + ); + } if (data.containsKey('updated_at')) { context.handle( _updatedAtMeta, @@ -921,6 +1075,10 @@ class $TransactionsTable extends Transactions DriftSqlType.int, data['${effectivePrefix}recurring_transaction_id'], ), + externalIdentifier: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}external_identifier'], + ), updatedAt: attachedDatabase.typeMapping.read( DriftSqlType.dateTime, data['${effectivePrefix}updated_at'], @@ -957,6 +1115,9 @@ class Transaction extends DataClass implements Insertable { /// falls vorhanden final int? recurringTransactionId; + /// Der externe Identifier, wenn woanders gespeichert + final int? externalIdentifier; + /// Wann die Transaktion das letzte mal geupdated wurde final DateTime updatedAt; const Transaction({ @@ -967,6 +1128,7 @@ class Transaction extends DataClass implements Insertable { required this.checked, required this.accountId, this.recurringTransactionId, + this.externalIdentifier, required this.updatedAt, }); @override @@ -983,6 +1145,9 @@ class Transaction extends DataClass implements Insertable { if (!nullToAbsent || recurringTransactionId != null) { map['recurring_transaction_id'] = Variable(recurringTransactionId); } + if (!nullToAbsent || externalIdentifier != null) { + map['external_identifier'] = Variable(externalIdentifier); + } map['updated_at'] = Variable(updatedAt); return map; } @@ -998,6 +1163,9 @@ class Transaction extends DataClass implements Insertable { recurringTransactionId: recurringTransactionId == null && nullToAbsent ? const Value.absent() : Value(recurringTransactionId), + externalIdentifier: externalIdentifier == null && nullToAbsent + ? const Value.absent() + : Value(externalIdentifier), updatedAt: Value(updatedAt), ); } @@ -1017,6 +1185,7 @@ class Transaction extends DataClass implements Insertable { recurringTransactionId: serializer.fromJson( json['recurringTransactionId'], ), + externalIdentifier: serializer.fromJson(json['externalIdentifier']), updatedAt: serializer.fromJson(json['updatedAt']), ); } @@ -1031,6 +1200,7 @@ class Transaction extends DataClass implements Insertable { 'checked': serializer.toJson(checked), 'accountId': serializer.toJson(accountId), 'recurringTransactionId': serializer.toJson(recurringTransactionId), + 'externalIdentifier': serializer.toJson(externalIdentifier), 'updatedAt': serializer.toJson(updatedAt), }; } @@ -1043,6 +1213,7 @@ class Transaction extends DataClass implements Insertable { bool? checked, int? accountId, Value recurringTransactionId = const Value.absent(), + Value externalIdentifier = const Value.absent(), DateTime? updatedAt, }) => Transaction( id: id ?? this.id, @@ -1054,6 +1225,9 @@ class Transaction extends DataClass implements Insertable { recurringTransactionId: recurringTransactionId.present ? recurringTransactionId.value : this.recurringTransactionId, + externalIdentifier: externalIdentifier.present + ? externalIdentifier.value + : this.externalIdentifier, updatedAt: updatedAt ?? this.updatedAt, ); Transaction copyWithCompanion(TransactionsCompanion data) { @@ -1067,6 +1241,9 @@ class Transaction extends DataClass implements Insertable { recurringTransactionId: data.recurringTransactionId.present ? data.recurringTransactionId.value : this.recurringTransactionId, + externalIdentifier: data.externalIdentifier.present + ? data.externalIdentifier.value + : this.externalIdentifier, updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, ); } @@ -1080,7 +1257,8 @@ class Transaction extends DataClass implements Insertable { ..write('amount: $amount, ') ..write('checked: $checked, ') ..write('accountId: $accountId, ') - ..write('recurringTransactionId: $recurringTransactionId, ') + ..write('recurringTransactionId: $recurringTransactionId, ')..write( + 'externalIdentifier: $externalIdentifier, ') ..write('updatedAt: $updatedAt') ..write(')')) .toString(); @@ -1095,6 +1273,7 @@ class Transaction extends DataClass implements Insertable { checked, accountId, recurringTransactionId, + externalIdentifier, updatedAt, ); @override @@ -1108,6 +1287,7 @@ class Transaction extends DataClass implements Insertable { other.checked == this.checked && other.accountId == this.accountId && other.recurringTransactionId == this.recurringTransactionId && + other.externalIdentifier == this.externalIdentifier && other.updatedAt == this.updatedAt); } @@ -1119,6 +1299,7 @@ class TransactionsCompanion extends UpdateCompanion { final Value checked; final Value accountId; final Value recurringTransactionId; + final Value externalIdentifier; final Value updatedAt; const TransactionsCompanion({ this.id = const Value.absent(), @@ -1128,6 +1309,7 @@ class TransactionsCompanion extends UpdateCompanion { this.checked = const Value.absent(), this.accountId = const Value.absent(), this.recurringTransactionId = const Value.absent(), + this.externalIdentifier = const Value.absent(), this.updatedAt = const Value.absent(), }); TransactionsCompanion.insert({ @@ -1138,6 +1320,7 @@ class TransactionsCompanion extends UpdateCompanion { this.checked = const Value.absent(), required int accountId, this.recurringTransactionId = const Value.absent(), + this.externalIdentifier = const Value.absent(), this.updatedAt = const Value.absent(), }) : accountId = Value(accountId); static Insertable custom({ @@ -1148,6 +1331,7 @@ class TransactionsCompanion extends UpdateCompanion { Expression? checked, Expression? accountId, Expression? recurringTransactionId, + Expression? externalIdentifier, Expression? updatedAt, }) { return RawValuesInsertable({ @@ -1159,6 +1343,7 @@ class TransactionsCompanion extends UpdateCompanion { if (accountId != null) 'account_id': accountId, if (recurringTransactionId != null) 'recurring_transaction_id': recurringTransactionId, + if (externalIdentifier != null) 'external_identifier': externalIdentifier, if (updatedAt != null) 'updated_at': updatedAt, }); } @@ -1171,6 +1356,7 @@ class TransactionsCompanion extends UpdateCompanion { Value? checked, Value? accountId, Value? recurringTransactionId, + Value? externalIdentifier, Value? updatedAt, }) { return TransactionsCompanion( @@ -1182,6 +1368,7 @@ class TransactionsCompanion extends UpdateCompanion { accountId: accountId ?? this.accountId, recurringTransactionId: recurringTransactionId ?? this.recurringTransactionId, + externalIdentifier: externalIdentifier ?? this.externalIdentifier, updatedAt: updatedAt ?? this.updatedAt, ); } @@ -1212,6 +1399,9 @@ class TransactionsCompanion extends UpdateCompanion { recurringTransactionId.value, ); } + if (externalIdentifier.present) { + map['external_identifier'] = Variable(externalIdentifier.value); + } if (updatedAt.present) { map['updated_at'] = Variable(updatedAt.value); } @@ -1227,7 +1417,8 @@ class TransactionsCompanion extends UpdateCompanion { ..write('amount: $amount, ') ..write('checked: $checked, ') ..write('accountId: $accountId, ') - ..write('recurringTransactionId: $recurringTransactionId, ') + ..write('recurringTransactionId: $recurringTransactionId, ')..write( + 'externalIdentifier: $externalIdentifier, ') ..write('updatedAt: $updatedAt') ..write(')')) .toString(); @@ -1566,12 +1757,14 @@ typedef $$AccountsTableCreateCompanionBuilder = AccountsCompanion Function({ Value id, Value name, + Value externalIdentifier, Value updatedAt, }); typedef $$AccountsTableUpdateCompanionBuilder = AccountsCompanion Function({ Value id, Value name, + Value externalIdentifier, Value updatedAt, }); @@ -1645,6 +1838,11 @@ class $$AccountsTableFilterComposer builder: (column) => ColumnFilters(column), ); + ColumnFilters get externalIdentifier => $composableBuilder( + column: $table.externalIdentifier, + builder: (column) => ColumnFilters(column), + ); + ColumnFilters get updatedAt => $composableBuilder( column: $table.updatedAt, builder: (column) => ColumnFilters(column), @@ -1721,6 +1919,11 @@ class $$AccountsTableOrderingComposer builder: (column) => ColumnOrderings(column), ); + ColumnOrderings get externalIdentifier => $composableBuilder( + column: $table.externalIdentifier, + builder: (column) => ColumnOrderings(column), + ); + ColumnOrderings get updatedAt => $composableBuilder( column: $table.updatedAt, builder: (column) => ColumnOrderings(column), @@ -1742,6 +1945,11 @@ class $$AccountsTableAnnotationComposer GeneratedColumn get name => $composableBuilder(column: $table.name, builder: (column) => column); + GeneratedColumn get externalIdentifier => $composableBuilder( + column: $table.externalIdentifier, + builder: (column) => column, + ); + GeneratedColumn get updatedAt => $composableBuilder(column: $table.updatedAt, builder: (column) => column); @@ -1830,16 +2038,24 @@ class $$AccountsTableTableManager ({ Value id = const Value.absent(), Value name = const Value.absent(), + Value externalIdentifier = const Value.absent(), Value updatedAt = const Value.absent(), - }) => AccountsCompanion(id: id, name: name, updatedAt: updatedAt), + }) => AccountsCompanion( + id: id, + name: name, + externalIdentifier: externalIdentifier, + updatedAt: updatedAt, + ), createCompanionCallback: ({ Value id = const Value.absent(), Value name = const Value.absent(), + Value externalIdentifier = const Value.absent(), Value updatedAt = const Value.absent(), }) => AccountsCompanion.insert( id: id, name: name, + externalIdentifier: externalIdentifier, updatedAt: updatedAt, ), withReferenceMapper: (p0) => p0 @@ -1936,6 +2152,7 @@ typedef $$RecurringTransactionsTableCreateCompanionBuilder = required TimeFrameEnum timeFrame, Value amount, required int accountId, + Value externalIdentifier, Value updatedAt, }); typedef $$RecurringTransactionsTableUpdateCompanionBuilder = @@ -1946,6 +2163,7 @@ typedef $$RecurringTransactionsTableUpdateCompanionBuilder = Value timeFrame, Value amount, Value accountId, + Value externalIdentifier, Value updatedAt, }); @@ -2042,6 +2260,11 @@ class $$RecurringTransactionsTableFilterComposer builder: (column) => ColumnFilters(column), ); + ColumnFilters get externalIdentifier => $composableBuilder( + column: $table.externalIdentifier, + builder: (column) => ColumnFilters(column), + ); + ColumnFilters get updatedAt => $composableBuilder( column: $table.updatedAt, builder: (column) => ColumnFilters(column), @@ -2130,6 +2353,11 @@ class $$RecurringTransactionsTableOrderingComposer builder: (column) => ColumnOrderings(column), ); + ColumnOrderings get externalIdentifier => $composableBuilder( + column: $table.externalIdentifier, + builder: (column) => ColumnOrderings(column), + ); + ColumnOrderings get updatedAt => $composableBuilder( column: $table.updatedAt, builder: (column) => ColumnOrderings(column), @@ -2183,6 +2411,11 @@ class $$RecurringTransactionsTableAnnotationComposer GeneratedColumn get amount => $composableBuilder(column: $table.amount, builder: (column) => column); + GeneratedColumn get externalIdentifier => $composableBuilder( + column: $table.externalIdentifier, + builder: (column) => column, + ); + GeneratedColumn get updatedAt => $composableBuilder(column: $table.updatedAt, builder: (column) => column); @@ -2280,6 +2513,7 @@ class $$RecurringTransactionsTableTableManager Value timeFrame = const Value.absent(), Value amount = const Value.absent(), Value accountId = const Value.absent(), + Value externalIdentifier = const Value.absent(), Value updatedAt = const Value.absent(), }) => RecurringTransactionsCompanion( id: id, @@ -2288,6 +2522,7 @@ class $$RecurringTransactionsTableTableManager timeFrame: timeFrame, amount: amount, accountId: accountId, + externalIdentifier: externalIdentifier, updatedAt: updatedAt, ), createCompanionCallback: @@ -2298,6 +2533,7 @@ class $$RecurringTransactionsTableTableManager required TimeFrameEnum timeFrame, Value amount = const Value.absent(), required int accountId, + Value externalIdentifier = const Value.absent(), Value updatedAt = const Value.absent(), }) => RecurringTransactionsCompanion.insert( id: id, @@ -2306,6 +2542,7 @@ class $$RecurringTransactionsTableTableManager timeFrame: timeFrame, amount: amount, accountId: accountId, + externalIdentifier: externalIdentifier, updatedAt: updatedAt, ), withReferenceMapper: (p0) => p0 @@ -2412,6 +2649,7 @@ typedef $$TransactionsTableCreateCompanionBuilder = Value checked, required int accountId, Value recurringTransactionId, + Value externalIdentifier, Value updatedAt, }); typedef $$TransactionsTableUpdateCompanionBuilder = @@ -2423,6 +2661,7 @@ typedef $$TransactionsTableUpdateCompanionBuilder = Value checked, Value accountId, Value recurringTransactionId, + Value externalIdentifier, Value updatedAt, }); @@ -2510,6 +2749,11 @@ class $$TransactionsTableFilterComposer builder: (column) => ColumnFilters(column), ); + ColumnFilters get externalIdentifier => $composableBuilder( + column: $table.externalIdentifier, + builder: (column) => ColumnFilters(column), + ); + ColumnFilters get updatedAt => $composableBuilder( column: $table.updatedAt, builder: (column) => ColumnFilters(column), @@ -2597,6 +2841,11 @@ class $$TransactionsTableOrderingComposer builder: (column) => ColumnOrderings(column), ); + ColumnOrderings get externalIdentifier => $composableBuilder( + column: $table.externalIdentifier, + builder: (column) => ColumnOrderings(column), + ); + ColumnOrderings get updatedAt => $composableBuilder( column: $table.updatedAt, builder: (column) => ColumnOrderings(column), @@ -2674,6 +2923,11 @@ class $$TransactionsTableAnnotationComposer GeneratedColumn get checked => $composableBuilder(column: $table.checked, builder: (column) => column); + GeneratedColumn get externalIdentifier => $composableBuilder( + column: $table.externalIdentifier, + builder: (column) => column, + ); + GeneratedColumn get updatedAt => $composableBuilder(column: $table.updatedAt, builder: (column) => column); @@ -2760,6 +3014,7 @@ class $$TransactionsTableTableManager Value checked = const Value.absent(), Value accountId = const Value.absent(), Value recurringTransactionId = const Value.absent(), + Value externalIdentifier = const Value.absent(), Value updatedAt = const Value.absent(), }) => TransactionsCompanion( id: id, @@ -2769,6 +3024,7 @@ class $$TransactionsTableTableManager checked: checked, accountId: accountId, recurringTransactionId: recurringTransactionId, + externalIdentifier: externalIdentifier, updatedAt: updatedAt, ), createCompanionCallback: @@ -2780,6 +3036,7 @@ class $$TransactionsTableTableManager Value checked = const Value.absent(), required int accountId, Value recurringTransactionId = const Value.absent(), + Value externalIdentifier = const Value.absent(), Value updatedAt = const Value.absent(), }) => TransactionsCompanion.insert( id: id, @@ -2789,6 +3046,7 @@ class $$TransactionsTableTableManager checked: checked, accountId: accountId, recurringTransactionId: recurringTransactionId, + externalIdentifier: externalIdentifier, updatedAt: updatedAt, ), withReferenceMapper: (p0) => p0 diff --git a/lib/Repositories/transaction_repository.dart b/lib/Repositories/transaction_repository.dart index 6474b25..7aeb71b 100644 --- a/lib/Repositories/transaction_repository.dart +++ b/lib/Repositories/transaction_repository.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:drift/drift.dart'; import '../Entities/drift_database.dart'; @@ -29,19 +31,29 @@ class TransactionRepository { } /// Aktualisiert eine Transaktion in der Datenbank - Future update(final TransactionsCompanion transaction) { + Future update(final TransactionsCompanion transaction) async { + final Transaction? transactionInDb = await find(transaction.id.value); + final Transaction? transactionData = transactionInDb?.copyWithCompanion( + transaction, + ); + final DateTime date = transaction.date.value!.add( transaction.date.value!.timeZoneOffset, ); final TransactionsCompanion transactionToUpdate = TransactionsCompanion( - id: transaction.id, - name: transaction.name, + id: Value(transactionData?.id ?? transaction.id.value), + name: Value(transactionData?.name ?? transaction.name.value), date: Value(date), - amount: transaction.amount, - checked: transaction.checked, - accountId: transaction.accountId, - recurringTransactionId: transaction.recurringTransactionId, + amount: Value(transactionData?.amount ?? transaction.amount.value), + checked: const Value(true), + accountId: Value( + transactionData?.accountId ?? transaction.accountId.value, + ), + recurringTransactionId: Value( + transactionData?.recurringTransactionId ?? + transaction.recurringTransactionId.value, + ), updatedAt: Value(DateTime.now()), ); @@ -69,6 +81,7 @@ class TransactionRepository { final double? amountMin, final double? amountMax, final Account? account, + final bool? checked, final RecurringTransaction? recurringTransaction, final int? limit, final int? offset, @@ -113,6 +126,10 @@ class TransactionRepository { query.where((final t) => t.accountId.equals(account.id)); } + if (checked != null) { + query.where((final t) => t.checked.equals(checked)); + } + if (recurringTransaction != null) { query.where( (final t) => t.recurringTransactionId.equals(recurringTransaction.id), @@ -262,4 +279,16 @@ class TransactionRepository { return result; } + + /// Markiert die übergebenen Transaktionen als überprüft + void markTransactionsAsChecked(final List transactions) { + for (final value in transactions) { + final TransactionsCompanion transaction = TransactionsCompanion( + id: Value(value.id), + date: Value(value.date), + checked: const Value(true), + ); + unawaited(update(transaction)); + } + } } diff --git a/lib/Services/initializer.dart b/lib/Services/initializer.dart new file mode 100644 index 0000000..bf328f8 --- /dev/null +++ b/lib/Services/initializer.dart @@ -0,0 +1,25 @@ +import 'dart:async'; + +/// Ein Service zur vereinfachung, um darauf zu warten, +/// dass etwas Initialisiert wurde +class Initializer { + bool _initialized = false; + final Completer _initializedCompleter = Completer(); + + /// Gibt zurück, ob bereits initialisiert wurde + bool get initialized => _initialized; + + /// Auf diese Funktion kann gewartet werden, + /// bis [Initializer] initialisiert wurde + Future waitUntilInitialized() => _initializedCompleter.future; + + /// Setzt den [Initializer] auf initialisiert + void setInitialized() { + if (_initialized) { + return; + } + + _initialized = true; + _initializedCompleter.complete(); + } +} diff --git a/lib/Services/transaction_service.dart b/lib/Services/transaction_service.dart new file mode 100644 index 0000000..a17af6f --- /dev/null +++ b/lib/Services/transaction_service.dart @@ -0,0 +1,29 @@ +import 'dart:convert'; + +import '../Entities/drift_database.dart'; + +/// Ein Service um Transaktionen zu verarbeiten +class TransactionService { + /// Wandelt die übergebenen Transaktionen in einen String um + static String transactionsToString(final List transactions) { + final List> jsonTransactions = transactions + .map((final value) => value.toJson()) + .toList(); + + return jsonEncode(jsonTransactions); + } + + /// Wandelt den String in eine Liste von Transaktionen um + static List transactionsFromString(final String? transactions) { + if (transactions == null) { + return []; + } + + try { + final List decoded = jsonDecode(transactions); + return decoded.map((final item) => Transaction.fromJson(item)).toList(); + } on Exception { + return []; + } + } +} diff --git a/lib/Tasks/background_init.dart b/lib/Tasks/BackgroundHandler/background_init.dart similarity index 91% rename from lib/Tasks/background_init.dart rename to lib/Tasks/BackgroundHandler/background_init.dart index abf86af..ab71aa5 100644 --- a/lib/Tasks/background_init.dart +++ b/lib/Tasks/BackgroundHandler/background_init.dart @@ -1,7 +1,7 @@ import 'package:flutter/services.dart'; import 'package:logger/logger.dart'; -import '../Controller/port_controller.dart'; +import '../../Controller/port_controller.dart'; /// Initialisiert benötigte Services in Background-Isolates Future initBackground() async { diff --git a/lib/Tasks/background_init_web.dart b/lib/Tasks/BackgroundHandler/background_init_web.dart similarity index 100% rename from lib/Tasks/background_init_web.dart rename to lib/Tasks/BackgroundHandler/background_init_web.dart diff --git a/lib/Tasks/workers.dart b/lib/Tasks/BackgroundHandler/workers.dart similarity index 85% rename from lib/Tasks/workers.dart rename to lib/Tasks/BackgroundHandler/workers.dart index 0ce13a2..8a44ec8 100644 --- a/lib/Tasks/workers.dart +++ b/lib/Tasks/BackgroundHandler/workers.dart @@ -2,8 +2,9 @@ import 'dart:async'; import 'package:isolate_manager/isolate_manager.dart'; +import '../generate_transactions_task.dart'; +import '../show_notifications_task.dart'; import 'background_init_web.dart' if (dart.library.io) 'background_init.dart'; -import 'generate_transactions_task.dart'; @pragma('vm:entry-point') @isolateManagerWorker @@ -32,6 +33,8 @@ Future executeTask( switch (taskName) { case 'generate_transactions': return GenerateTransactionsTask().execute(); + case 'show_notifications': + return ShowNotificationsTask().execute(); } return Future.value(true); diff --git a/lib/Tasks/workmanager_workers.dart b/lib/Tasks/BackgroundHandler/workmanager_workers.dart similarity index 100% rename from lib/Tasks/workmanager_workers.dart rename to lib/Tasks/BackgroundHandler/workmanager_workers.dart diff --git a/lib/Tasks/show_notifications_task.dart b/lib/Tasks/show_notifications_task.dart new file mode 100644 index 0000000..90068a9 --- /dev/null +++ b/lib/Tasks/show_notifications_task.dart @@ -0,0 +1,28 @@ +import 'package:logger/logger.dart'; + +import '../Controller/local_notifications.dart'; +import '../Entities/drift_database.dart'; +import '../Repositories/transaction_repository.dart'; +import 'task.dart'; + +/// Zeigt Benachrichtigungen für nicht überprüfte Transaktionen an. +class ShowNotificationsTask extends Task { + final TransactionRepository _transactionRepository = TransactionRepository(); + + @override + Future execute() async { + final List transactions = await _transactionRepository.findBy( + checked: false, + dateTo: DateTime.now(), + ); + + if (transactions.isNotEmpty) { + Logger().i('Showing notification for unchecked transactions...'); + await LocalNotifications().showTransactionsToCheckNotification( + transactions, + ); + } + + return Future.value(true); + } +} diff --git a/lib/main.dart b/lib/main.dart index 1a884c2..eed6689 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,8 +1,10 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:routemaster/routemaster.dart'; import 'Controller/background_task_controller.dart'; +import 'Controller/local_notifications.dart'; import 'Controller/port_controller.dart'; import 'Services/navigation_service.dart'; import 'Services/router_service.dart'; @@ -14,6 +16,10 @@ void main() { PortController(); BackgroundTaskController(); + if (!kIsWeb) { + LocalNotifications(); + } + runApp( MaterialApp.router( routerDelegate: RoutemasterDelegate(