Feat: Fügt die Liste für wiederkehrende Transaktionen hinzu
This commit is contained in:
@@ -3,10 +3,10 @@ import 'dart:async';
|
|||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import '../Entities/dialog_type_enum.dart';
|
|
||||||
import '../Entities/drift_database.dart';
|
import '../Entities/drift_database.dart';
|
||||||
import '../Pages/Dialog/dialog_action.dart';
|
import '../Pages/Dialog/dialog_action.dart';
|
||||||
import '../Pages/Dialog/dialog_input_field.dart';
|
import '../Pages/Dialog/dialog_input_field.dart';
|
||||||
|
import '../Pages/Dialog/dialog_type_enum.dart';
|
||||||
import '../Pages/Dialog/dynamic_dialog.dart';
|
import '../Pages/Dialog/dynamic_dialog.dart';
|
||||||
import '../Repositories/account_repository.dart';
|
import '../Repositories/account_repository.dart';
|
||||||
|
|
||||||
|
|||||||
310
lib/Controller/recurring_transaction_controller.dart
Normal file
310
lib/Controller/recurring_transaction_controller.dart
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
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
|
/// Ein Input-Feld für den dynamischen Dialog
|
||||||
class DialogInputField {
|
class DialogInputField {
|
||||||
/// Erstellt ein neues Input-Feld
|
/// Erstellt ein neues Input-Feld
|
||||||
@@ -11,6 +14,8 @@ class DialogInputField {
|
|||||||
this.obscureText = false,
|
this.obscureText = false,
|
||||||
this.autoFocus = false,
|
this.autoFocus = false,
|
||||||
this.onChanged,
|
this.onChanged,
|
||||||
|
this.inputType = DialogInputFieldTypeEnum.text,
|
||||||
|
this.selectItems = const [],
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Die Id des InputFelds
|
/// Die Id des InputFelds
|
||||||
@@ -20,7 +25,7 @@ class DialogInputField {
|
|||||||
final String? label;
|
final String? label;
|
||||||
|
|
||||||
/// Der initiale Wert des InputFelds
|
/// Der initiale Wert des InputFelds
|
||||||
final String? initialValue;
|
final dynamic initialValue;
|
||||||
|
|
||||||
/// Der InputTyp des InputFeld
|
/// Der InputTyp des InputFeld
|
||||||
final TextInputType keyboardType;
|
final TextInputType keyboardType;
|
||||||
@@ -32,5 +37,11 @@ class DialogInputField {
|
|||||||
final bool autoFocus;
|
final bool autoFocus;
|
||||||
|
|
||||||
/// Was bei Veränderung des Textfeldes geschehen soll
|
/// 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;
|
||||||
}
|
}
|
||||||
|
|||||||
11
lib/Pages/Dialog/dialog_input_field_select_item.dart
Normal file
11
lib/Pages/Dialog/dialog_input_field_select_item.dart
Normal 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;
|
||||||
|
}
|
||||||
13
lib/Pages/Dialog/dialog_input_field_type_enum.dart
Normal file
13
lib/Pages/Dialog/dialog_input_field_type_enum.dart
Normal 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,
|
||||||
|
}
|
||||||
@@ -1,10 +1,14 @@
|
|||||||
|
import 'package:dropdown_search/dropdown_search.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import '../../Entities/dialog_type_enum.dart';
|
|
||||||
import '../../Services/navigation_service.dart';
|
import '../../Services/navigation_service.dart';
|
||||||
import '../../Services/theme_service.dart';
|
import '../../Services/theme_service.dart';
|
||||||
|
import '../Misc/dynamic_date_time_field.dart';
|
||||||
import 'dialog_action.dart';
|
import 'dialog_action.dart';
|
||||||
import 'dialog_input_field.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
|
/// Erstellt einen neuen dynamischen Dialog
|
||||||
class DynamicDialog {
|
class DynamicDialog {
|
||||||
@@ -19,9 +23,10 @@ class DynamicDialog {
|
|||||||
this.borderRadius = 16,
|
this.borderRadius = 16,
|
||||||
this.barrierDismissible = true,
|
this.barrierDismissible = true,
|
||||||
this.dialogType = DialogTypeEnum.info,
|
this.dialogType = DialogTypeEnum.info,
|
||||||
this.hiddenValues
|
this.hiddenValues,
|
||||||
}) : inputFields = inputFields ?? const [],
|
}) : inputFields = inputFields ?? const [],
|
||||||
actions = actions ?? [DialogAction(label: 'Schließen')];
|
actions = actions ?? [DialogAction(label: 'Schließen')],
|
||||||
|
_values = hiddenValues ?? {};
|
||||||
|
|
||||||
/// Der Titel des Dialogs
|
/// Der Titel des Dialogs
|
||||||
final String? title;
|
final String? title;
|
||||||
@@ -53,36 +58,10 @@ class DynamicDialog {
|
|||||||
/// Versteckte Werte, die beim Abschicken mit zurückgegeben werden
|
/// Versteckte Werte, die beim Abschicken mit zurückgegeben werden
|
||||||
final Map<String, dynamic>? hiddenValues;
|
final Map<String, dynamic>? hiddenValues;
|
||||||
|
|
||||||
Map<String, TextEditingController>? _controllers;
|
final Map<String, dynamic> _values;
|
||||||
Map<String, FocusNode>? _focusNodes;
|
|
||||||
|
|
||||||
BuildContext? _dialogContext;
|
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
|
/// Zeigt den vorher zusammengebauten Dialog an
|
||||||
Future<void> show() async {
|
Future<void> show() async {
|
||||||
final BuildContext? context = NavigationService.getCurrentBuildContext();
|
final BuildContext? context = NavigationService.getCurrentBuildContext();
|
||||||
@@ -90,30 +69,13 @@ class DynamicDialog {
|
|||||||
if (context != null) {
|
if (context != null) {
|
||||||
final ThemeData theme = Theme.of(context);
|
final ThemeData theme = Theme.of(context);
|
||||||
|
|
||||||
_prepareControllers();
|
|
||||||
_prepareFocusNodes();
|
|
||||||
|
|
||||||
await showDialog(
|
await showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
barrierDismissible: barrierDismissible,
|
barrierDismissible: barrierDismissible,
|
||||||
builder: (final BuildContext ctx) {
|
builder: (final BuildContext ctx) {
|
||||||
_dialogContext = 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 DialogAction primaryAction = actions.firstWhere(
|
||||||
(final a) => a.isPrimary,
|
(final a) => a.isPrimary,
|
||||||
orElse: () => actions.first,
|
orElse: () => actions.first,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -154,58 +116,23 @@ class DynamicDialog {
|
|||||||
children: [
|
children: [
|
||||||
if (content != null) content!,
|
if (content != null) content!,
|
||||||
...inputFields.map(
|
...inputFields.map(
|
||||||
(final DialogInputField field) =>
|
(final DialogInputField field) => Padding(
|
||||||
Padding(
|
padding: const EdgeInsets.symmetric(vertical: 6),
|
||||||
padding: const EdgeInsets.symmetric(vertical: 6),
|
child: _getInputField(field, primaryAction),
|
||||||
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);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
actions: actions
|
actions: actions
|
||||||
.map(
|
.map(
|
||||||
(final action) =>
|
(final action) => TextButton(
|
||||||
TextButton(
|
|
||||||
onPressed: () {
|
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();
|
close();
|
||||||
action.onPressed?.call(values);
|
action.onPressed?.call(_values);
|
||||||
},
|
},
|
||||||
child: Text(action.label),
|
child: Text(action.label),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList(),
|
.toList(),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -218,8 +145,88 @@ class DynamicDialog {
|
|||||||
if (_dialogContext != null) {
|
if (_dialogContext != null) {
|
||||||
Navigator.of(_dialogContext!).pop();
|
Navigator.of(_dialogContext!).pop();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_disposeControllers();
|
Widget _getInputField(
|
||||||
_disposeFocusNodes();
|
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);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ class _AccountSelectState extends State<AccountSelect> {
|
|||||||
|
|
||||||
_accountController.selected.addListener(() {
|
_accountController.selected.addListener(() {
|
||||||
setState(() {
|
setState(() {
|
||||||
if (context.mounted) {
|
if (mounted) {
|
||||||
_selected = _accountController.selected.value;
|
_selected = _accountController.selected.value;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -35,7 +35,7 @@ class _AccountSelectState extends State<AccountSelect> {
|
|||||||
|
|
||||||
_accountController.accounts.addListener(() {
|
_accountController.accounts.addListener(() {
|
||||||
setState(() {
|
setState(() {
|
||||||
if (context.mounted) {
|
if (mounted) {
|
||||||
_accounts = _accountController.accounts.value;
|
_accounts = _accountController.accounts.value;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
60
lib/Pages/Misc/dynamic_date_time_field.dart
Normal file
60
lib/Pages/Misc/dynamic_date_time_field.dart
Normal 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,
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -9,7 +9,7 @@ class EditableList extends StatelessWidget {
|
|||||||
required this.name,
|
required this.name,
|
||||||
required this.items,
|
required this.items,
|
||||||
required this.onAdd,
|
required this.onAdd,
|
||||||
required this.onRename,
|
required this.onEdit,
|
||||||
required this.onDelete,
|
required this.onDelete,
|
||||||
this.icon,
|
this.icon,
|
||||||
this.addTooltip,
|
this.addTooltip,
|
||||||
@@ -28,7 +28,7 @@ class EditableList extends StatelessWidget {
|
|||||||
final void Function() onAdd;
|
final void Function() onAdd;
|
||||||
|
|
||||||
/// Die Funktion, die beim umbenennen aufgerufen wird
|
/// 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
|
///Die Funktion, die beim Löschen aufgerufen wird
|
||||||
final void Function(int) onDelete;
|
final void Function(int) onDelete;
|
||||||
@@ -72,14 +72,14 @@ class EditableList extends StatelessWidget {
|
|||||||
trailing: PopupMenuButton<String>(
|
trailing: PopupMenuButton<String>(
|
||||||
tooltip: menuTooltip,
|
tooltip: menuTooltip,
|
||||||
onSelected: (final value) {
|
onSelected: (final value) {
|
||||||
if (value == 'rename') {
|
if (value == 'edit') {
|
||||||
onRename(items[index].id);
|
onEdit(items[index].id);
|
||||||
} else if (value == 'delete') {
|
} else if (value == 'delete') {
|
||||||
onDelete(items[index].id);
|
onDelete(items[index].id);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
itemBuilder: (_) => const [
|
itemBuilder: (_) => const [
|
||||||
PopupMenuItem(value: 'rename', child: Text('Umbenennen')),
|
PopupMenuItem(value: 'edit', child: Text('Bearbeiten')),
|
||||||
PopupMenuItem(value: 'delete', child: Text('Entfernen')),
|
PopupMenuItem(value: 'delete', child: Text('Entfernen')),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ class _AccountListState extends State<AccountList> {
|
|||||||
|
|
||||||
_accountController.accounts.addListener(() {
|
_accountController.accounts.addListener(() {
|
||||||
setState(() {
|
setState(() {
|
||||||
if (context.mounted) {
|
if (mounted) {
|
||||||
_accounts = _accountController.accounts.value;
|
_accounts = _accountController.accounts.value;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -45,7 +45,7 @@ class _AccountListState extends State<AccountList> {
|
|||||||
name: 'Konten',
|
name: 'Konten',
|
||||||
items: formatedAccounts,
|
items: formatedAccounts,
|
||||||
onAdd: _accountController.newAccountHandler,
|
onAdd: _accountController.newAccountHandler,
|
||||||
onRename: _accountController.renameAccountHandler,
|
onEdit: _accountController.renameAccountHandler,
|
||||||
onDelete: _accountController.deleteAccountHandler,
|
onDelete: _accountController.deleteAccountHandler,
|
||||||
icon: const Icon(Icons.account_balance_wallet),
|
icon: const Icon(Icons.account_balance_wallet),
|
||||||
addTooltip: 'Konto hinzufügen',
|
addTooltip: 'Konto hinzufügen',
|
||||||
|
|||||||
65
lib/Pages/Settings/recurring_transaction_list.dart
Normal file
65
lib/Pages/Settings/recurring_transaction_list.dart
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:package_info_plus/package_info_plus.dart';
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
|
|
||||||
import 'account_list.dart';
|
import 'account_list.dart';
|
||||||
|
import 'recurring_transaction_list.dart';
|
||||||
|
|
||||||
/// Eine Widget-Klasse, die die Einstellungsseite der Anwendung darstellt.
|
/// Eine Widget-Klasse, die die Einstellungsseite der Anwendung darstellt.
|
||||||
class Settings extends StatefulWidget {
|
class Settings extends StatefulWidget {
|
||||||
@@ -31,7 +32,9 @@ class _SettingsState extends State<Settings> {
|
|||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
const AccountList(),
|
const AccountList(),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
|
const RecurringTransactionList(),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
_versionNumber(theme),
|
_versionNumber(theme),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -18,6 +18,11 @@ class RecurringTransactionRepository {
|
|||||||
return find(id);
|
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
|
/// Entfernt eine wiederkehrende Transaktion aus der Datenbank
|
||||||
Future<int> remove(final RecurringTransaction recurringTransaction) =>
|
Future<int> remove(final RecurringTransaction recurringTransaction) =>
|
||||||
(_db.delete(
|
(_db.delete(
|
||||||
@@ -43,6 +48,7 @@ class RecurringTransactionRepository {
|
|||||||
final double? amount,
|
final double? amount,
|
||||||
final double? amountMin,
|
final double? amountMin,
|
||||||
final double? amountMax,
|
final double? amountMax,
|
||||||
|
final Account? account,
|
||||||
final String? orderBy,
|
final String? orderBy,
|
||||||
}) {
|
}) {
|
||||||
final SimpleSelectStatement<
|
final SimpleSelectStatement<
|
||||||
@@ -93,6 +99,10 @@ class RecurringTransactionRepository {
|
|||||||
query.where((final t) => t.amount.isSmallerThanValue(amountMax));
|
query.where((final t) => t.amount.isSmallerThanValue(amountMax));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (account != null) {
|
||||||
|
query.where((final t) => t.accountId.equals(account.id));
|
||||||
|
}
|
||||||
|
|
||||||
if (orderBy != null) {
|
if (orderBy != null) {
|
||||||
switch (orderBy) {
|
switch (orderBy) {
|
||||||
case 'nameAsc':
|
case 'nameAsc':
|
||||||
|
|||||||
Reference in New Issue
Block a user