add PromptDialog helper for text input dialogs
This commit is contained in:
@@ -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,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(
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user