added upload with multiple files #61
| @@ -1,233 +0,0 @@ | ||||
| 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<String> 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<FileUploadDialog> createState() => _FileUploadDialogState(); | ||||
| } | ||||
|  | ||||
| class _FileUploadDialogState extends State<FileUploadDialog> { | ||||
|   FileUploadState state = FileUploadState.naming; | ||||
|   CancelableOperation? cancelableOperation; | ||||
|   late String targetFileName; | ||||
|   late String remoteFolderName; | ||||
|   late String fullRemotePath = "${widget.remotePath.join("/")}/$targetFileName"; | ||||
|   String? lastError; | ||||
|  | ||||
|   TextEditingController fileNameController = TextEditingController(); | ||||
|  | ||||
|  | ||||
|   Future<void> upload({bool override = false}) async { | ||||
|     setState(() { | ||||
|       state = FileUploadState.upload; | ||||
|     }); | ||||
|  | ||||
|     var webdavClient = await WebdavApi.webdav; | ||||
|  | ||||
|     if(!override) { | ||||
|       setState(() { | ||||
|         state = FileUploadState.checkConflict; | ||||
|       }); | ||||
|       var 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; | ||||
|         }); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     var uploadTask = webdavClient.putFile(File(widget.localPath), FileStat.statSync(widget.localPath), PathUri.parse(fullRemotePath)); // TODO use onProgress from putFile | ||||
|     uploadTask.then(Future<HttpClientResponse?>.value).catchError((e) { | ||||
|       setState(() { | ||||
|         state = FileUploadState.error; | ||||
|       }); | ||||
|       return null; | ||||
|     }); | ||||
|  | ||||
|  | ||||
|     cancelableOperation = CancelableOperation<HttpClientResponse>.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 | ||||
| } | ||||
|   | ||||
| @@ -4,6 +4,7 @@ import 'dart:io'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:loader_overlay/loader_overlay.dart'; | ||||
| import 'package:nextcloud/nextcloud.dart'; | ||||
| import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart'; | ||||
| import 'package:provider/provider.dart'; | ||||
|  | ||||
| import '../../../api/marianumcloud/webdav/queries/listFiles/cacheableFile.dart'; | ||||
| @@ -15,8 +16,8 @@ import '../../../storage/base/settingsProvider.dart'; | ||||
| import '../../../widget/loadingSpinner.dart'; | ||||
| import '../../../widget/placeholderView.dart'; | ||||
| import '../../../widget/filePick.dart'; | ||||
| import 'fileUploadDialog.dart'; | ||||
| import 'fileElement.dart'; | ||||
| import 'filesUploadDialog.dart'; | ||||
|  | ||||
| class Files extends StatefulWidget { | ||||
|   final List<String> path; | ||||
| @@ -88,7 +89,6 @@ class _FilesState extends State<Files> { | ||||
|     ListFilesCache( | ||||
|         path: widget.path.isEmpty ? '/' : widget.path.join('/'), | ||||
|         onUpdate: (ListFilesResponse d) { | ||||
|           if(!context.mounted) return; // prevent setState when widget is possibly already disposed | ||||
|           d.files.removeWhere((element) => element.name.isEmpty || element.name == widget.path.lastOrNull()); | ||||
|           setState(() { | ||||
|             data = d; | ||||
| @@ -97,6 +97,18 @@ class _FilesState extends State<Files> { | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   Future<void> mediaUpload(List<String>? paths) async { | ||||
|     if(paths == null) return; | ||||
|  | ||||
|     pushScreen( | ||||
|       context, | ||||
|       withNavBar: false, | ||||
|       screen: FilesUploadDialog(filePaths: paths, remotePath: widget.path.join('/'), onUploadFinished: (uploadedFilePaths) => _query()), | ||||
|     ); | ||||
|  | ||||
|     return; | ||||
|   } | ||||
|  | ||||
| 
					
					Pupsi marked this conversation as resolved
					
				 | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     var files = data?.sortBy( | ||||
| @@ -144,7 +156,7 @@ class _FilesState extends State<Files> { | ||||
|                     children: [ | ||||
|                       Icon(SortOptions.getOption(key).icon, color: Theme.of(context).colorScheme.onSurface), | ||||
|                       const SizedBox(width: 15), | ||||
|                       Text(SortOptions.getOption(key).displayName) | ||||
|                       Text(SortOptions.getOption(key).displayName), | ||||
|                     ], | ||||
|                   ) | ||||
|               )).toList(), | ||||
| @@ -197,7 +209,6 @@ class _FilesState extends State<Files> { | ||||
|                   leading: const Icon(Icons.upload_file), | ||||
|                   title: const Text('Aus Dateien hochladen'), | ||||
|                   onTap: () { | ||||
|                     context.loaderOverlay.show(); | ||||
|                     FilePick.documentPick().then(mediaUpload); | ||||
|                     Navigator.of(context).pop(); | ||||
|                   }, | ||||
| @@ -208,9 +219,8 @@ class _FilesState extends State<Files> { | ||||
|                     leading: const Icon(Icons.add_a_photo_outlined), | ||||
|                     title: const Text('Aus Gallerie hochladen'), | ||||
|                     onTap: () { | ||||
|                       context.loaderOverlay.show(); | ||||
|                       FilePick.galleryPick().then((value) { | ||||
|                         mediaUpload(value?.path); | ||||
|                       FilePick.multipleGalleryPick().then((value) { | ||||
|                         if(value != null) mediaUpload(value.map((e) => e.path).toList()); | ||||
|                       }); | ||||
|                       Navigator.of(context).pop(); | ||||
|                     }, | ||||
| @@ -239,15 +249,4 @@ class _FilesState extends State<Files> { | ||||
|       ) | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   Future<void> mediaUpload(String? path) async { | ||||
|     context.loaderOverlay.hide(); | ||||
|  | ||||
|     if(path == null) { | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     var fileName = path.split(Platform.pathSeparator).last; | ||||
|     showDialog(context: context, builder: (context) => FileUploadDialog(localPath: path, remotePath: widget.path, fileName: fileName, onUploadFinished: _query), barrierDismissible: false); | ||||
|   } | ||||
| } | ||||
|   | ||||
							
								
								
									
										311
									
								
								lib/view/pages/files/filesUploadDialog.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,311 @@ | ||||
| import 'dart:io'; | ||||
|  | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:loader_overlay/loader_overlay.dart'; | ||||
| import 'package:nextcloud/nextcloud.dart'; | ||||
| import 'package:uuid/uuid.dart'; | ||||
|  | ||||
| import '../../../api/marianumcloud/webdav/webdavApi.dart'; | ||||
| import '../../../widget/confirmDialog.dart'; | ||||
| import '../../../widget/focusBehaviour.dart'; | ||||
|  | ||||
| class FilesUploadDialog extends StatefulWidget { | ||||
|   final List<String> filePaths; | ||||
|   final String remotePath; | ||||
| 
					
					Pupsi marked this conversation as resolved
					
				 
				
					
						MineTec
						commented  wofür ist hier der Parameter  wofür ist hier der Parameter `List<String> uploadedFilePaths`? | ||||
|   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}); | ||||
|  | ||||
|   @override | ||||
|   State<FilesUploadDialog> createState() => _FilesUploadDialogState(); | ||||
| } | ||||
|  | ||||
| class UploadableFile { | ||||
|   TextEditingController fileNameController = TextEditingController(); | ||||
|   String filePath; | ||||
|   String fileName; | ||||
|   double? _uploadProgress; | ||||
|   bool isConflicting = false; | ||||
|  | ||||
|   UploadableFile(this.filePath, this.fileName); | ||||
| } | ||||
|  | ||||
|  | ||||
| class _FilesUploadDialogState extends State<FilesUploadDialog> { | ||||
| 
					
					Pupsi marked this conversation as resolved
					
				 
				
					
						MineTec
						commented  variable nicht leer initialisieren und durch addAll popularisieren du kannst die variable hier late setzen und im initState setzen variable nicht leer initialisieren und durch addAll popularisieren
du kannst die variable hier late setzen und im initState setzen | ||||
|   late List<UploadableFile> _uploadableFiles; | ||||
|   bool _isUploading = false; | ||||
| 
					
					Pupsi marked this conversation as resolved
					
				 
				
					
						MineTec
						commented  passenderer Variablenname  passenderer Variablenname `_overallProgressValue`
damit klar ist das sie den Gesamtfortschritt wiederspiegelt | ||||
|   double _overallProgressValue = 0.0; | ||||
|   String _infoText = ''; | ||||
|  | ||||
|   @override | ||||
|   void initState() { | ||||
|     super.initState(); | ||||
|  | ||||
|     _uploadableFiles = widget.filePaths.map((filePath) { | ||||
|       var fileName = filePath.split(Platform.pathSeparator).last; | ||||
|       return UploadableFile(filePath, fileName); | ||||
|     }).toList(); | ||||
|   } | ||||
|  | ||||
| 
					
					Pupsi marked this conversation as resolved
					
						
						
							Outdated
						
					
				 
				
					
						MineTec
						commented  was ist das für ein error code? Ein http status code? Falls ja variable enstprechend umbenennen und in der message auch angeben was ist das für ein error code? Ein http status code?
Falls ja variable enstprechend umbenennen und in der message auch angeben | ||||
|   void showHttpErrorCode(int httpErrorCode){ | ||||
| 
					
					Pupsi marked this conversation as resolved
					
				 
				
					
						MineTec
						commented  ungenutzer code raus ungenutzer code raus | ||||
|     showDialog( | ||||
|       context: context, | ||||
|       builder: (BuildContext context) => AlertDialog( | ||||
|           title: const Text('Ein Fehler ist aufgetreten'), | ||||
|           contentPadding: const EdgeInsets.all(10), | ||||
|           content: Text('Error code: $httpErrorCode'), | ||||
|           actions: [ | ||||
|             TextButton( | ||||
|               onPressed: () => Navigator.of(context).pop(), | ||||
|               child: const Text('Schließen', textAlign: TextAlign.center), | ||||
|             ), | ||||
|           ], | ||||
|         ) | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   Future<void> uploadFiles({bool override = false}) async { | ||||
|     setState(() { | ||||
| 
					
					Pupsi marked this conversation as resolved
					
				 
				
					
						MineTec
						commented  dein ehemaliger name  dein ehemaliger name `upload` ist in diesem kontext doch besser, vielleicht auch `uploadFiles` | ||||
|       _isUploading = true; | ||||
|       _infoText = 'Vorbereiten'; | ||||
|       for (var file in _uploadableFiles) { | ||||
|         file.isConflicting = false; | ||||
|       } | ||||
|     }); | ||||
|  | ||||
| 
					
					Pupsi marked this conversation as resolved
					
				 
				
					
						MineTec
						commented  setState für jede datei auszuführen ist unnötig und ressourcenintensiv. Mit setState auf zeile 70 verbinden und inline schreiben mit 
 setState für jede datei auszuführen ist unnötig und ressourcenintensiv.
Mit setState auf zeile 70 verbinden und inline schreiben mit
`_uploadableFiles.foreach(f => f.isConflicting = false)` | ||||
|     var webdavClient = await WebdavApi.webdav; | ||||
|  | ||||
|     if (!override) { | ||||
|       var result = (await webdavClient.propfind(PathUri.parse(widget.remotePath))).responses; | ||||
|       var conflictingFiles = _uploadableFiles.where((file) { | ||||
|         var fileName = file.fileName; | ||||
|         return result.any((element) => Uri.decodeComponent(element.href!).endsWith('/$fileName')); | ||||
|       }).toList(); | ||||
|  | ||||
|       if(conflictingFiles.isNotEmpty) { | ||||
|         bool replaceFiles = await showDialog( | ||||
| 
					
					Pupsi marked this conversation as resolved
					
				 
				
					
						MineTec
						commented  umschreiben in fluente schreibweise umschreiben in fluente schreibweise
```
conflictingFiles = _uploadableFiles.where(...)
``` | ||||
|           context: context, | ||||
|           barrierDismissible: false, | ||||
|           builder: (context) => AlertDialog( | ||||
| 
					
					Pupsi marked this conversation as resolved
					
						
						
							Outdated
						
					
				 
				
					
						MineTec
						commented  einfaches return 
 ggf auch ohne return als pfeilsyntax einfaches return
`return result.any((element) => Uri.decodeComponent(element.href!).endsWith('/$fileName'))`
ggf auch ohne return als pfeilsyntax | ||||
|               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), | ||||
|                 ), | ||||
|                 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); | ||||
| 
					
					Pupsi marked this conversation as resolved
					
						
						
							Outdated
						
					
				 
				
					
						MineTec
						commented  
 `Umbenennen` 
				
					
						Pupsi
						commented  Passt Bearbeiten nicht besser? Es gibt ja dann auch noch die Möglichkeit neben dem Umbenennen auch die Datei zu löschen oder komplett abzubrechen. Passt Bearbeiten nicht besser? Es gibt ja dann auch noch die Möglichkeit neben dem Umbenennen auch die Datei zu löschen oder komplett abzubrechen. | ||||
|                         }, | ||||
|                         confirmButton: 'Ja', | ||||
|                         cancelButton: 'Nein', | ||||
|                         ), | ||||
|                     ); | ||||
|  | ||||
| 
					
					Pupsi marked this conversation as resolved
					
						
						
							Outdated
						
					
				 
				
					
						MineTec
						commented  
 diese Aktion sollte nochmal ein "Bist du sicher" dialog öffnen. Du kannst hierzu den bestehenden ConfirmDialog verwenden `Überschreiben`
diese Aktion sollte nochmal ein "Bist du sicher" dialog öffnen. Du kannst hierzu den bestehenden ConfirmDialog verwenden | ||||
|                   }, | ||||
|                   child: const Text('Überschreiben', textAlign: TextAlign.center), | ||||
|                 ), | ||||
|               ], | ||||
|             ) | ||||
|         ); | ||||
|  | ||||
|         if(!replaceFiles) { | ||||
|           setState(() { | ||||
|             _isUploading = false; | ||||
|             _overallProgressValue = 0.0; | ||||
|             _infoText = ''; | ||||
|             for (var element in conflictingFiles) { | ||||
|               element.isConflicting = true; | ||||
|             } | ||||
|           }); | ||||
|           return; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     var uploadetFilePaths = <String>[]; | ||||
|     for (var file in _uploadableFiles) { | ||||
|       var fileName = file.fileName; | ||||
|       var filePath = file.filePath; | ||||
| 
					
					Pupsi marked this conversation as resolved
					
						
						
							Outdated
						
					
				 
				
					
						MineTec
						commented  state variablen in setState ändern state variablen in setState ändern | ||||
|  | ||||
|       if(widget.uniqueNames) fileName = '${fileName.split('.').first}-${const Uuid().v4()}.${fileName.split('.').last}'; | ||||
|  | ||||
|       var fullRemotePath = '${widget.remotePath}/$fileName'; | ||||
|  | ||||
|       setState(() { | ||||
|         _infoText = '${_uploadableFiles.indexOf(file) + 1}/${_uploadableFiles.length}'; | ||||
|       }); | ||||
|  | ||||
|       var uploadTask = await webdavClient.putFile( | ||||
|         File(filePath), | ||||
|         FileStat.statSync(filePath), | ||||
|         PathUri.parse(fullRemotePath), | ||||
|         onProgress: (progress) { | ||||
|           setState(() { | ||||
|             file._uploadProgress = progress; | ||||
|             _overallProgressValue = ((progress + _uploadableFiles.indexOf(file)) / _uploadableFiles.length).toDouble(); | ||||
|           }); | ||||
|         }, | ||||
|       ); | ||||
|  | ||||
|       if(uploadTask.statusCode < 200 || uploadTask.statusCode > 299) { | ||||
|         setState(() { | ||||
| 
					
					Pupsi marked this conversation as resolved
					
				 
				
					
						MineTec
						commented  kommentar raus kommentar raus | ||||
|           _isUploading = false; | ||||
|           _overallProgressValue = 0.0; | ||||
|           _infoText = ''; | ||||
|         }); | ||||
|         Navigator.of(context).pop(); | ||||
|         showHttpErrorCode(uploadTask.statusCode); | ||||
|       } else { | ||||
|         uploadetFilePaths.add(fullRemotePath); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     setState(() { | ||||
|       _isUploading = false; | ||||
|       _overallProgressValue = 0.0; | ||||
|       _infoText = ''; | ||||
|     }); | ||||
|     Navigator.of(context).pop(); | ||||
|     widget.onUploadFinished(uploadetFilePaths); | ||||
|   } | ||||
|  | ||||
|   @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: const TextStyle(color: Colors.red), | ||||
|                       ), | ||||
|                       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, | ||||
|                         onPressed: () { | ||||
|                           if(!_isUploading) { | ||||
|                             if(_uploadableFiles.length-1 <= 0) Navigator.of(context).pop(); | ||||
|                             setState(() { | ||||
|                               _uploadableFiles.removeAt(index); | ||||
|                             }); | ||||
|                           } | ||||
|                         }, | ||||
|                         icon: const Icon(Icons.delete_outlined), | ||||
|                       ), | ||||
|                     ), | ||||
|                   ); | ||||
|                 }, | ||||
|               ), | ||||
|             ), | ||||
|             Padding( | ||||
|               padding: const EdgeInsets.only(left: 15, right: 15, bottom: 15, top: 5), | ||||
|               child: Row( | ||||
| 
					
					Pupsi marked this conversation as resolved
					
				 
				
					
						MineTec
						commented  das ist sehr verwirrend.... bitte nur die tonne rechts, die das element löscht. Der "Name" sollte nicht per button löschbar sein. das ist sehr verwirrend....
bitte nur die tonne rechts, die das element löscht.
Der "Name" sollte nicht per button löschbar sein. | ||||
|                 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)), | ||||
|                         ], | ||||
|                       ), | ||||
|                     ), | ||||
|  | ||||
|  | ||||
|                   ), | ||||
|                 ], | ||||
|               ), | ||||
|             ), | ||||
|           ], | ||||
|         ), | ||||
|       ), | ||||
|     ); | ||||
| } | ||||
| @@ -1,10 +1,9 @@ | ||||
|  | ||||
| import 'dart:io'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:loader_overlay/loader_overlay.dart'; | ||||
| import 'package:nextcloud/nextcloud.dart'; | ||||
| import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart'; | ||||
| import 'package:provider/provider.dart'; | ||||
| import 'package:uuid/uuid.dart'; | ||||
|  | ||||
| import '../../../../api/marianumcloud/files-sharing/fileSharingApi.dart'; | ||||
| import '../../../../api/marianumcloud/files-sharing/fileSharingApiParams.dart'; | ||||
| @@ -15,7 +14,7 @@ import '../../../../model/chatList/chatProps.dart'; | ||||
| import '../../../../storage/base/settingsProvider.dart'; | ||||
| import '../../../../widget/filePick.dart'; | ||||
| import '../../../../widget/focusBehaviour.dart'; | ||||
| import '../../files/fileUploadDialog.dart'; | ||||
| import '../../files/filesUploadDialog.dart'; | ||||
|  | ||||
| class ChatTextfield extends StatefulWidget { | ||||
|   final String sendToToken; | ||||
| @@ -34,32 +33,37 @@ class _ChatTextfieldState extends State<ChatTextfield> { | ||||
|     Provider.of<ChatProps>(context, listen: false).run(); | ||||
|   } | ||||
|  | ||||
|   Future<void> mediaUpload(String? path) async { | ||||
|     context.loaderOverlay.hide(); | ||||
|  | ||||
|     if(path == null) { | ||||
|       return; | ||||
|   void share(String shareFolder, List<String> filePaths) { | ||||
|     for (var element in filePaths) { | ||||
|       var fileName = element.split(Platform.pathSeparator).last; | ||||
|       FileSharingApi().share(FileSharingApiParams( | ||||
|           shareType: 10, | ||||
|           shareWith: widget.sendToToken, | ||||
|           path: '$shareFolder/$fileName', | ||||
|       )).then((value) => _query()); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   Future<void> mediaUpload(List<String>? paths) async { | ||||
|     if (paths == null) return; | ||||
|  | ||||
|     var filename = "${path.split("/").last.split(".").first}-${const Uuid().v4()}.${path.split(".").last}"; | ||||
|     var shareFolder = 'MarianumMobile'; | ||||
|     WebdavApi.webdav.then((webdav) { | ||||
|       webdav.mkcol(PathUri.parse('/$shareFolder')); | ||||
|     }); | ||||
|  | ||||
|     showDialog(context: context, builder: (context) => FileUploadDialog( | ||||
|       doShowFinish: false, | ||||
|       fileName: filename, | ||||
|       localPath: path, | ||||
|       remotePath: [shareFolder], | ||||
|       onUploadFinished: () { | ||||
|         FileSharingApi().share(FileSharingApiParams( | ||||
|           shareType: 10, | ||||
|           shareWith: widget.sendToToken, | ||||
|           path: '$shareFolder/$filename', | ||||
|         )).then((value) => _query()); | ||||
|       }, | ||||
|     ), barrierDismissible: false); | ||||
|     pushScreen( | ||||
|       context, | ||||
|       withNavBar: false, | ||||
|       screen: FilesUploadDialog( | ||||
|         filePaths: paths, | ||||
|         remotePath: shareFolder, | ||||
|         onUploadFinished: (uploadedFilePaths) { | ||||
|           share(shareFolder, uploadedFilePaths); | ||||
|         }, | ||||
|         uniqueNames: true, | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   void setDraft(String text) { | ||||
| @@ -97,7 +101,6 @@ class _ChatTextfieldState extends State<ChatTextfield> { | ||||
|                             leading: const Icon(Icons.file_open), | ||||
|                             title: const Text('Aus Dateien auswählen'), | ||||
|                             onTap: () { | ||||
|                               context.loaderOverlay.show(); | ||||
|                               FilePick.documentPick().then(mediaUpload); | ||||
|                               Navigator.of(context).pop(); | ||||
|                             }, | ||||
| @@ -108,9 +111,8 @@ class _ChatTextfieldState extends State<ChatTextfield> { | ||||
|                               leading: const Icon(Icons.image), | ||||
|                               title: const Text('Aus Gallerie auswählen'), | ||||
|                               onTap: () { | ||||
|                                 context.loaderOverlay.show(); | ||||
|                                 FilePick.galleryPick().then((value) { | ||||
|                                   mediaUpload(value?.path); | ||||
|                                 FilePick.multipleGalleryPick().then((value) { | ||||
|                                   if(value != null) mediaUpload(value.map((e) => e.path).toList()); | ||||
|                                 }); | ||||
|                                 Navigator.of(context).pop(); | ||||
|                               }, | ||||
|   | ||||
| @@ -13,8 +13,17 @@ class FilePick { | ||||
|     return null; | ||||
|   } | ||||
|  | ||||
|   static Future<String?> documentPick() async { | ||||
|     var result = await FilePicker.platform.pickFiles(); | ||||
|     return result?.files.single.path; | ||||
|   static Future<List<XFile>?> multipleGalleryPick() async { | ||||
|     final pickedImages = await _picker.pickMultiImage(); | ||||
|     if(pickedImages.isNotEmpty) { | ||||
|       return pickedImages; | ||||
|     } | ||||
|     return null; | ||||
|   } | ||||
|  | ||||
|   static Future<List<String>?> documentPick() async { | ||||
|     var result = await FilePicker.platform.pickFiles(allowMultiple: true); | ||||
|     var paths = result?.files.nonNulls.map((e) => e.path).toList(); | ||||
|     return paths?.nonNulls.toList(); | ||||
|   } | ||||
| } | ||||
|   | ||||
funktioniert das _query callback ohne die klammern()?
wofüg irst das leere return hier?