Feat: Fügt die Liste für wiederkehrende Transaktionen hinzu

This commit is contained in:
2025-12-28 02:26:58 +01:00
parent 8d7f6bc4d3
commit 0c4c6d7c3d
14 changed files with 594 additions and 104 deletions

View File

@@ -3,10 +3,10 @@ import 'dart:async';
import 'package:drift/drift.dart';
import 'package:flutter/material.dart';
import '../Entities/dialog_type_enum.dart';
import '../Entities/drift_database.dart';
import '../Pages/Dialog/dialog_action.dart';
import '../Pages/Dialog/dialog_input_field.dart';
import '../Pages/Dialog/dialog_type_enum.dart';
import '../Pages/Dialog/dynamic_dialog.dart';
import '../Repositories/account_repository.dart';

View File

@@ -0,0 +1,310 @@
import 'dart:async';
import 'package:drift/drift.dart';
import 'package:flutter/material.dart';
import '../Entities/drift_database.dart';
import '../Entities/time_frame_enum.dart';
import '../Pages/Dialog/dialog_action.dart';
import '../Pages/Dialog/dialog_input_field.dart';
import '../Pages/Dialog/dialog_input_field_select_item.dart';
import '../Pages/Dialog/dialog_input_field_type_enum.dart';
import '../Pages/Dialog/dialog_type_enum.dart';
import '../Pages/Dialog/dynamic_dialog.dart';
import '../Repositories/recurring_transacation_repository.dart';
import 'account_controller.dart';
/// Steuert die Interaktion mit den wiederkehrenden Transaktionen
class RecurringTransactionController {
/// Gibt die aktuell gültige Instanz der Klasse zurück
factory RecurringTransactionController() => _instance;
/// Erstellt eine neue Instanz dieser Klasse
RecurringTransactionController._internal() {
_newRecurringTransactionDialog = DynamicDialog(
title: 'Neue wiederkehrende Transaktion erstellen',
icon: Icons.repeat,
inputFields: [
const DialogInputField(id: 'name', label: 'Name', autoFocus: true),
const DialogInputField(
id: 'startDate',
label: 'Anfangsdatum',
keyboardType: TextInputType.datetime,
inputType: DialogInputFieldTypeEnum.date,
),
DialogInputField(
id: 'timeFrame',
label: 'Zeitraum',
inputType: DialogInputFieldTypeEnum.select,
selectItems: TimeFrameEnum.values
.map(
(final value) => DialogInputFieldSelectItem(
id: value.index,
value: value.name,
),
)
.toList(),
),
const DialogInputField(
id: 'amount',
label: 'Betrag €',
keyboardType: TextInputType.number,
),
],
actions: [
DialogAction(label: 'Abbruch'),
DialogAction(
label: 'Speichern',
isPrimary: true,
onPressed: _saveNewRecurringTransaction,
),
],
);
_errorRecurringTransactionValueEmptyDialog = DynamicDialog(
title: 'Fehler!',
icon: Icons.error,
content: const Text('Es wurden nicht alle Werte eingetragen!'),
dialogType: DialogTypeEnum.error,
);
_recurringTransactionCreatedDialog = DynamicDialog(
title: 'Wiederkehrende Transaktion erstellt!',
icon: Icons.check_circle,
content: const Text(
'Die wiederkehrende Transaktion wurde erfolgreich erstellt!',
),
dialogType: DialogTypeEnum.success,
);
_selectedAccount = _accountController.selected.value;
_accountController.selected.addListener(() {
_selectedAccount = _accountController.selected.value;
unawaited(updateRecurringTransactions());
});
unawaited(updateRecurringTransactions());
}
static final RecurringTransactionController _instance =
RecurringTransactionController._internal();
final RecurringTransactionRepository _recurringTransactionRepository =
RecurringTransactionRepository();
final AccountController _accountController = AccountController();
DynamicDialog? _newRecurringTransactionDialog;
DynamicDialog? _errorRecurringTransactionValueEmptyDialog;
DynamicDialog? _recurringTransactionCreatedDialog;
final ValueNotifier<List<RecurringTransaction>> _recurringTransactions =
ValueNotifier<List<RecurringTransaction>>([]);
/// Stellt die Liste der wiederkehrenden Transaktionen dar
ValueNotifier<List<RecurringTransaction>> get recurringTransactions {
if (_recurringTransactions.value == []) {
unawaited(updateRecurringTransactions());
}
return _recurringTransactions;
}
set recurringTransactions(
final List<RecurringTransaction> recurringTransactions,
) {
_recurringTransactions.value = recurringTransactions;
}
Account? _selectedAccount;
/// Gibt die gespeicherten wiederkehrenden Transaktionen als Liste zurück
Future<void> updateRecurringTransactions() async {
if (_selectedAccount != null) {
final List<RecurringTransaction> recurringTransactions =
await _recurringTransactionRepository.findBy(
account: _selectedAccount,
orderBy: 'nameAsc',
);
_recurringTransactions.value = recurringTransactions;
}
}
/// Startet den Prozess, um eine neue wiederkehrende Transaktion anzulegen
void newRecurringTransactionHandler() {
unawaited(_newRecurringTransactionDialog?.show());
}
/// Startet den Prozess, um eine neue wiederkehrende Transaktion zu bearbeiten
Future<void> editRecurringTransaction(
final int recurringTransactionId,
) async {
final RecurringTransaction? recurringTransaction =
await _recurringTransactionRepository.find(recurringTransactionId);
if (recurringTransaction != null) {
final editRecurringTransactionDialog = DynamicDialog(
title: '${recurringTransaction.name} umbenennen',
icon: Icons.edit,
inputFields: [
DialogInputField(
id: 'name',
label: 'Name',
autoFocus: true,
initialValue: recurringTransaction.name,
),
DialogInputField(
id: 'startDate',
label: 'Anfangsdatum',
keyboardType: TextInputType.datetime,
inputType: DialogInputFieldTypeEnum.date,
initialValue: recurringTransaction.startDate,
),
DialogInputField(
id: 'timeFrame',
label: 'Zeitraum',
inputType: DialogInputFieldTypeEnum.select,
selectItems: TimeFrameEnum.values
.map(
(final value) => DialogInputFieldSelectItem(
id: value.index,
value: value.name,
),
)
.toList(),
initialValue: recurringTransaction.timeFrame,
),
DialogInputField(
id: 'amount',
label: 'Betrag €',
keyboardType: TextInputType.number,
initialValue: recurringTransaction.amount.toString(),
),
],
actions: [
DialogAction(label: 'Abbruch'),
DialogAction(
label: 'Speichern',
isPrimary: true,
onPressed: _editRecurringTransaction,
),
],
hiddenValues: {'recurringTransaction': recurringTransaction},
);
unawaited(editRecurringTransactionDialog.show());
}
}
/// Startet den Prozess, um eine wiederkehrende Transaktion zu löschen
Future<void> deleteRecurringTransactionHandler(
final int recurringTransactionId,
) async {
final RecurringTransaction? recurringTransaction =
await _recurringTransactionRepository.find(recurringTransactionId);
if (recurringTransaction != null) {
final deleteRecurringTransactionDialog = DynamicDialog(
dialogType: DialogTypeEnum.error,
title: '${recurringTransaction.name} löschen',
content: Text(
'Willst du ${recurringTransaction.name} wirklich löschen?',
),
icon: Icons.delete_forever,
actions: [
DialogAction(label: 'Abbruch', isPrimary: true),
DialogAction(
label: 'Wiederkehrende Transaktion löschen',
onPressed: _deleteRecurringTransaction,
),
],
hiddenValues: {'recurringTransaction': recurringTransaction},
);
unawaited(deleteRecurringTransactionDialog.show());
}
}
Future<void> _saveNewRecurringTransaction(
final Map<String, dynamic> values,
) async {
if (values['name'] == null ||
values['name'] == '' ||
values['startDate'] == null ||
values['startDate'] == '' ||
values['timeFrame'] == null ||
values['timeFrame'] == '' ||
values['amount'] == null ||
values['amount'] == '' ||
_selectedAccount == null) {
await _errorRecurringTransactionValueEmptyDialog?.show();
} else {
final DialogInputFieldSelectItem timeFrame = values['timeFrame'];
values['timeFrame'] = TimeFrameEnum.values[timeFrame.id];
final String amount = values['amount'];
values['amount'] = double.tryParse(amount);
if (values['amount'] == null || values['amount'] == 0) {
await _errorRecurringTransactionValueEmptyDialog?.show();
} else {
final recurringTransaction = RecurringTransactionsCompanion(
name: Value(values['name']),
startDate: Value(values['startDate']),
timeFrame: Value(values['timeFrame']),
amount: Value(values['amount']),
accountId: Value(_selectedAccount!.id),
);
await _recurringTransactionRepository.add(recurringTransaction);
await _recurringTransactionCreatedDialog?.show();
await updateRecurringTransactions();
}
}
}
Future<void> _editRecurringTransaction(
final Map<String, dynamic> values,
) async {
if (values['recurringTransaction'] != null &&
values['recurringTransaction'] != null &&
values['startDate'] != null &&
values['startDate'] != '' &&
values['timeFrame'] != null &&
values['timeFrame'] != '' &&
values['amount'] != null &&
values['amount'] != '') {
final DialogInputFieldSelectItem timeFrame = values['timeFrame'];
values['timeFrame'] = TimeFrameEnum.values[timeFrame.id];
final String amount = values['amount'];
values['amount'] = double.tryParse(amount);
if (values['amount'] != null && values['amount'] != 0) {
final RecurringTransaction recurringTransaction =
values['recurringTransaction'];
final rtc = RecurringTransactionsCompanion(
id: Value(recurringTransaction.id),
name: Value(values['name']),
startDate: Value(values['startDate']),
timeFrame: Value(values['timeFrame']),
amount: Value(values['amount']),
accountId: Value(recurringTransaction.accountId),
);
await _recurringTransactionRepository.update(rtc);
await updateRecurringTransactions();
}
}
}
Future<void> _deleteRecurringTransaction(
final Map<String, dynamic> values,
) async {
if (values['recurringTransaction'] != null) {
final RecurringTransaction recurringTransaction =
values['recurringTransaction'];
await _recurringTransactionRepository.remove(recurringTransaction);
await updateRecurringTransactions();
}
}
}

View File

@@ -1,5 +1,8 @@
import 'package:flutter/material.dart';
import 'dialog_input_field_select_item.dart';
import 'dialog_input_field_type_enum.dart';
/// Ein Input-Feld für den dynamischen Dialog
class DialogInputField {
/// Erstellt ein neues Input-Feld
@@ -11,6 +14,8 @@ class DialogInputField {
this.obscureText = false,
this.autoFocus = false,
this.onChanged,
this.inputType = DialogInputFieldTypeEnum.text,
this.selectItems = const [],
});
/// Die Id des InputFelds
@@ -20,7 +25,7 @@ class DialogInputField {
final String? label;
/// Der initiale Wert des InputFelds
final String? initialValue;
final dynamic initialValue;
/// Der InputTyp des InputFeld
final TextInputType keyboardType;
@@ -32,5 +37,11 @@ class DialogInputField {
final bool autoFocus;
/// Was bei Veränderung des Textfeldes geschehen soll
final ValueChanged<String>? onChanged;
final ValueChanged<dynamic>? onChanged;
/// Stellt den Eingabetypen des Inputfeldes dar
final DialogInputFieldTypeEnum inputType;
/// Die Auswahlmöglichkeiten für den Select-Input-Typen
final List<DialogInputFieldSelectItem> selectItems;
}

View File

@@ -0,0 +1,11 @@
/// Eine Klasse, um den DialogInputFeldern des Select-Typs die Werte zu geben
class DialogInputFieldSelectItem {
/// Erstellt eine neue Instanz dieser Klasse
DialogInputFieldSelectItem({required this.id, required this.value});
/// Die id des Feldes
final int id;
/// Der Wert des Feldes
final String value;
}

View File

@@ -0,0 +1,13 @@
import 'dialog_input_field.dart';
/// Eine Enum um [DialogInputField] sagen zu können, welcher Typ er ist
enum DialogInputFieldTypeEnum {
/// Der Standard Text-Typ
text,
/// Der Datums-Typ
date,
/// Ein Select-Feld
select,
}

View File

@@ -1,10 +1,14 @@
import 'package:dropdown_search/dropdown_search.dart';
import 'package:flutter/material.dart';
import '../../Entities/dialog_type_enum.dart';
import '../../Services/navigation_service.dart';
import '../../Services/theme_service.dart';
import '../Misc/dynamic_date_time_field.dart';
import 'dialog_action.dart';
import 'dialog_input_field.dart';
import 'dialog_input_field_select_item.dart';
import 'dialog_input_field_type_enum.dart';
import 'dialog_type_enum.dart';
/// Erstellt einen neuen dynamischen Dialog
class DynamicDialog {
@@ -19,9 +23,10 @@ class DynamicDialog {
this.borderRadius = 16,
this.barrierDismissible = true,
this.dialogType = DialogTypeEnum.info,
this.hiddenValues
this.hiddenValues,
}) : inputFields = inputFields ?? const [],
actions = actions ?? [DialogAction(label: 'Schließen')];
actions = actions ?? [DialogAction(label: 'Schließen')],
_values = hiddenValues ?? {};
/// Der Titel des Dialogs
final String? title;
@@ -53,36 +58,10 @@ class DynamicDialog {
/// Versteckte Werte, die beim Abschicken mit zurückgegeben werden
final Map<String, dynamic>? hiddenValues;
Map<String, TextEditingController>? _controllers;
Map<String, FocusNode>? _focusNodes;
final Map<String, dynamic> _values;
BuildContext? _dialogContext;
void _prepareControllers() {
_controllers = {
for (final field in inputFields)
field.id: TextEditingController(text: field.initialValue ?? ''),
};
}
void _prepareFocusNodes() {
_focusNodes = {for (final field in inputFields) field.id: FocusNode()};
}
void _disposeControllers() {
for (final TextEditingController controller in _controllers!.values) {
controller.dispose();
}
_controllers = null;
}
void _disposeFocusNodes() {
for (final FocusNode node in _focusNodes!.values) {
node.dispose();
}
_focusNodes = null;
}
/// Zeigt den vorher zusammengebauten Dialog an
Future<void> show() async {
final BuildContext? context = NavigationService.getCurrentBuildContext();
@@ -90,30 +69,13 @@ class DynamicDialog {
if (context != null) {
final ThemeData theme = Theme.of(context);
_prepareControllers();
_prepareFocusNodes();
await showDialog(
context: context,
barrierDismissible: barrierDismissible,
builder: (final BuildContext ctx) {
_dialogContext = ctx;
WidgetsBinding.instance.addPostFrameCallback((_) {
final DialogInputField? autoFocusField = inputFields
.where((final DialogInputField f) => f.autoFocus)
.cast<DialogInputField?>()
.firstWhere(
(final DialogInputField? f) => f != null,
orElse: () => null,
);
if (autoFocusField != null) {
_focusNodes![autoFocusField.id]!.requestFocus();
}
});
final DialogAction primaryAction = actions.firstWhere(
(final a) => a.isPrimary,
(final a) => a.isPrimary,
orElse: () => actions.first,
);
@@ -154,58 +116,23 @@ class DynamicDialog {
children: [
if (content != null) content!,
...inputFields.map(
(final DialogInputField field) =>
Padding(
padding: const EdgeInsets.symmetric(vertical: 6),
child: TextField(
controller: _controllers![field.id],
focusNode: _focusNodes![field.id],
keyboardType: field.keyboardType,
obscureText: field.obscureText,
onChanged: field.onChanged,
decoration: InputDecoration(
labelText: field.label,
border: const OutlineInputBorder(),
isDense: true,
),
onSubmitted: (_) {
final Map<String, dynamic> values = {
for (final entry in _controllers!.entries)
entry.key: entry.value.text,
};
hiddenValues?.forEach((final key, final value) {
values[key] = value;
});
close();
primaryAction.onPressed?.call(values);
},
),
),
(final DialogInputField field) => Padding(
padding: const EdgeInsets.symmetric(vertical: 6),
child: _getInputField(field, primaryAction),
),
),
],
),
actions: actions
.map(
(final action) =>
TextButton(
(final action) => TextButton(
onPressed: () {
final Map<String, dynamic> values = {
for (final entry in _controllers!.entries)
entry.key: entry.value.text,
};
hiddenValues?.forEach((final key, final value) {
values[key] = value;
});
close();
action.onPressed?.call(values);
action.onPressed?.call(_values);
},
child: Text(action.label),
),
)
)
.toList(),
);
},
@@ -218,8 +145,88 @@ class DynamicDialog {
if (_dialogContext != null) {
Navigator.of(_dialogContext!).pop();
}
}
_disposeControllers();
_disposeFocusNodes();
Widget _getInputField(
final DialogInputField inputField,
final DialogAction primaryAction,
) {
_values[inputField.id] = inputField.initialValue;
if (inputField.inputType == DialogInputFieldTypeEnum.date) {
return DynamicDateTimeField(
initialValue: inputField.initialValue,
autofocus: inputField.autoFocus,
onChanged: (final value) {
inputField.onChanged?.call(value);
_values[inputField.id] = value;
},
decoration: InputDecoration(
labelText: inputField.label,
border: const OutlineInputBorder(),
isDense: true,
),
);
} else if (inputField.inputType == DialogInputFieldTypeEnum.select) {
DialogInputFieldSelectItem? initialValue;
if (inputField.initialValue is Enum) {
final Enum inputFieldInitialValue = inputField.initialValue;
for (final DialogInputFieldSelectItem value in inputField.selectItems) {
if (value.id == inputFieldInitialValue.index) {
initialValue = value;
}
}
}
_values[inputField.id] = initialValue;
return DropdownSearch<DialogInputFieldSelectItem>(
items: (final f, final cs) => inputField.selectItems,
itemAsString: (final DialogInputFieldSelectItem value) => value.value,
selectedItem: initialValue,
onChanged: (final DialogInputFieldSelectItem? value) {
inputField.onChanged?.call(value);
_values[inputField.id] = value;
},
decoratorProps: DropDownDecoratorProps(
decoration: InputDecoration(
labelText: inputField.label,
border: const OutlineInputBorder(),
isDense: true,
),
),
compareFn:
(
final DialogInputFieldSelectItem v1,
final DialogInputFieldSelectItem v2,
) => v1.id == v2.id,
);
} else {
return TextField(
controller: TextEditingController(
text: inputField.initialValue is String
? inputField.initialValue
: '',
),
autofocus: inputField.autoFocus,
keyboardType: inputField.keyboardType,
obscureText: inputField.obscureText,
onChanged: (final value) {
inputField.onChanged?.call(value);
_values[inputField.id] = value;
},
decoration: InputDecoration(
labelText: inputField.label,
border: const OutlineInputBorder(),
isDense: true,
),
onSubmitted: (_) {
close();
primaryAction.onPressed?.call(_values);
},
);
}
}
}

View File

@@ -27,7 +27,7 @@ class _AccountSelectState extends State<AccountSelect> {
_accountController.selected.addListener(() {
setState(() {
if (context.mounted) {
if (mounted) {
_selected = _accountController.selected.value;
}
});
@@ -35,7 +35,7 @@ class _AccountSelectState extends State<AccountSelect> {
_accountController.accounts.addListener(() {
setState(() {
if (context.mounted) {
if (mounted) {
_accounts = _accountController.accounts.value;
}
});

View File

@@ -0,0 +1,60 @@
import 'package:date_field/date_field.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
/// Ein Feld mit Popup über welches man Datumsfelder auswählen kann
class DynamicDateTimeField extends StatefulWidget {
/// Initialisiert eine neue Instanz dieser Klasse
const DynamicDateTimeField({
this.initialValue,
this.mode = DateTimeFieldPickerMode.date,
this.autofocus = false,
this.onChanged,
this.decoration,
super.key,
});
final DateTime? initialValue;
/// Der Modus des Datums-Feldes
final DateTimeFieldPickerMode mode;
/// Ob das Feld automatisch ausgewählt werden soll
final bool autofocus;
/// Die Funktion, die bei Veränderung des Wertes aufgerufen wird
final Function(DateTime?)? onChanged;
/// Die Dekoration, wie das Feld aussehen soll
final InputDecoration? decoration;
@override
State<StatefulWidget> createState() => _DynamicDateTimeFieldState();
}
class _DynamicDateTimeFieldState extends State<DynamicDateTimeField> {
DateTime? _value;
@override
void initState() {
super.initState();
_value = widget.initialValue;
}
@override
Widget build(final BuildContext context) => DateTimeField(
initialPickerDateTime: widget.initialValue,
dateFormat: DateFormat('d.M.y'),
value: _value,
mode: widget.mode,
autofocus: widget.autofocus,
onChanged: (final value) {
widget.onChanged?.call(value);
setState(() {
_value = value;
});
},
decoration: widget.decoration,
);
}

View File

@@ -9,7 +9,7 @@ class EditableList extends StatelessWidget {
required this.name,
required this.items,
required this.onAdd,
required this.onRename,
required this.onEdit,
required this.onDelete,
this.icon,
this.addTooltip,
@@ -28,7 +28,7 @@ class EditableList extends StatelessWidget {
final void Function() onAdd;
/// Die Funktion, die beim umbenennen aufgerufen wird
final void Function(int) onRename;
final void Function(int) onEdit;
///Die Funktion, die beim Löschen aufgerufen wird
final void Function(int) onDelete;
@@ -72,14 +72,14 @@ class EditableList extends StatelessWidget {
trailing: PopupMenuButton<String>(
tooltip: menuTooltip,
onSelected: (final value) {
if (value == 'rename') {
onRename(items[index].id);
if (value == 'edit') {
onEdit(items[index].id);
} else if (value == 'delete') {
onDelete(items[index].id);
}
},
itemBuilder: (_) => const [
PopupMenuItem(value: 'rename', child: Text('Umbenennen')),
PopupMenuItem(value: 'edit', child: Text('Bearbeiten')),
PopupMenuItem(value: 'delete', child: Text('Entfernen')),
],
),

View File

@@ -26,7 +26,7 @@ class _AccountListState extends State<AccountList> {
_accountController.accounts.addListener(() {
setState(() {
if (context.mounted) {
if (mounted) {
_accounts = _accountController.accounts.value;
}
});
@@ -45,7 +45,7 @@ class _AccountListState extends State<AccountList> {
name: 'Konten',
items: formatedAccounts,
onAdd: _accountController.newAccountHandler,
onRename: _accountController.renameAccountHandler,
onEdit: _accountController.renameAccountHandler,
onDelete: _accountController.deleteAccountHandler,
icon: const Icon(Icons.account_balance_wallet),
addTooltip: 'Konto hinzufügen',

View File

@@ -0,0 +1,65 @@
import 'package:flutter/material.dart';
import '../../Controller/recurring_transaction_controller.dart';
import '../../Entities/drift_database.dart';
import '../../Entities/list_item.dart';
import '../Misc/editable_list.dart';
/// Ein Widget,
/// das die Liste mit vorhandenen wiederkehrenden Transaktionen anzeigt
class RecurringTransactionList extends StatefulWidget {
/// Erstellt eine neue Instanz dieser Klasse
const RecurringTransactionList({super.key});
@override
State<StatefulWidget> createState() => _RecurringTransactionListState();
}
class _RecurringTransactionListState extends State<RecurringTransactionList> {
final RecurringTransactionController _recurringTransactionController =
RecurringTransactionController();
List<RecurringTransaction> _recurringTransactions = [];
@override
void initState() {
super.initState();
_recurringTransactions =
_recurringTransactionController.recurringTransactions.value;
_recurringTransactionController.recurringTransactions.addListener(() {
setState(() {
if (mounted) {
_recurringTransactions =
_recurringTransactionController.recurringTransactions.value;
}
});
});
}
@override
Widget build(final BuildContext context) {
if (_recurringTransactions != []) {
final List<ListItem> formatedRecurringTransactions = [];
for (final RecurringTransaction data in _recurringTransactions) {
formatedRecurringTransactions.add(
ListItem(id: data.id, name: data.name),
);
}
return EditableList(
name: 'Wiederkehrende Transaktionen',
items: formatedRecurringTransactions,
onAdd: _recurringTransactionController.newRecurringTransactionHandler,
onEdit: _recurringTransactionController.editRecurringTransaction,
onDelete:
_recurringTransactionController.deleteRecurringTransactionHandler,
icon: const Icon(Icons.repeat),
addTooltip: 'Wiederkehrende Transaktion hinzufügen',
menuTooltip: 'Menü anzeigen',
);
} else {
return const Center(child: CircularProgressIndicator());
}
}
}

View File

@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'account_list.dart';
import 'recurring_transaction_list.dart';
/// Eine Widget-Klasse, die die Einstellungsseite der Anwendung darstellt.
class Settings extends StatefulWidget {
@@ -31,7 +32,9 @@ class _SettingsState extends State<Settings> {
const SizedBox(height: 24),
const AccountList(),
const SizedBox(height: 24),
const RecurringTransactionList(),
const SizedBox(height: 8),
_versionNumber(theme),
],

View File

@@ -18,6 +18,11 @@ class RecurringTransactionRepository {
return find(id);
}
/// Aktualisiert ein Konto in der Datenbank
Future<bool> update(
final RecurringTransactionsCompanion recurringTransaction,
) => _db.update(_db.recurringTransactions).replace(recurringTransaction);
/// Entfernt eine wiederkehrende Transaktion aus der Datenbank
Future<int> remove(final RecurringTransaction recurringTransaction) =>
(_db.delete(
@@ -43,6 +48,7 @@ class RecurringTransactionRepository {
final double? amount,
final double? amountMin,
final double? amountMax,
final Account? account,
final String? orderBy,
}) {
final SimpleSelectStatement<
@@ -93,6 +99,10 @@ class RecurringTransactionRepository {
query.where((final t) => t.amount.isSmallerThanValue(amountMax));
}
if (account != null) {
query.where((final t) => t.accountId.equals(account.id));
}
if (orderBy != null) {
switch (orderBy) {
case 'nameAsc':