import 'dart:developer'; import 'dart:io'; import 'package:async/async.dart'; import 'package:flutter/material.dart'; import 'package:nextcloud/nextcloud.dart'; import '../../../api/marianumcloud/webdav/webdavApi.dart'; class FileUploadDialog extends StatefulWidget { final String localPath; final List remotePath; final String fileName; final void Function() onUploadFinished; final bool doShowFinish; const FileUploadDialog({super.key, required this.localPath, required this.remotePath, required this.fileName, required this.onUploadFinished, this.doShowFinish = true}); @override State createState() => _FileUploadDialogState(); } class _FileUploadDialogState extends State { FileUploadState state = FileUploadState.naming; CancelableOperation? cancelableOperation; late String targetFileName; late String remoteFolderName; late String fullRemotePath = "${widget.remotePath.join("/")}/$targetFileName"; String? lastError; TextEditingController fileNameController = TextEditingController(); void upload({bool override = false}) async { setState(() { state = FileUploadState.upload; }); WebDavClient webdavClient = await WebdavApi.webdav; if(!override) { setState(() { state = FileUploadState.checkConflict; }); List result = (await webdavClient.propfind(PathUri.parse(widget.remotePath.join('/')))).responses; if(result.any((element) => element.href!.endsWith('/$targetFileName'))) { setState(() { state = FileUploadState.conflict; }); return; } else { setState(() { state = FileUploadState.upload; }); } } Future uploadTask = webdavClient.putFile(File(widget.localPath), FileStat.statSync(widget.localPath), PathUri.parse(fullRemotePath)); // TODO use onProgress from putFile uploadTask.then(Future.value).catchError((e) { setState(() { state = FileUploadState.error; }); return null; }); cancelableOperation = CancelableOperation.fromFuture( uploadTask, onCancel: () => log('Upload cancelled'), ); cancelableOperation!.then((value) { setState(() { state = FileUploadState.done; }); }); } void cancel() { cancelableOperation?.cancel(); setState(() { state = FileUploadState.naming; }); } @override void initState() { super.initState(); targetFileName = widget.fileName; remoteFolderName = widget.remotePath.isNotEmpty ? widget.remotePath.last : '/'; fileNameController.text = widget.fileName; } @override Widget build(BuildContext context) { if(state == FileUploadState.naming) { return AlertDialog( title: const Text('Datei hochladen'), content: Column( mainAxisSize: MainAxisSize.min, children: [ TextField( controller: fileNameController, onChanged: (input) { targetFileName = input; }, autocorrect: false, decoration: const InputDecoration( labelText: 'Dateiname', ), ), ], ), actions: [ TextButton(onPressed: () { Navigator.of(context).pop(); }, child: const Text('Abbrechen')), TextButton(onPressed: () async { upload(); }, child: const Text('Hochladen')), ], ); } if(state == FileUploadState.conflict) { return AlertDialog( icon: const Icon(Icons.error_outline), title: const Text('Datei konflikt'), content: Column( mainAxisSize: MainAxisSize.min, children: [ Text("Es gibt bereits eine Datei mit dem Namen $targetFileName in dem ausgewählten Ordner '$remoteFolderName'", textAlign: TextAlign.center), ], ), actions: [ TextButton(onPressed: () { setState(() { state = FileUploadState.naming; }); }, child: const Text('Datei umbenennen')), TextButton(onPressed: () { upload(override: true); }, child: const Text('Datei überschreiben')), ], ); } if(state == FileUploadState.upload || state == FileUploadState.checkConflict) { return AlertDialog( icon: const Icon(Icons.upload), title: const Text('Hochladen'), content: Column( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, children: [ Visibility( visible: state == FileUploadState.upload, replacement: const Text('Prüfe auf dateikonflikte...'), child: const Text('Upload läuft!\nDies kann je nach Dateigröße einige Zeit dauern...', textAlign: TextAlign.center), ), const SizedBox(height: 30), const CircularProgressIndicator() ], ), actions: const [ // TODO implement working upload cancelling // TextButton(onPressed: () { // cancel(); // }, child: const Text("Abbrechen")), ], ); } if(state == FileUploadState.done) { widget.onUploadFinished(); if(!widget.doShowFinish) { Navigator.of(context).pop(); return const SizedBox.shrink(); } return AlertDialog( icon: const Icon(Icons.done), title: const Text('Upload fertig'), content: Column( mainAxisSize: MainAxisSize.min, children: [ Text("Die Datei wurde erfolgreich nach '$remoteFolderName' hochgeladen!", textAlign: TextAlign.center), ], ), actions: [ TextButton(onPressed: () { Navigator.of(context).pop(); }, child: const Text('Fertig')), ], ); } if(state == FileUploadState.error) { return AlertDialog( icon: const Icon(Icons.error_outline), title: const Text('Fehler'), content: const Column( mainAxisSize: MainAxisSize.min, children: [ Text('Es ist ein Fehler aufgetreten!', textAlign: TextAlign.center), ], ), actions: [ TextButton(onPressed: () { Navigator.of(context).pop(); }, child: const Text('Schlißen')), ], ); } throw UnimplementedError('Invalid state'); } } enum FileUploadState { naming, checkConflict, conflict, upload, done, error }