From a45169bf12119d68208779e365fcc331cfade154 Mon Sep 17 00:00:00 2001 From: DragonSlayer_14 Date: Thu, 25 Dec 2025 23:15:54 +0100 Subject: [PATCH] Feat: Macht die Kontoliste und Versionsanzeige funktional --- lib/Controller/account_controller.dart | 138 ++++++++++-- lib/Entities/list_item.dart | 11 + lib/Pages/Dialog/dialog_action.dart | 2 +- lib/Pages/Dialog/dynamic_dialog.dart | 219 ++++++++++--------- lib/Pages/Misc/account_select.dart | 93 ++++---- lib/Pages/Misc/editable_list.dart | 92 ++++++++ lib/Pages/Misc/floating_creation_button.dart | 4 +- lib/Pages/Settings/account_list.dart | 56 +++++ lib/Pages/Settings/settings.dart | 192 +++------------- lib/Repositories/account_repository.dart | 4 + lib/Services/navigation_service.dart | 3 + 11 files changed, 488 insertions(+), 326 deletions(-) create mode 100644 lib/Entities/list_item.dart create mode 100644 lib/Pages/Misc/editable_list.dart create mode 100644 lib/Pages/Settings/account_list.dart diff --git a/lib/Controller/account_controller.dart b/lib/Controller/account_controller.dart index b24ac52..4e1668b 100644 --- a/lib/Controller/account_controller.dart +++ b/lib/Controller/account_controller.dart @@ -49,48 +49,152 @@ class AccountController { } static final AccountController _instance = AccountController._internal(); - BuildContext? _buildContext; final AccountRepository _accountRepository = AccountRepository(); DynamicDialog? _newAccountDialog; DynamicDialog? _errorNameEmptyDialog; DynamicDialog? _accountCreatedDialog; - Account? _selected; + final ValueNotifier _selected = ValueNotifier(null); /// Stellt das ausgewählte Konto dar, das angezeigt wird - Future get selected async => _selected ??= (await getAccounts())[0]; + ValueNotifier get selected { + if (_selected.value == null) { + unawaited( + updateAccounts().then((_) { + _selected.value = accounts.value.firstOrNull; + }), + ); + } + + return _selected; + } set selected(final Account selected) { - _selected = selected; + _selected.value = selected; + } + + final ValueNotifier> _accounts = ValueNotifier>( + [], + ); + + /// Stellt die Liste der Konten dar + ValueNotifier> get accounts { + if (_accounts.value == []) { + unawaited(updateAccounts()); + } + + return _accounts; + } + + set accounts(final List accounts) { + _accounts.value = accounts; } /// Gibt die gespeicherten Konten als Liste zurück - Future> getAccounts() async { + Future updateAccounts() async { final List accounts = await _accountRepository.findBy( orderBy: 'nameAsc', ); - return accounts; + _accounts.value = accounts; } - /// Startet den Prozess um ein neues Konto anzulegen - void newAccountHandler(final BuildContext buildContext) { - _buildContext = buildContext; - - unawaited(_newAccountDialog?.show(buildContext)); + /// Startet den Prozess, um ein neues Konto anzulegen + void newAccountHandler() { + unawaited(_newAccountDialog?.show()); } - Future _saveNewAccount(final Map values) async { - if (values['name'] == null || values['name']!.isEmpty) { - if (_buildContext != null) { - await _errorNameEmptyDialog?.show(_buildContext!); - } + /// Startet den Prozess, um ein Konto umzubenennen + Future renameAccountHandler(final int accountId) async { + final Account? account = await _accountRepository.find(accountId); + + if (account != null) { + final renameAccountDialog = DynamicDialog( + title: '${account.name} umbenennen', + icon: Icons.edit, + inputFields: [ + DialogInputField( + id: 'name', + label: 'Name', + initialValue: account.name, + autoFocus: true, + ), + ], + actions: [ + DialogAction(label: 'Abbruch'), + DialogAction( + label: 'Speichern', + isPrimary: true, + onPressed: _renameAccount, + ), + ], + hiddenValues: {'account': account}, + ); + unawaited(renameAccountDialog.show()); + } + } + + /// Startet den Prozess, um ein Konto zu löschen + Future deleteAccountHandler(final int accountId) async { + final Account? account = await _accountRepository.find(accountId); + + if (account != null) { + final deleteAccountDialog = DynamicDialog( + dialogType: DialogTypeEnum.error, + title: '${account.name} löschen', + content: Text('Willst du ${account.name} wirklich löschen?'), + icon: Icons.delete_forever, + actions: [ + DialogAction(label: 'Abbruch', isPrimary: true), + DialogAction(label: 'Account löschen', onPressed: _deleteAccount), + ], + hiddenValues: {'account': account}, + ); + unawaited(deleteAccountDialog.show()); + } + } + + Future _saveNewAccount(final Map values) async { + if (values['name'] == null || values['name'] == '') { + await _errorNameEmptyDialog?.show(); } else { final account = AccountsCompanion(name: Value(values['name']!)); await _accountRepository.add(account); - await _accountCreatedDialog?.show(_buildContext!); + await _accountCreatedDialog?.show(); + + await updateAccounts(); + } + } + + Future _renameAccount(final Map values) async { + if (values['account'] != null && values['name'] != null) { + final Account account = values['account']; + final acc = AccountsCompanion( + id: Value(account.id), + name: Value(values['name']), + ); + + await _accountRepository.update(acc); + await updateAccounts(); + + if (account == selected.value) { + selected.value = await _accountRepository.find(account.id); + } + } + } + + Future _deleteAccount(final Map values) async { + if (values['account'] != null) { + final Account account = values['account']; + await _accountRepository.remove(account); + + await updateAccounts(); + + if (account == _selected.value) { + _selected.value = _accounts.value.firstOrNull; + } } } } diff --git a/lib/Entities/list_item.dart b/lib/Entities/list_item.dart new file mode 100644 index 0000000..115b7b2 --- /dev/null +++ b/lib/Entities/list_item.dart @@ -0,0 +1,11 @@ +/// Ein Listenitem, verwendet um editierbare Listen zu erstellen +class ListItem { + /// Erstellt eine neue Instanz dieser Klasse + ListItem({required this.id, required this.name}); + + /// Die Id des Eintrags + int id; + + /// Der Name des Eintrags + String name; +} diff --git a/lib/Pages/Dialog/dialog_action.dart b/lib/Pages/Dialog/dialog_action.dart index e953dc6..be7dbfa 100644 --- a/lib/Pages/Dialog/dialog_action.dart +++ b/lib/Pages/Dialog/dialog_action.dart @@ -10,5 +10,5 @@ class DialogAction { final bool isPrimary; /// Was bei einem Knopfdruck passieren soll - final void Function(Map inputValues)? onPressed; + final void Function(Map inputValues)? onPressed; } diff --git a/lib/Pages/Dialog/dynamic_dialog.dart b/lib/Pages/Dialog/dynamic_dialog.dart index 3bc1b5d..507628a 100644 --- a/lib/Pages/Dialog/dynamic_dialog.dart +++ b/lib/Pages/Dialog/dynamic_dialog.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import '../../Entities/dialog_type_enum.dart'; +import '../../Services/navigation_service.dart'; import '../../Services/theme_service.dart'; import 'dialog_action.dart'; import 'dialog_input_field.dart'; @@ -18,6 +19,7 @@ class DynamicDialog { this.borderRadius = 16, this.barrierDismissible = true, this.dialogType = DialogTypeEnum.info, + this.hiddenValues }) : inputFields = inputFields ?? const [], actions = actions ?? [DialogAction(label: 'Schließen')]; @@ -48,6 +50,9 @@ class DynamicDialog { /// Der Typ des Dialogs final DialogTypeEnum dialogType; + /// Versteckte Werte, die beim Abschicken mit zurückgegeben werden + final Map? hiddenValues; + Map? _controllers; Map? _focusNodes; @@ -79,119 +84,133 @@ class DynamicDialog { } /// Zeigt den vorher zusammengebauten Dialog an - Future show(final BuildContext context) async { - final ThemeData theme = Theme.of(context); + Future show() async { + final BuildContext? context = NavigationService.getCurrentBuildContext(); - _prepareControllers(); - _prepareFocusNodes(); + if (context != null) { + final ThemeData theme = Theme.of(context); - 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() - .firstWhere( - (final DialogInputField? f) => f != null, - orElse: () => null, - ); + _prepareControllers(); + _prepareFocusNodes(); - if (autoFocusField != null) { - _focusNodes![autoFocusField.id]!.requestFocus(); - } - }); + 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() + .firstWhere( + (final DialogInputField? f) => f != null, + orElse: () => null, + ); - final DialogAction primaryAction = actions.firstWhere( - (final a) => a.isPrimary, - orElse: () => actions.first, - ); + if (autoFocusField != null) { + _focusNodes![autoFocusField.id]!.requestFocus(); + } + }); - Color backgroundColor = - this.backgroundColor ?? theme.colorScheme.surface; - - if (dialogType == DialogTypeEnum.error) { - backgroundColor = theme.colorScheme.errorContainer; - } else if (dialogType == DialogTypeEnum.success) { - backgroundColor = ThemeService.getSuccessColor( - brightness: theme.brightness, + final DialogAction primaryAction = actions.firstWhere( + (final a) => a.isPrimary, + orElse: () => actions.first, ); - } - return AlertDialog( - backgroundColor: backgroundColor, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(borderRadius), - ), - title: Column( - mainAxisSize: MainAxisSize.min, - children: [ - if (icon != null) - Icon(icon, size: 48, color: theme.colorScheme.primary), - if (title != null) - Padding( - padding: const EdgeInsets.only(top: 8), - child: Text( - title!, - style: theme.textTheme.titleLarge, - textAlign: TextAlign.center, - ), - ), - ], - ), - content: Column( - mainAxisSize: MainAxisSize.min, - 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, + Color backgroundColor = + this.backgroundColor ?? theme.colorScheme.surface; + + if (dialogType == DialogTypeEnum.error) { + backgroundColor = theme.colorScheme.errorContainer; + } else if (dialogType == DialogTypeEnum.success) { + backgroundColor = ThemeService.getSuccessColor( + brightness: theme.brightness, + ); + } + + return AlertDialog( + backgroundColor: backgroundColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(borderRadius), + ), + title: Column( + mainAxisSize: MainAxisSize.min, + children: [ + if (icon != null) + Icon(icon, size: 48, color: theme.colorScheme.primary), + if (title != null) + Padding( + padding: const EdgeInsets.only(top: 8), + child: Text( + title!, + style: theme.textTheme.titleLarge, + textAlign: TextAlign.center, ), - onSubmitted: (_) { - final Map values = { + ), + ], + ), + content: Column( + mainAxisSize: MainAxisSize.min, + 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 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 + .map( + (final action) => + TextButton( + onPressed: () { + final Map values = { for (final entry in _controllers!.entries) entry.key: entry.value.text, }; - close(); - primaryAction.onPressed?.call(values); - }, - ), - ), - ), - ], - ), - actions: actions - .map( - (final action) => TextButton( - onPressed: () { - final Map 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); - }, - child: Text(action.label), - ), - ) - .toList(), - ); - }, - ); + close(); + action.onPressed?.call(values); + }, + child: Text(action.label), + ), + ) + .toList(), + ); + }, + ); + } } /// Schließt den dynamischen Dialog diff --git a/lib/Pages/Misc/account_select.dart b/lib/Pages/Misc/account_select.dart index b352d14..13606be 100644 --- a/lib/Pages/Misc/account_select.dart +++ b/lib/Pages/Misc/account_select.dart @@ -15,52 +15,59 @@ class AccountSelect extends StatefulWidget { class _AccountSelectState extends State { final AccountController _accountController = AccountController(); + Account? _selected; + List _accounts = []; + + @override + void initState() { + super.initState(); + + _selected = _accountController.selected.value; + _accounts = _accountController.accounts.value; + + _accountController.selected.addListener(() { + setState(() { + if (context.mounted) { + _selected = _accountController.selected.value; + } + }); + }); + + _accountController.accounts.addListener(() { + setState(() { + if (context.mounted) { + _accounts = _accountController.accounts.value; + } + }); + }); + } @override Widget build(final BuildContext context) { - final Future selected = _accountController.selected; + if (_selected != null && _accounts != []) { + return DropdownSearch( + items: (final f, final cs) => _accounts, + selectedItem: _selected, + onChanged: (final Account? account) { + if (account != null) { + _accountController.selected = account; + } + }, - return FutureBuilder( - future: selected, - builder: - (final BuildContext context, final AsyncSnapshot snapshot) { - if (snapshot.hasData) { - return DropdownSearch( - items: (final f, final cs) => _accountController.getAccounts(), - selectedItem: snapshot.data, - onChanged: (final Account? account) { - if (account != null) { - _accountController.selected = account; - } - }, - - itemAsString: (final Account account) => account.name, - compareFn: (final Account a1, final Account a2) => - a1.id == a2.id, - popupProps: const PopupProps.menu( - showSearchBox: true, - searchFieldProps: TextFieldProps( - decoration: InputDecoration( - hintText: 'Konto suchen...', - contentPadding: EdgeInsets.symmetric( - horizontal: 12, - vertical: 8, - ), - ), - ), - ), - ); - } else if (snapshot.hasError) { - return const Row( - children: [ - Icon(Icons.error, color: Colors.red), - Text('Fehler beim Laden der Konten!'), - ], - ); - } else { - return const CircularProgressIndicator(); - } - }, - ); + itemAsString: (final Account account) => account.name, + compareFn: (final Account a1, final Account a2) => a1.id == a2.id, + popupProps: const PopupProps.menu( + showSearchBox: true, + searchFieldProps: TextFieldProps( + decoration: InputDecoration( + hintText: 'Konto suchen...', + contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 8), + ), + ), + ), + ); + } else { + return const CircularProgressIndicator(); + } } } diff --git a/lib/Pages/Misc/editable_list.dart b/lib/Pages/Misc/editable_list.dart new file mode 100644 index 0000000..ecfaabd --- /dev/null +++ b/lib/Pages/Misc/editable_list.dart @@ -0,0 +1,92 @@ +import 'package:flutter/material.dart'; + +import '../../Entities/list_item.dart'; + +/// Eine editierbare Liste mit Funktionen zur Bearbeitung +class EditableList extends StatelessWidget { + /// Erstellt eine neue Instanz dieser Klasse + const EditableList({ + required this.name, + required this.items, + required this.onAdd, + required this.onRename, + required this.onDelete, + this.icon, + this.addTooltip, + this.menuTooltip, + super.key, + }); + + /// Der Name, der oben angezeigt wird + final String name; + + /// Die + final List items; + + /// Die Funktion, die aufgerufen wird, + /// wenn ein neuer Eintrag hinzugefügt werden soll + final void Function() onAdd; + + /// Die Funktion, die beim umbenennen aufgerufen wird + final void Function(int) onRename; + + ///Die Funktion, die beim Löschen aufgerufen wird + final void Function(int) onDelete; + + /// Das Icon, das angezeigt wird + final Icon? icon; + + /// Der Tooltip, der beim erstellen-Button angezeigt wird + final String? addTooltip; + + /// Der Tooltip, der auf dem Menü angezeigt wird + final String? menuTooltip; + + @override + Widget build(final BuildContext context) => Expanded( + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + name, + style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + ), + IconButton( + onPressed: onAdd, + icon: const Icon(Icons.add), + tooltip: addTooltip, + ), + ], + ), + const SizedBox(height: 8), + Expanded( + child: ListView.separated( + itemCount: items.length, + separatorBuilder: (_, _) => const Divider(), + itemBuilder: (final context, final index) => ListTile( + contentPadding: EdgeInsets.zero, + title: Text(items[index].name), + leading: icon, + trailing: PopupMenuButton( + tooltip: menuTooltip, + onSelected: (final value) { + if (value == 'rename') { + onRename(items[index].id); + } else if (value == 'delete') { + onDelete(items[index].id); + } + }, + itemBuilder: (_) => const [ + PopupMenuItem(value: 'rename', child: Text('Umbenennen')), + PopupMenuItem(value: 'delete', child: Text('Entfernen')), + ], + ), + ), + ), + ), + ], + ), + ); +} diff --git a/lib/Pages/Misc/floating_creation_button.dart b/lib/Pages/Misc/floating_creation_button.dart index bad9231..44d9802 100644 --- a/lib/Pages/Misc/floating_creation_button.dart +++ b/lib/Pages/Misc/floating_creation_button.dart @@ -33,9 +33,7 @@ class _FloatingCreationButtonState extends State { _expandableButton( label: 'Neues Konto', icon: Icons.account_balance_wallet, - onPressed: () { - _accountController.newAccountHandler(context); - }, + onPressed: _accountController.newAccountHandler, ), ], ); diff --git a/lib/Pages/Settings/account_list.dart b/lib/Pages/Settings/account_list.dart new file mode 100644 index 0000000..8fba058 --- /dev/null +++ b/lib/Pages/Settings/account_list.dart @@ -0,0 +1,56 @@ +import 'package:flutter/material.dart'; + +import '../../Controller/account_controller.dart'; +import '../../Entities/drift_database.dart'; +import '../../Entities/list_item.dart'; +import '../Misc/editable_list.dart'; + +class AccountList extends StatefulWidget { + const AccountList({super.key}); + + @override + State createState() => _AccountListState(); +} + +class _AccountListState extends State { + final AccountController _accountController = AccountController(); + List _accounts = []; + + @override + void initState() { + super.initState(); + + _accounts = _accountController.accounts.value; + + _accountController.accounts.addListener(() { + setState(() { + if (context.mounted) { + _accounts = _accountController.accounts.value; + } + }); + }); + } + + @override + Widget build(final BuildContext context) { + if (_accounts != []) { + final List formatedAccounts = []; + for (final Account data in _accounts) { + formatedAccounts.add(ListItem(id: data.id, name: data.name)); + } + + return EditableList( + name: 'Konten', + items: formatedAccounts, + onAdd: _accountController.newAccountHandler, + onRename: _accountController.renameAccountHandler, + onDelete: _accountController.deleteAccountHandler, + icon: const Icon(Icons.account_balance_wallet), + addTooltip: 'Konto hinzufügen', + menuTooltip: 'Menü anzeigen', + ); + } else { + return const Center(child: CircularProgressIndicator()); + } + } +} diff --git a/lib/Pages/Settings/settings.dart b/lib/Pages/Settings/settings.dart index 471633b..620b711 100644 --- a/lib/Pages/Settings/settings.dart +++ b/lib/Pages/Settings/settings.dart @@ -1,4 +1,7 @@ import 'package:flutter/material.dart'; +import 'package:package_info_plus/package_info_plus.dart'; + +import 'account_list.dart'; /// Eine Widget-Klasse, die die Einstellungsseite der Anwendung darstellt. class Settings extends StatefulWidget { @@ -10,110 +13,6 @@ class Settings extends StatefulWidget { } class _SettingsState extends State { - final List _accounts = [ - 'Girokonto', - 'Sparkonto', - 'Kreditkarte', - ]; - - Future _addAccount() async { - final controller = TextEditingController(); - - await showDialog( - context: context, - builder: (final BuildContext context) => AlertDialog( - title: const Text('Konto hinzufügen'), - content: TextField( - controller: controller, - decoration: const InputDecoration( - labelText: 'Kontoname', - border: OutlineInputBorder(), - ), - autofocus: true, - ), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: const Text('Abbrechen'), - ), - ElevatedButton( - onPressed: () { - if (controller.text.isNotEmpty) { - setState(() => _accounts.add(controller.text)); - } - Navigator.pop(context); - }, - child: const Text('Hinzufügen'), - ), - ], - ), - ); - } - - Future _renameAccount(final int index) async { - final controller = TextEditingController( - text: _accounts[index], - ); - - await showDialog( - context: context, - builder: (final BuildContext context) => AlertDialog( - title: const Text('Konto umbenennen'), - content: TextField( - controller: controller, - decoration: const InputDecoration( - labelText: 'Neuer Name', - border: OutlineInputBorder(), - ), - autofocus: true, - ), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: const Text('Abbrechen'), - ), - ElevatedButton( - onPressed: () { - if (controller.text.isNotEmpty) { - setState(() => _accounts[index] = controller.text); - } - Navigator.pop(context); - }, - child: const Text('Speichern'), - ), - ], - ), - ); - } - - Future _removeAccount(final int index) async { - await showDialog( - context: context, - builder: (final BuildContext context) => AlertDialog( - title: const Text('Konto entfernen'), - content: Text( - 'Möchtest du das Konto "${_accounts[index]}" wirklich löschen?', - ), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: const Text('Abbrechen'), - ), - ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: Theme.of(context).colorScheme.error, - ), - onPressed: () { - setState(() => _accounts.removeAt(index)); - Navigator.pop(context); - }, - child: const Text('Löschen'), - ), - ], - ), - ); - } - @override Widget build(final BuildContext context) { final ThemeData theme = Theme.of(context); @@ -129,12 +28,9 @@ class _SettingsState extends State { 'Einstellungen', style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), ), - const SizedBox(height: 24), - _accountHeader(), - const SizedBox(height: 8), - _accountList(), + const AccountList(), const SizedBox(height: 8), _versionNumber(theme), @@ -145,60 +41,32 @@ class _SettingsState extends State { ); } - Widget _accountHeader() => Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Text( - 'Konten', - style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), - ), - IconButton( - onPressed: _addAccount, - icon: const Icon(Icons.add), - tooltip: 'Konto hinzufügen', - ), - ], - ); + Widget _versionNumber(final ThemeData theme) { + final Future packageInfo = PackageInfo.fromPlatform(); - Widget _accountList() => Expanded( - child: ListView.separated( - itemCount: _accounts.length, - separatorBuilder: (_, _) => const Divider(), - itemBuilder: (final BuildContext context, final int index) => ListTile( - contentPadding: EdgeInsets.zero, - title: Text(_accounts[index]), - leading: const Icon(Icons.account_balance_wallet), - trailing: PopupMenuButton( - onSelected: (final String value) async { - if (value == 'rename') { - await _renameAccount(index); - } else if (value == 'delete') { - await _removeAccount(index); - } - }, - itemBuilder: (final BuildContext context) => - const >[ - PopupMenuItem( - value: 'rename', - child: Text('Umbenennen'), - ), - PopupMenuItem( - value: 'delete', - child: Text('Entfernen'), - ), - ], - ), + return Align( + alignment: Alignment.bottomLeft, + child: FutureBuilder( + future: packageInfo, + builder: + ( + final BuildContext context, + final AsyncSnapshot snapshot, + ) { + if (snapshot.hasData) { + return Text( + '${snapshot.data?.version}+${snapshot.data?.buildNumber}', + style: theme.textTheme.bodySmall?.copyWith( + color: theme.colorScheme.onSurface.withAlpha( + (0.6 * 255).round(), + ), + ), + ); + } else { + return const CircularProgressIndicator(); + } + }, ), - ), - ); - - Widget _versionNumber(final ThemeData theme) => Align( - alignment: Alignment.bottomLeft, - child: Text( - 'Version 0.0.0+0', - style: theme.textTheme.bodySmall?.copyWith( - color: theme.colorScheme.onSurface.withAlpha((0.6 * 255).round()), - ), - ), - ); + ); + } } diff --git a/lib/Repositories/account_repository.dart b/lib/Repositories/account_repository.dart index 298bd35..cca9478 100644 --- a/lib/Repositories/account_repository.dart +++ b/lib/Repositories/account_repository.dart @@ -13,6 +13,10 @@ class AccountRepository { return find(id); } + /// Aktualisiert ein Konto in der Datenbank + Future update(final AccountsCompanion account) => + _db.update(_db.accounts).replace(account); + /// Entfernt ein Konto aus der Datenbank Future remove(final Account account) => (_db.delete( _db.accounts, diff --git a/lib/Services/navigation_service.dart b/lib/Services/navigation_service.dart index b43e0a2..f135acc 100644 --- a/lib/Services/navigation_service.dart +++ b/lib/Services/navigation_service.dart @@ -5,4 +5,7 @@ class NavigationService { /// Der Navigator-Schlüssel, der für die Navigation verwendet wird static final GlobalKey navigatorKey = GlobalKey(); + + /// Gibt den aktuell gültigen BuildContext zurück + static BuildContext? getCurrentBuildContext() => navigatorKey.currentContext; }