Feat: Fügt Benachrichtigungen für nicht überprüfte Transaktionen hinzu

This commit is contained in:
2026-01-05 01:07:23 +01:00
parent 40eaca3157
commit 6f3d987d19
14 changed files with 676 additions and 31 deletions

View File

@@ -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,
}),
);
}
}
}
}

View File

@@ -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<void> _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<void> showTransactionsToCheckNotification(
final List<Transaction> 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>[
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<String> 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),
);
}
}
}

View File

@@ -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<Transaction> 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<Transaction>? 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());