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: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';
|
||||
|
||||
|
||||
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 '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;
|
||||
}
|
||||
|
||||
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 '../../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);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
});
|
||||
|
||||
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.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')),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -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',
|
||||
|
||||
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 '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),
|
||||
],
|
||||
|
||||
@@ -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':
|
||||
|
||||
Reference in New Issue
Block a user