add PromptDialog helper for text input dialogs

This commit is contained in:
2026-07-12 23:36:46 +02:00
parent 564a334cdc
commit dfce3e7b5c
3 changed files with 67 additions and 76 deletions
+11 -23
View File
@@ -1,10 +1,10 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../../../state/app/modules/files/bloc/files_bloc.dart'; import '../../../../state/app/modules/files/bloc/files_bloc.dart';
import '../../../../widget/async_action_button.dart';
import '../../../../widget/demo_restricted.dart'; import '../../../../widget/demo_restricted.dart';
import '../../../../widget/details_bottom_sheet.dart'; import '../../../../widget/details_bottom_sheet.dart';
import '../../../../widget/file_pick.dart'; import '../../../../widget/file_pick.dart';
import '../../../../widget/prompt_dialog.dart';
/// Opens the "Element hinzufügen" sheet (create folder, upload, take photo, …). /// Opens the "Element hinzufügen" sheet (create folder, upload, take photo, …).
/// [onPickedFiles] receives selected/captured file paths (gallery, file picker /// [onPickedFiles] receives selected/captured file paths (gallery, file picker
@@ -59,27 +59,15 @@ void showAddFileSheet(
} }
void showCreateFolderDialog(BuildContext context, FilesBloc bloc) { void showCreateFolderDialog(BuildContext context, FilesBloc bloc) {
final inputController = TextEditingController(); showPromptDialog(
showDialog( context,
context: context, title: 'Neuer Ordner',
builder: (dialogCtx) => AlertDialog( confirmButton: 'Ordner erstellen',
title: const Text('Neuer Ordner'), onConfirm: (name) async {
content: TextField( if (name.isEmpty) {
controller: inputController, throw Exception('Bitte einen Namen eingeben.');
decoration: const InputDecoration(labelText: 'Name'), }
autofocus: true, await bloc.createFolder(name);
), },
actions: [
AsyncDialogAction(
confirmLabel: 'Ordner erstellen',
onConfirm: () async {
if (inputController.text.trim().isEmpty) {
throw Exception('Bitte einen Namen eingeben.');
}
await bloc.createFolder(inputController.text.trim());
},
),
],
),
); );
} }
+19 -53
View File
@@ -19,6 +19,7 @@ import '../../../../widget/demo_restricted.dart';
import '../../../../widget/details_bottom_sheet.dart'; import '../../../../widget/details_bottom_sheet.dart';
import '../../../../widget/downloads/download_trigger.dart'; import '../../../../widget/downloads/download_trigger.dart';
import '../../../../widget/info_dialog.dart'; import '../../../../widget/info_dialog.dart';
import '../../../../widget/prompt_dialog.dart';
import '../../talk/widgets/highlighted_linkify.dart'; import '../../talk/widgets/highlighted_linkify.dart';
import '../sharing/share_sheet.dart'; import '../sharing/share_sheet.dart';
import 'file_details_sheet.dart'; import 'file_details_sheet.dart';
@@ -136,52 +137,30 @@ class _FileElementState extends State<FileElement>
String _joinPath(String folder, String name, {required bool isDirectory}) => String _joinPath(String folder, String name, {required bool isDirectory}) =>
isDirectory ? '$folder$name/' : '$folder$name'; isDirectory ? '$folder$name/' : '$folder$name';
Future<void> _rename() async { void _rename() {
if (guardDemoAction(context)) return; if (guardDemoAction(context)) return;
final controller = TextEditingController(text: widget.file.name); showPromptDialog(
try { context,
final newName = await showDialog<String>( title: 'Umbenennen',
context: context, label: 'Neuer Name',
builder: (dialogCtx) => AlertDialog( confirmButton: 'Umbenennen',
title: const Text('Umbenennen'), initialValue: widget.file.name,
content: TextField( onConfirm: (newName) async {
controller: controller, if (newName.isEmpty || newName == widget.file.name) return;
decoration: const InputDecoration(labelText: 'Neuer Name'), final parent = _parentPathOf(widget.file.path);
autofocus: true, final destination = _joinPath(
), parent,
actions: [ newName,
TextButton( isDirectory: widget.file.isDirectory,
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 {
final webdav = await WebdavApi.webdav; final webdav = await WebdavApi.webdav;
await webdav.move( await webdav.move(
PathUri.parse(widget.file.path), PathUri.parse(widget.file.path),
PathUri.parse(destination), PathUri.parse(destination),
); );
}, errorTitle: 'Umbenennen fehlgeschlagen'); widget.refetch();
} finally { },
controller.dispose(); );
}
} }
void _putOnClipboard({required bool copy}) { void _putOnClipboard({required bool copy}) {
@@ -218,19 +197,6 @@ class _FileElementState extends State<FileElement>
); );
} }
Future<void> _runWebdavOp(
Future<void> 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() { void _showActionSheet() {
Haptics.longPress(); Haptics.longPress();
showDetailsBottomSheet( showDetailsBottomSheet(
+37
View File
@@ -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<void> 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);
}