From dfce3e7b5cd04623ad8c8078bca2cdd2f25b0641 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20M=C3=BCller?= Date: Sun, 12 Jul 2026 23:36:46 +0200 Subject: [PATCH] add PromptDialog helper for text input dialogs --- .../pages/files/widgets/add_file_menu.dart | 34 +++------ .../pages/files/widgets/file_element.dart | 72 +++++-------------- lib/widget/prompt_dialog.dart | 37 ++++++++++ 3 files changed, 67 insertions(+), 76 deletions(-) create mode 100644 lib/widget/prompt_dialog.dart diff --git a/lib/view/pages/files/widgets/add_file_menu.dart b/lib/view/pages/files/widgets/add_file_menu.dart index 6d50052..c87353f 100644 --- a/lib/view/pages/files/widgets/add_file_menu.dart +++ b/lib/view/pages/files/widgets/add_file_menu.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; import '../../../../state/app/modules/files/bloc/files_bloc.dart'; -import '../../../../widget/async_action_button.dart'; import '../../../../widget/demo_restricted.dart'; import '../../../../widget/details_bottom_sheet.dart'; import '../../../../widget/file_pick.dart'; +import '../../../../widget/prompt_dialog.dart'; /// Opens the "Element hinzufügen" sheet (create folder, upload, take photo, …). /// [onPickedFiles] receives selected/captured file paths (gallery, file picker @@ -59,27 +59,15 @@ void showAddFileSheet( } void showCreateFolderDialog(BuildContext context, FilesBloc bloc) { - final inputController = TextEditingController(); - showDialog( - context: context, - builder: (dialogCtx) => AlertDialog( - title: const Text('Neuer Ordner'), - content: TextField( - controller: inputController, - decoration: const InputDecoration(labelText: 'Name'), - autofocus: true, - ), - actions: [ - AsyncDialogAction( - confirmLabel: 'Ordner erstellen', - onConfirm: () async { - if (inputController.text.trim().isEmpty) { - throw Exception('Bitte einen Namen eingeben.'); - } - await bloc.createFolder(inputController.text.trim()); - }, - ), - ], - ), + showPromptDialog( + context, + title: 'Neuer Ordner', + confirmButton: 'Ordner erstellen', + onConfirm: (name) async { + if (name.isEmpty) { + throw Exception('Bitte einen Namen eingeben.'); + } + await bloc.createFolder(name); + }, ); } diff --git a/lib/view/pages/files/widgets/file_element.dart b/lib/view/pages/files/widgets/file_element.dart index 2813944..de2345e 100644 --- a/lib/view/pages/files/widgets/file_element.dart +++ b/lib/view/pages/files/widgets/file_element.dart @@ -19,6 +19,7 @@ import '../../../../widget/demo_restricted.dart'; import '../../../../widget/details_bottom_sheet.dart'; import '../../../../widget/downloads/download_trigger.dart'; import '../../../../widget/info_dialog.dart'; +import '../../../../widget/prompt_dialog.dart'; import '../../talk/widgets/highlighted_linkify.dart'; import '../sharing/share_sheet.dart'; import 'file_details_sheet.dart'; @@ -136,52 +137,30 @@ class _FileElementState extends State String _joinPath(String folder, String name, {required bool isDirectory}) => isDirectory ? '$folder$name/' : '$folder$name'; - Future _rename() async { + void _rename() { if (guardDemoAction(context)) return; - final controller = TextEditingController(text: widget.file.name); - try { - final newName = await showDialog( - context: context, - builder: (dialogCtx) => AlertDialog( - title: const Text('Umbenennen'), - content: TextField( - controller: controller, - decoration: const InputDecoration(labelText: 'Neuer Name'), - autofocus: true, - ), - actions: [ - TextButton( - onPressed: () => Navigator.of(dialogCtx).pop(), - child: const Text('Abbrechen'), - ), - TextButton( - onPressed: () => - Navigator.of(dialogCtx).pop(controller.text.trim()), - child: const Text('Umbenennen'), - ), - ], - ), - ); - if (newName == null || newName.isEmpty || newName == widget.file.name) { - return; - } - - final parent = _parentPathOf(widget.file.path); - final destination = _joinPath( - parent, - newName, - isDirectory: widget.file.isDirectory, - ); - await _runWebdavOp(() async { + showPromptDialog( + context, + title: 'Umbenennen', + label: 'Neuer Name', + confirmButton: 'Umbenennen', + initialValue: widget.file.name, + onConfirm: (newName) async { + if (newName.isEmpty || newName == widget.file.name) return; + final parent = _parentPathOf(widget.file.path); + final destination = _joinPath( + parent, + newName, + isDirectory: widget.file.isDirectory, + ); final webdav = await WebdavApi.webdav; await webdav.move( PathUri.parse(widget.file.path), PathUri.parse(destination), ); - }, errorTitle: 'Umbenennen fehlgeschlagen'); - } finally { - controller.dispose(); - } + widget.refetch(); + }, + ); } void _putOnClipboard({required bool copy}) { @@ -218,19 +197,6 @@ class _FileElementState extends State ); } - Future _runWebdavOp( - Future Function() action, { - required String errorTitle, - }) async { - try { - await action(); - widget.refetch(); - } on Object catch (e) { - if (!mounted) return; - InfoDialog.show(context, e.toString(), title: errorTitle, copyable: true); - } - } - void _showActionSheet() { Haptics.longPress(); showDetailsBottomSheet( diff --git a/lib/widget/prompt_dialog.dart b/lib/widget/prompt_dialog.dart new file mode 100644 index 0000000..9747fbe --- /dev/null +++ b/lib/widget/prompt_dialog.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; + +import 'async_action_button.dart'; + +/// Single-line text-input dialog. The confirm action runs [onConfirm] with the +/// trimmed input via [AsyncDialogAction], so it shows a spinner and an inline +/// error and only closes on success. Throw inside [onConfirm] to keep the +/// dialog open with a message (e.g. for empty or duplicate input). +void showPromptDialog( + BuildContext context, { + required String title, + required String confirmButton, + required Future Function(String value) onConfirm, + String label = 'Name', + String initialValue = '', + AsyncErrorBuilder? errorBuilder, +}) { + final controller = TextEditingController(text: initialValue); + showDialog( + context: context, + builder: (dialogCtx) => AlertDialog( + title: Text(title), + content: TextField( + controller: controller, + decoration: InputDecoration(labelText: label), + autofocus: true, + ), + actions: [ + AsyncDialogAction( + confirmLabel: confirmButton, + onConfirm: () => onConfirm(controller.text.trim()), + errorBuilder: errorBuilder, + ), + ], + ), + ).whenComplete(controller.dispose); +}