refactored data providers with centralized cache resolution, unified UI using custom dialogs and bottom sheets, and enhanced network error handling for Dio and TLS errors

This commit is contained in:
2026-05-08 20:01:45 +02:00
parent c62a14645a
commit 9e139b5704
37 changed files with 595 additions and 753 deletions
+74 -88
View File
@@ -11,6 +11,7 @@ import '../../../../utils/download_manager.dart';
import '../../../../utils/file_clipboard.dart';
import '../../../../widget/centered_leading.dart';
import '../../../../widget/confirm_dialog.dart';
import '../../../../widget/details_bottom_sheet.dart';
import '../../../../widget/info_dialog.dart';
import 'file_details_sheet.dart';
@@ -77,13 +78,7 @@ class _FileElementState extends State<FileElement> {
DownloadManager.instance.clear(widget.file.path);
_detachJob();
setState(() {});
showDialog<void>(
context: context,
builder: (context) => AlertDialog(
title: const Text('Download'),
content: Text(message),
),
);
InfoDialog.show(context, message, title: 'Download', copyable: true);
} else if (status is DownloadCancelled) {
DownloadManager.instance.clear(widget.file.path);
_detachJob();
@@ -172,32 +167,36 @@ class _FileElementState extends State<FileElement> {
Future<void> _rename() async {
final controller = TextEditingController(text: widget.file.name);
final newName = await showDialog<String>(
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'),
try {
final newName = await showDialog<String>(
context: context,
builder: (dialogCtx) => AlertDialog(
title: const Text('Umbenennen'),
content: TextField(
controller: controller,
decoration: const InputDecoration(labelText: 'Neuer Name'),
autofocus: true,
),
],
),
);
if (newName == null || newName.isEmpty || newName == widget.file.name) return;
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 {
final webdav = await WebdavApi.webdav;
await webdav.move(PathUri.parse(widget.file.path), PathUri.parse(destination));
}, errorTitle: 'Umbenennen fehlgeschlagen');
final parent = _parentPathOf(widget.file.path);
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));
}, errorTitle: 'Umbenennen fehlgeschlagen');
} finally {
controller.dispose();
}
}
void _putOnClipboard({required bool copy}) {
@@ -234,68 +233,55 @@ class _FileElementState extends State<FileElement> {
widget.refetch();
} on Object catch (e) {
if (!mounted) return;
await showDialog<void>(
context: context,
builder: (dialogCtx) => AlertDialog(
title: Text(errorTitle),
content: Text(e.toString()),
actions: [TextButton(onPressed: () => Navigator.of(dialogCtx).pop(), child: const Text('OK'))],
),
);
InfoDialog.show(context, e.toString(), title: errorTitle, copyable: true);
}
}
void _showActionSheet() {
showModalBottomSheet<void>(
context: context,
showDragHandle: true,
builder: (sheetCtx) => SafeArea(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
leading: const CenteredLeading(Icon(Icons.info_outline)),
title: const Text('Info'),
onTap: () {
Navigator.of(sheetCtx).pop();
showFileDetailsSheet(context, widget.file);
},
),
ListTile(
leading: const CenteredLeading(Icon(Icons.drive_file_rename_outline)),
title: const Text('Umbenennen'),
onTap: () {
Navigator.of(sheetCtx).pop();
_rename();
},
),
ListTile(
leading: const CenteredLeading(Icon(Icons.drive_file_move_outline)),
title: const Text('Verschieben'),
onTap: () {
Navigator.of(sheetCtx).pop();
_putOnClipboard(copy: false);
},
),
ListTile(
leading: const CenteredLeading(Icon(Icons.copy_outlined)),
title: const Text('Kopieren'),
onTap: () {
Navigator.of(sheetCtx).pop();
_putOnClipboard(copy: true);
},
),
ListTile(
leading: const CenteredLeading(Icon(Icons.delete_outline)),
title: const Text('Löschen'),
onTap: () {
Navigator.of(sheetCtx).pop();
_delete();
},
),
],
showDetailsBottomSheet(
context,
children: (sheetCtx) => [
ListTile(
leading: const CenteredLeading(Icon(Icons.info_outline)),
title: const Text('Info'),
onTap: () {
Navigator.of(sheetCtx).pop();
showFileDetailsSheet(context, widget.file);
},
),
),
ListTile(
leading: const CenteredLeading(Icon(Icons.drive_file_rename_outline)),
title: const Text('Umbenennen'),
onTap: () {
Navigator.of(sheetCtx).pop();
_rename();
},
),
ListTile(
leading: const CenteredLeading(Icon(Icons.drive_file_move_outline)),
title: const Text('Verschieben'),
onTap: () {
Navigator.of(sheetCtx).pop();
_putOnClipboard(copy: false);
},
),
ListTile(
leading: const CenteredLeading(Icon(Icons.copy_outlined)),
title: const Text('Kopieren'),
onTap: () {
Navigator.of(sheetCtx).pop();
_putOnClipboard(copy: true);
},
),
ListTile(
leading: const CenteredLeading(Icon(Icons.delete_outline)),
title: const Text('Löschen'),
onTap: () {
Navigator.of(sheetCtx).pop();
_delete();
},
),
],
);
}
@@ -2,9 +2,6 @@ import 'package:flutter/material.dart';
import '../data/sort_options.dart';
/// AppBar action buttons for sort direction (asc/desc) and sort field
/// (name/date/size). Pure UI owners pass current values + selection
/// callbacks.
class FilesSortActions extends StatelessWidget {
final SortOption currentSort;
final bool ascending;