simplified code across the app and consolidated duplicated helpers

This commit is contained in:
2026-09-27 17:29:54 +02:00
parent 3e856d0b1c
commit ee1240f71a
155 changed files with 1772 additions and 2546 deletions
+67 -71
View File
@@ -28,13 +28,15 @@ class FilesUploadDialog extends StatefulWidget {
}
class UploadableFile {
TextEditingController fileNameController = TextEditingController();
String filePath;
String fileName;
final String filePath;
final TextEditingController fileNameController;
double? _uploadProgress;
bool isConflicting = false;
UploadableFile(this.filePath, this.fileName);
UploadableFile(this.filePath, String fileName)
: fileNameController = TextEditingController(text: fileName);
String get fileName => fileNameController.text;
}
class _FilesUploadDialogState extends State<FilesUploadDialog> {
@@ -47,10 +49,22 @@ class _FilesUploadDialogState extends State<FilesUploadDialog> {
void initState() {
super.initState();
_uploadableFiles = widget.filePaths.map((filePath) {
var fileName = filePath.split(Platform.pathSeparator).last;
return UploadableFile(filePath, fileName);
}).toList();
_uploadableFiles = widget.filePaths
.map(
(filePath) => UploadableFile(
filePath,
filePath.split(Platform.pathSeparator).last,
),
)
.toList();
}
@override
void dispose() {
for (final file in _uploadableFiles) {
file.fileNameController.dispose();
}
super.dispose();
}
void _resetProgress() {
@@ -69,6 +83,10 @@ class _FilesUploadDialogState extends State<FilesUploadDialog> {
);
}
void _clearConflict(UploadableFile file) {
if (file.isConflicting) setState(() => file.isConflicting = false);
}
Future<void> uploadFiles({bool override = false}) async {
setState(() {
_isUploading = true;
@@ -82,36 +100,39 @@ class _FilesUploadDialogState extends State<FilesUploadDialog> {
if (!mounted) return;
if (!override) {
List<dynamic> result;
final List<String> existingPaths;
try {
result = (await webdavClient.propfind(
final result = await webdavClient.propfind(
PathUri.parse(widget.remotePath),
)).responses;
);
existingPaths = [
for (final response in result.responses)
Uri.decodeComponent(response.href!),
];
} catch (e) {
if (!mounted) return;
_showUploadError('Verbindung fehlgeschlagen: $e');
return;
}
final conflictingFiles = _uploadableFiles.where((file) {
final fileName = file.fileName;
return result.any(
(element) => Uri.decodeComponent(
(element as WebDavResponse).href!,
).endsWith('/$fileName'),
);
}).toList();
final conflictingFiles = _uploadableFiles
.where(
(file) => existingPaths.any(
(path) => path.endsWith('/${file.fileName}'),
),
)
.toList();
if (conflictingFiles.isNotEmpty) {
if (!mounted) return;
final replaceFiles = await showDialog<bool>(
context: context,
barrierDismissible: false,
builder: (context) => AlertDialog(
builder: (dialogContext) => 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.',
'Eine Datei mit dem Namen "${conflictingFiles.first.fileName}" existiert bereits.',
textAlign: TextAlign.left,
)
: SingleChildScrollView(
@@ -122,27 +143,20 @@ class _FilesUploadDialogState extends State<FilesUploadDialog> {
),
actions: [
TextButton(
onPressed: () {
Navigator.pop(context, false);
},
onPressed: () => Navigator.pop(dialogContext, 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',
),
);
},
// ConfirmDialog closes itself first, so this pop closes the
// conflict dialog.
onPressed: () => ConfirmDialog(
title: 'Bestätigen?',
content:
'Bist du sicher, dass du ${conflictingFiles.length} Dateien überschreiben möchtest?',
onConfirm: () => Navigator.pop(dialogContext, true),
confirmButton: 'Ja',
cancelButton: 'Nein',
).asDialog(dialogContext),
child: const Text('Überschreiben', textAlign: TextAlign.center),
),
],
@@ -163,9 +177,9 @@ class _FilesUploadDialogState extends State<FilesUploadDialog> {
}
var uploadetFilePaths = <String>[];
for (var file in _uploadableFiles) {
for (final (fileIndex, file) in _uploadableFiles.indexed) {
var fileName = file.fileName;
var filePath = file.filePath;
final filePath = file.filePath;
if (widget.uniqueNames) {
final unique = DateTime.now().microsecondsSinceEpoch.toRadixString(36);
@@ -177,7 +191,7 @@ class _FilesUploadDialogState extends State<FilesUploadDialog> {
setState(() {
_infoText =
'${_uploadableFiles.indexOf(file) + 1}/${_uploadableFiles.length}';
'${fileIndex + 1}/${_uploadableFiles.length}';
});
// A vanished source file would otherwise surface as a cryptic
@@ -193,7 +207,6 @@ class _FilesUploadDialogState extends State<FilesUploadDialog> {
}
final HttpClientResponse uploadTask;
final fileIndex = _uploadableFiles.indexOf(file);
var lastPercent = -1;
try {
uploadTask = await webdavClient.putFile(
@@ -253,12 +266,6 @@ class _FilesUploadDialogState extends State<FilesUploadDialog> {
itemCount: _uploadableFiles.length,
itemBuilder: (context, index) {
final currentFile = _uploadableFiles[index];
// Only sync when it differs: assigning text resets selection
// and notifies the field on every (progress) rebuild.
if (currentFile.fileNameController.text !=
currentFile.fileName) {
currentFile.fileNameController.text = currentFile.fileName;
}
return ListTile(
title: TextField(
readOnly: _isUploading,
@@ -273,24 +280,11 @@ class _FilesUploadDialogState extends State<FilesUploadDialog> {
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;
});
}
_clearConflict(currentFile);
},
onEditingComplete: () => _clearConflict(currentFile),
),
subtitle:
_isUploading && (currentFile._uploadProgress ?? 0) < 1
@@ -301,21 +295,23 @@ class _FilesUploadDialogState extends State<FilesUploadDialog> {
),
)
: null,
trailing: Container(
trailing: SizedBox(
width: 24,
height: 24,
padding: EdgeInsets.zero,
child: IconButton(
tooltip: 'Datei entfernen',
padding: EdgeInsets.zero,
onPressed: () {
if (!_isUploading) {
if (_uploadableFiles.length - 1 <= 0) {
if (_uploadableFiles.length == 1) {
Navigator.of(context).pop();
}
setState(() {
_uploadableFiles.removeAt(index);
});
final removed = _uploadableFiles[index];
setState(() => _uploadableFiles.removeAt(index));
// Its TextField is still mounted until the rebuild.
WidgetsBinding.instance.addPostFrameCallback(
(_) => removed.fileNameController.dispose(),
);
}
},
icon: const Icon(Icons.delete_outlined),
@@ -342,7 +338,7 @@ class _FilesUploadDialogState extends State<FilesUploadDialog> {
child: const Text('Abbrechen'),
),
),
const Expanded(child: SizedBox.shrink()),
const Spacer(),
Visibility(
visible: _isUploading,
replacement: TextButton(