dart format

This commit is contained in:
2026-05-08 20:12:40 +02:00
parent 9e139b5704
commit 3b8da1d3d6
295 changed files with 6404 additions and 4161 deletions
+5 -1
View File
@@ -9,7 +9,11 @@ class BetterSortOption {
final int Function(CacheableFile, CacheableFile) compare;
final IconData icon;
BetterSortOption({required this.displayName, required this.icon, required this.compare});
BetterSortOption({
required this.displayName,
required this.icon,
required this.compare,
});
}
class SortOptions {
+34 -16
View File
@@ -25,7 +25,8 @@ class Files extends StatelessWidget {
Files({List<String>? path, super.key}) : path = path ?? [];
@override
Widget build(BuildContext context) => BlocModule<FilesBloc, LoadableState<FilesState>>(
Widget build(BuildContext context) =>
BlocModule<FilesBloc, LoadableState<FilesState>>(
create: (_) => FilesBloc(initialPath: path),
child: (context, _, _) => _FilesView(path: path),
);
@@ -51,7 +52,8 @@ class _FilesViewState extends State<_FilesView> {
// Relative folder path matching the WebDAV format used by `CacheableFile.path`
// (no leading slash; trailing slash for non-root). Empty string means root.
String get _currentFolderPath => widget.path.isEmpty ? '' : '${widget.path.join('/')}/';
String get _currentFolderPath =>
widget.path.isEmpty ? '' : '${widget.path.join('/')}/';
@override
void initState() {
@@ -59,7 +61,9 @@ class _FilesViewState extends State<_FilesView> {
settings = context.read<SettingsCubit>();
currentSort = settings.val().fileSettings.sortBy;
currentSortDirection = settings.val().fileSettings.ascending;
_invalidationSub = CacheInvalidationBus.listFilesStream.listen(_onInvalidation);
_invalidationSub = CacheInvalidationBus.listFilesStream.listen(
_onInvalidation,
);
}
void _onInvalidation(String invalidatedPath) {
@@ -77,15 +81,17 @@ class _FilesViewState extends State<_FilesView> {
Future<void> _mediaUpload(List<String>? paths) async {
if (paths == null) return;
final bloc = context.read<FilesBloc>();
unawaited(pushScreen(
context,
withNavBar: false,
screen: FilesUploadDialog(
filePaths: paths,
remotePath: widget.path.join('/'),
onUploadFinished: (_) => bloc.refresh(),
unawaited(
pushScreen(
context,
withNavBar: false,
screen: FilesUploadDialog(
filePaths: paths,
remotePath: widget.path.join('/'),
onUploadFinished: (_) => bloc.refresh(),
),
),
));
);
}
@override
@@ -116,29 +122,41 @@ class _FilesViewState extends State<_FilesView> {
floatingActionButton: FloatingActionButton(
heroTag: 'uploadFile',
backgroundColor: Theme.of(context).primaryColor,
onPressed: () => showAddFileSheet(context, bloc: bloc, onPickedFiles: _mediaUpload),
onPressed: () =>
showAddFileSheet(context, bloc: bloc, onPickedFiles: _mediaUpload),
child: const Icon(Icons.add),
),
body: Column(
children: [
ClipboardBanner(currentFolder: _currentFolderPath, onPasteDone: bloc.refresh),
ClipboardBanner(
currentFolder: _currentFolderPath,
onPasteDone: bloc.refresh,
),
Expanded(
child: LoadableStateConsumer<FilesBloc, FilesState>(
isReady: (state) => state.listing != null,
child: (state, _) {
final listing = state.listing!;
if (listing.files.isEmpty) {
return const PlaceholderView(icon: Icons.folder_off_rounded, text: 'Der Ordner ist leer');
return const PlaceholderView(
icon: Icons.folder_off_rounded,
text: 'Der Ordner ist leer',
);
}
final files = listing.sortBy(
sortOption: currentSort,
foldersToTop: context.watch<SettingsCubit>().val().fileSettings.sortFoldersToTop,
foldersToTop: context
.watch<SettingsCubit>()
.val()
.fileSettings
.sortFoldersToTop,
reversed: currentSortDirection,
);
return ListView.builder(
padding: EdgeInsets.zero,
itemCount: files.length,
itemBuilder: (context, index) => FileElement(files[index], widget.path, bloc.refresh),
itemBuilder: (context, index) =>
FileElement(files[index], widget.path, bloc.refresh),
);
},
),
+190 -155
View File
@@ -15,7 +15,13 @@ class FilesUploadDialog extends StatefulWidget {
final void Function(List<String> uploadedFilePaths) onUploadFinished;
final bool uniqueNames;
const FilesUploadDialog({super.key, required this.filePaths, required this.remotePath, required this.onUploadFinished, this.uniqueNames = false});
const FilesUploadDialog({
super.key,
required this.filePaths,
required this.remotePath,
required this.onUploadFinished,
this.uniqueNames = false,
});
@override
State<FilesUploadDialog> createState() => _FilesUploadDialogState();
@@ -31,7 +37,6 @@ class UploadableFile {
UploadableFile(this.filePath, this.fileName);
}
class _FilesUploadDialogState extends State<FilesUploadDialog> {
late List<UploadableFile> _uploadableFiles;
bool _isUploading = false;
@@ -63,7 +68,12 @@ class _FilesUploadDialogState extends State<FilesUploadDialog> {
_overallProgressValue = 0.0;
_infoText = '';
});
InfoDialog.show(context, message, title: 'Upload fehlgeschlagen', copyable: true);
InfoDialog.show(
context,
message,
title: 'Upload fehlgeschlagen',
copyable: true,
);
}
Future<void> uploadFiles({bool override = false}) async {
@@ -80,7 +90,9 @@ class _FilesUploadDialogState extends State<FilesUploadDialog> {
if (!override) {
List<dynamic> result;
try {
result = (await webdavClient.propfind(PathUri.parse(widget.remotePath))).responses;
result = (await webdavClient.propfind(
PathUri.parse(widget.remotePath),
)).responses;
} catch (e) {
if (!mounted) return;
_showUploadError('Verbindung fehlgeschlagen: $e');
@@ -88,7 +100,11 @@ class _FilesUploadDialogState extends State<FilesUploadDialog> {
}
final conflictingFiles = _uploadableFiles.where((file) {
final fileName = file.fileName;
return result.any((element) => Uri.decodeComponent((element as WebDavResponse).href!).endsWith('/$fileName'));
return result.any(
(element) => Uri.decodeComponent(
(element as WebDavResponse).href!,
).endsWith('/$fileName'),
);
}).toList();
if (conflictingFiles.isNotEmpty) {
@@ -97,46 +113,46 @@ class _FilesUploadDialogState extends State<FilesUploadDialog> {
context: context,
barrierDismissible: false,
builder: (context) => AlertDialog(
contentPadding: const EdgeInsets.all(10),
title: const Text('Konflikt', textAlign: TextAlign.center),
content: conflictingFiles.length == 1 ?
Text(
'Eine Datei mit dem Namen "${conflictingFiles.map((e) => e.fileName).first}" existiert bereits.',
textAlign: TextAlign.left,
) :
SingleChildScrollView(
child: Text(
'${conflictingFiles.length} Dateien mit folgenden Namen existieren bereits: \n${conflictingFiles.map((e) => '\n - ${e.fileName}').join('')}',
textAlign: TextAlign.left,
),
contentPadding: const EdgeInsets.all(10),
title: const Text('Konflikt', textAlign: TextAlign.center),
content: conflictingFiles.length == 1
? Text(
'Eine Datei mit dem Namen "${conflictingFiles.map((e) => e.fileName).first}" existiert bereits.',
textAlign: TextAlign.left,
)
: SingleChildScrollView(
child: Text(
'${conflictingFiles.length} Dateien mit folgenden Namen existieren bereits: \n${conflictingFiles.map((e) => '\n - ${e.fileName}').join('')}',
textAlign: TextAlign.left,
),
),
actions: [
TextButton(
onPressed: () {
Navigator.pop(context, false);
},
child: const Text('Bearbeiten', textAlign: TextAlign.center),
),
actions: [
TextButton(
onPressed: () {
Navigator.pop(context, false);
},
child: const Text('Bearbeiten', textAlign: TextAlign.center),
),
TextButton(
onPressed: () {
showDialog(
context: context,
builder: (context) => ConfirmDialog(
title: 'Bestätigen?',
content: 'Bist du sicher, dass du ${conflictingFiles.length} Dateien überschreiben möchtest?',
onConfirm: () {
Navigator.pop(context, true);
},
confirmButton: 'Ja',
cancelButton: 'Nein',
),
);
},
child: const Text('Überschreiben', textAlign: TextAlign.center),
),
],
)
TextButton(
onPressed: () {
showDialog(
context: context,
builder: (context) => ConfirmDialog(
title: 'Bestätigen?',
content:
'Bist du sicher, dass du ${conflictingFiles.length} Dateien überschreiben möchtest?',
onConfirm: () {
Navigator.pop(context, true);
},
confirmButton: 'Ja',
cancelButton: 'Nein',
),
);
},
child: const Text('Überschreiben', textAlign: TextAlign.center),
),
],
),
);
if (replaceFiles != true) {
@@ -160,13 +176,15 @@ class _FilesUploadDialogState extends State<FilesUploadDialog> {
if (widget.uniqueNames) {
final unique = DateTime.now().microsecondsSinceEpoch.toRadixString(36);
fileName = '${fileName.split('.').first}-$unique.${fileName.split('.').last}';
fileName =
'${fileName.split('.').first}-$unique.${fileName.split('.').last}';
}
var fullRemotePath = '${widget.remotePath}/$fileName';
setState(() {
_infoText = '${_uploadableFiles.indexOf(file) + 1}/${_uploadableFiles.length}';
_infoText =
'${_uploadableFiles.indexOf(file) + 1}/${_uploadableFiles.length}';
});
final HttpClientResponse uploadTask;
@@ -178,7 +196,10 @@ class _FilesUploadDialogState extends State<FilesUploadDialog> {
onProgress: (progress) {
setState(() {
file._uploadProgress = progress;
_overallProgressValue = ((progress + _uploadableFiles.indexOf(file)) / _uploadableFiles.length).toDouble();
_overallProgressValue =
((progress + _uploadableFiles.indexOf(file)) /
_uploadableFiles.length)
.toDouble();
});
},
);
@@ -188,7 +209,7 @@ class _FilesUploadDialogState extends State<FilesUploadDialog> {
return;
}
if(uploadTask.statusCode < 200 || uploadTask.statusCode > 299) {
if (uploadTask.statusCode < 200 || uploadTask.statusCode > 299) {
setState(() {
_isUploading = false;
_overallProgressValue = 0.0;
@@ -214,119 +235,133 @@ class _FilesUploadDialogState extends State<FilesUploadDialog> {
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(
title: const Text('Dateien hochladen'),
automaticallyImplyLeading: false,
),
body: LoaderOverlay(
overlayWholeScreen: true,
child: Column(
children: [
Expanded(
child: ListView.builder(
itemCount: _uploadableFiles.length,
itemBuilder: (context, index) {
final currentFile = _uploadableFiles[index];
currentFile.fileNameController.text = currentFile.fileName;
return ListTile(
title: TextField(
readOnly: _isUploading,
controller: currentFile.fileNameController,
decoration: InputDecoration(
border: const UnderlineInputBorder(),
label: Text('Datei ${index+1}'),
errorText: currentFile.isConflicting ? 'existiert bereits' : null,
errorStyle: TextStyle(color: Theme.of(context).colorScheme.error),
appBar: AppBar(
title: const Text('Dateien hochladen'),
automaticallyImplyLeading: false,
),
body: LoaderOverlay(
overlayWholeScreen: true,
child: Column(
children: [
Expanded(
child: ListView.builder(
itemCount: _uploadableFiles.length,
itemBuilder: (context, index) {
final currentFile = _uploadableFiles[index];
currentFile.fileNameController.text = currentFile.fileName;
return ListTile(
title: TextField(
readOnly: _isUploading,
controller: currentFile.fileNameController,
decoration: InputDecoration(
border: const UnderlineInputBorder(),
label: Text('Datei ${index + 1}'),
errorText: currentFile.isConflicting
? 'existiert bereits'
: null,
errorStyle: TextStyle(
color: Theme.of(context).colorScheme.error,
),
onChanged: (input) {
currentFile.fileName = input;
},
onTapOutside: (PointerDownEvent event) {
FocusBehaviour.textFieldTapOutside(context);
if(currentFile.isConflicting){
setState(() {
currentFile.isConflicting = false;
});
}
},
onEditingComplete: () {
if(currentFile.isConflicting){
setState(() {
currentFile.isConflicting = false;
});
}
},
),
subtitle: _isUploading && (currentFile._uploadProgress ?? 0) < 1 ? LinearProgressIndicator(
value: currentFile._uploadProgress,
borderRadius: const BorderRadius.all(Radius.circular(2)),
) : null,
trailing: Container(
width: 24,
height: 24,
onChanged: (input) {
currentFile.fileName = input;
},
onTapOutside: (PointerDownEvent event) {
FocusBehaviour.textFieldTapOutside(context);
if (currentFile.isConflicting) {
setState(() {
currentFile.isConflicting = false;
});
}
},
onEditingComplete: () {
if (currentFile.isConflicting) {
setState(() {
currentFile.isConflicting = false;
});
}
},
),
subtitle:
_isUploading && (currentFile._uploadProgress ?? 0) < 1
? LinearProgressIndicator(
value: currentFile._uploadProgress,
borderRadius: const BorderRadius.all(
Radius.circular(2),
),
)
: null,
trailing: Container(
width: 24,
height: 24,
padding: EdgeInsets.zero,
child: IconButton(
tooltip: 'Datei entfernen',
padding: EdgeInsets.zero,
child: IconButton(
tooltip: 'Datei entfernen',
padding: EdgeInsets.zero,
onPressed: () {
if(!_isUploading) {
if(_uploadableFiles.length-1 <= 0) Navigator.of(context).pop();
setState(() {
_uploadableFiles.removeAt(index);
});
onPressed: () {
if (!_isUploading) {
if (_uploadableFiles.length - 1 <= 0) {
Navigator.of(context).pop();
}
},
icon: const Icon(Icons.delete_outlined),
),
),
);
},
),
),
Padding(
padding: const EdgeInsets.only(left: 15, right: 15, bottom: 15, top: 5),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Visibility(
visible: !_isUploading,
child: TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('Abbrechen'),
setState(() {
_uploadableFiles.removeAt(index);
});
}
},
icon: const Icon(Icons.delete_outlined),
),
),
const Expanded(child: SizedBox.shrink()),
Visibility(
visible: _isUploading,
replacement: TextButton(
onPressed: () => uploadFiles(override: widget.uniqueNames),
child: const Text('Hochladen'),
),
child: Visibility(
visible: _infoText.length < 5,
replacement: Row(
children: [
Text(_infoText),
const SizedBox(width: 15),
CircularProgressIndicator(value: _overallProgressValue),
],
),
child: Stack(
alignment: Alignment.center,
children: [
CircularProgressIndicator(value: _overallProgressValue),
Center(child: Text(_infoText)),
],
),
),
),
],
),
);
},
),
],
),
),
Padding(
padding: const EdgeInsets.only(
left: 15,
right: 15,
bottom: 15,
top: 5,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Visibility(
visible: !_isUploading,
child: TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('Abbrechen'),
),
),
const Expanded(child: SizedBox.shrink()),
Visibility(
visible: _isUploading,
replacement: TextButton(
onPressed: () => uploadFiles(override: widget.uniqueNames),
child: const Text('Hochladen'),
),
child: Visibility(
visible: _infoText.length < 5,
replacement: Row(
children: [
Text(_infoText),
const SizedBox(width: 15),
CircularProgressIndicator(value: _overallProgressValue),
],
),
child: Stack(
alignment: Alignment.center,
children: [
CircularProgressIndicator(value: _overallProgressValue),
Center(child: Text(_infoText)),
],
),
),
),
],
),
),
],
),
);
),
);
}
@@ -50,7 +50,11 @@ class _ClipboardBannerState extends State<ClipboardBanner> {
final src = _normalised(f.path);
if (dst == src || dst.startsWith(src)) return false;
}
final destination = _joinPath(widget.currentFolder, f.name, isDirectory: f.isDirectory);
final destination = _joinPath(
widget.currentFolder,
f.name,
isDirectory: f.isDirectory,
);
if (destination != f.path) atLeastOneActionable = true;
}
return atLeastOneActionable;
@@ -75,14 +79,24 @@ class _ClipboardBannerState extends State<ClipboardBanner> {
try {
final webdav = await WebdavApi.webdav;
for (final file in cb.files) {
final destination = _joinPath(widget.currentFolder, file.name, isDirectory: file.isDirectory);
final destination = _joinPath(
widget.currentFolder,
file.name,
isDirectory: file.isDirectory,
);
if (destination == file.path) continue;
try {
if (operation == FileClipboardOperation.cut) {
await webdav.move(PathUri.parse(file.path), PathUri.parse(destination));
await webdav.move(
PathUri.parse(file.path),
PathUri.parse(destination),
);
invalidatedSourceFolders.add(_parentCacheKey(file.path));
} else {
await webdav.copy(PathUri.parse(file.path), PathUri.parse(destination));
await webdav.copy(
PathUri.parse(file.path),
PathUri.parse(destination),
);
}
} on Object catch (e) {
errors.add('${file.name}: $e');
@@ -111,42 +125,49 @@ class _ClipboardBannerState extends State<ClipboardBanner> {
@override
Widget build(BuildContext context) => ListenableBuilder(
listenable: FileClipboard.instance,
builder: (context, _) {
final cb = FileClipboard.instance;
if (cb.isEmpty) return const SizedBox.shrink();
final cut = cb.operation == FileClipboardOperation.cut;
final count = cb.files.length;
final label = count == 1 ? '"${cb.files.first.name}"' : '$count Elemente';
return Material(
color: Theme.of(context).colorScheme.secondaryContainer,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
child: Row(
children: [
Icon(cut ? Icons.drive_file_move_outline : Icons.copy_outlined, size: 20),
const SizedBox(width: 12),
Expanded(
child: Text(
cut ? '$label verschieben' : '$label kopieren',
overflow: TextOverflow.ellipsis,
),
),
TextButton(
onPressed: _busy || !_canPaste ? null : _paste,
child: _busy
? const SizedBox(width: 14, height: 14, child: CircularProgressIndicator(strokeWidth: 2))
: const Text('Hier einfügen'),
),
IconButton(
tooltip: 'Verwerfen',
icon: const Icon(Icons.close, size: 20),
onPressed: _busy ? null : cb.clear,
),
],
listenable: FileClipboard.instance,
builder: (context, _) {
final cb = FileClipboard.instance;
if (cb.isEmpty) return const SizedBox.shrink();
final cut = cb.operation == FileClipboardOperation.cut;
final count = cb.files.length;
final label = count == 1 ? '"${cb.files.first.name}"' : '$count Elemente';
return Material(
color: Theme.of(context).colorScheme.secondaryContainer,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
child: Row(
children: [
Icon(
cut ? Icons.drive_file_move_outline : Icons.copy_outlined,
size: 20,
),
),
);
},
const SizedBox(width: 12),
Expanded(
child: Text(
cut ? '$label verschieben' : '$label kopieren',
overflow: TextOverflow.ellipsis,
),
),
TextButton(
onPressed: _busy || !_canPaste ? null : _paste,
child: _busy
? const SizedBox(
width: 14,
height: 14,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Text('Hier einfügen'),
),
IconButton(
tooltip: 'Verwerfen',
icon: const Icon(Icons.close, size: 20),
onPressed: _busy ? null : cb.clear,
),
],
),
),
);
},
);
}
@@ -12,49 +12,67 @@ void showFileDetailsSheet(BuildContext context, CacheableFile file) {
showDetailsBottomSheet(
context,
header: ListTile(
leading: Icon(file.isDirectory ? Icons.folder : Icons.description_outlined, size: 32),
title: Text(file.name, style: const TextStyle(fontWeight: FontWeight.bold)),
leading: Icon(
file.isDirectory ? Icons.folder : Icons.description_outlined,
size: 32,
),
title: Text(
file.name,
style: const TextStyle(fontWeight: FontWeight.bold),
),
subtitle: Text(file.isDirectory ? 'Ordner' : (file.mimeType ?? '')),
),
children: (_) => [
_DetailRow(label: 'Pfad', value: file.path, copyable: true),
if (!file.isDirectory) _DetailRow(label: 'Größe', value: filesize(file.size)),
if (!file.isDirectory)
_DetailRow(label: 'Größe', value: filesize(file.size)),
if (file.modifiedAt != null)
_DetailRow(
label: 'Geändert',
value: '${file.modifiedAt!.formatDateTime()} (${file.modifiedAt!.formatRelative()})',
value:
'${file.modifiedAt!.formatDateTime()} (${file.modifiedAt!.formatRelative()})',
),
if (file.createdAt != null)
_DetailRow(label: 'Erstellt', value: file.createdAt!.formatDateTime()),
if (file.eTag != null) _DetailRow(label: 'ETag', value: file.eTag!, copyable: true),
if (file.eTag != null)
_DetailRow(label: 'ETag', value: file.eTag!, copyable: true),
],
);
}
class _DetailRow extends StatelessWidget {
const _DetailRow({required this.label, required this.value, this.copyable = false});
const _DetailRow({
required this.label,
required this.value,
this.copyable = false,
});
final String label;
final String value;
final bool copyable;
@override
Widget build(BuildContext context) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 90,
child: Text(label, style: TextStyle(color: Theme.of(context).colorScheme.onSurfaceVariant)),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 90,
child: Text(
label,
style: TextStyle(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
Expanded(child: SelectableText(value)),
if (copyable)
IconButton(
tooltip: 'Kopieren',
icon: const Icon(Icons.copy, size: 18),
onPressed: () => copyToClipboard(context, value),
),
],
),
),
);
Expanded(child: SelectableText(value)),
if (copyable)
IconButton(
tooltip: 'Kopieren',
icon: const Icon(Icons.copy, size: 18),
onPressed: () => copyToClipboard(context, value),
),
],
),
);
}
+47 -21
View File
@@ -138,11 +138,17 @@ class _FileElementState extends State<FileElement> {
void _onTap() {
if (widget.file.isDirectory) {
AppRoutes.openFolder(context, widget.path.toList()..add(widget.file.name));
AppRoutes.openFolder(
context,
widget.path.toList()..add(widget.file.name),
);
return;
}
if (EndpointData().getEndpointMode() == EndpointMode.stage) {
InfoDialog.show(context, 'Virtuelle Dateien im Staging Prozess können nicht heruntergeladen werden!');
InfoDialog.show(
context,
'Virtuelle Dateien im Staging Prozess können nicht heruntergeladen werden!',
);
return;
}
final status = _job?.status.value;
@@ -178,21 +184,34 @@ class _FileElementState extends State<FileElement> {
autofocus: true,
),
actions: [
TextButton(onPressed: () => Navigator.of(dialogCtx).pop(), child: const Text('Abbrechen')),
TextButton(
onPressed: () => Navigator.of(dialogCtx).pop(controller.text.trim()),
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;
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);
final destination = _joinPath(
parent,
newName,
isDirectory: widget.file.isDirectory,
);
await _runWebdavOp(() async {
final webdav = await WebdavApi.webdav;
await webdav.move(PathUri.parse(widget.file.path), PathUri.parse(destination));
await webdav.move(
PathUri.parse(widget.file.path),
PathUri.parse(destination),
);
}, errorTitle: 'Umbenennen fehlgeschlagen');
} finally {
controller.dispose();
@@ -205,10 +224,14 @@ class _FileElementState extends State<FileElement> {
} else {
FileClipboard.instance.cut([widget.file]);
}
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('"${widget.file.name}" zum ${copy ? "Kopieren" : "Verschieben"} bereitgelegt'),
duration: const Duration(seconds: 2),
));
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'"${widget.file.name}" zum ${copy ? "Kopieren" : "Verschieben"} bereitgelegt',
),
duration: const Duration(seconds: 2),
),
);
}
Future<void> _delete() async {
@@ -227,7 +250,10 @@ class _FileElementState extends State<FileElement> {
);
}
Future<void> _runWebdavOp(Future<void> Function() action, {required String errorTitle}) async {
Future<void> _runWebdavOp(
Future<void> Function() action, {
required String errorTitle,
}) async {
try {
await action();
widget.refetch();
@@ -287,13 +313,13 @@ class _FileElementState extends State<FileElement> {
@override
Widget build(BuildContext context) => ListTile(
leading: CenteredLeading(
Icon(widget.file.isDirectory ? Icons.folder : Icons.description_outlined),
),
title: Text(widget.file.name, maxLines: 2, overflow: TextOverflow.ellipsis),
subtitle: _subtitle(),
trailing: Icon(widget.file.isDirectory ? Icons.arrow_right : null),
onTap: _onTap,
onLongPress: _showActionSheet,
);
leading: CenteredLeading(
Icon(widget.file.isDirectory ? Icons.folder : Icons.description_outlined),
),
title: Text(widget.file.name, maxLines: 2, overflow: TextOverflow.ellipsis),
subtitle: _subtitle(),
trailing: Icon(widget.file.isDirectory ? Icons.arrow_right : null),
onTap: _onTap,
onLongPress: _showActionSheet,
);
}
@@ -23,37 +23,48 @@ class FilesSortActions extends StatelessWidget {
mainAxisSize: MainAxisSize.min,
children: [
PopupMenuButton<bool>(
icon: Icon(ascending ? Icons.text_rotate_up : Icons.text_rotation_down),
icon: Icon(
ascending ? Icons.text_rotate_up : Icons.text_rotation_down,
),
itemBuilder: (context) => [true, false]
.map((e) => PopupMenuItem<bool>(
value: e,
enabled: e != ascending,
child: Row(
children: [
Icon(e ? Icons.text_rotate_up : Icons.text_rotation_down,
color: theme.colorScheme.onSurface),
const SizedBox(width: 15),
Text(e ? 'Aufsteigend' : 'Absteigend'),
],
),
))
.map(
(e) => PopupMenuItem<bool>(
value: e,
enabled: e != ascending,
child: Row(
children: [
Icon(
e ? Icons.text_rotate_up : Icons.text_rotation_down,
color: theme.colorScheme.onSurface,
),
const SizedBox(width: 15),
Text(e ? 'Aufsteigend' : 'Absteigend'),
],
),
),
)
.toList(),
onSelected: onDirectionChanged,
),
PopupMenuButton<SortOption>(
icon: const Icon(Icons.sort),
itemBuilder: (context) => SortOptions.options.keys
.map((key) => PopupMenuItem<SortOption>(
value: key,
enabled: key != currentSort,
child: Row(
children: [
Icon(SortOptions.getOption(key).icon, color: theme.colorScheme.onSurface),
const SizedBox(width: 15),
Text(SortOptions.getOption(key).displayName),
],
),
))
.map(
(key) => PopupMenuItem<SortOption>(
value: key,
enabled: key != currentSort,
child: Row(
children: [
Icon(
SortOptions.getOption(key).icon,
color: theme.colorScheme.onSurface,
),
const SizedBox(width: 15),
Text(SortOptions.getOption(key).displayName),
],
),
),
)
.toList(),
onSelected: onSortChanged,
),