added upload with multiple files #61
@ -1,9 +1,11 @@
|
|||||||
|
|
||||||
|
import 'dart:developer';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:loader_overlay/loader_overlay.dart';
|
import 'package:loader_overlay/loader_overlay.dart';
|
||||||
import 'package:nextcloud/nextcloud.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:provider/provider.dart';
|
||||||
|
|
||||||
import '../../../api/marianumcloud/webdav/queries/listFiles/cacheableFile.dart';
|
import '../../../api/marianumcloud/webdav/queries/listFiles/cacheableFile.dart';
|
||||||
@ -15,8 +17,8 @@ import '../../../storage/base/settingsProvider.dart';
|
|||||||
import '../../../widget/loadingSpinner.dart';
|
import '../../../widget/loadingSpinner.dart';
|
||||||
import '../../../widget/placeholderView.dart';
|
import '../../../widget/placeholderView.dart';
|
||||||
import '../../../widget/filePick.dart';
|
import '../../../widget/filePick.dart';
|
||||||
import 'fileUploadDialog.dart';
|
|
||||||
import 'fileElement.dart';
|
import 'fileElement.dart';
|
||||||
|
import 'filesUploadDialog.dart';
|
||||||
|
|
||||||
class Files extends StatefulWidget {
|
class Files extends StatefulWidget {
|
||||||
final List<String> path;
|
final List<String> path;
|
||||||
@ -90,6 +92,7 @@ class _FilesState extends State<Files> {
|
|||||||
ListFilesCache(
|
ListFilesCache(
|
||||||
path: widget.path.isEmpty ? '/' : widget.path.join('/'),
|
path: widget.path.isEmpty ? '/' : widget.path.join('/'),
|
||||||
onUpdate: (ListFilesResponse d) {
|
onUpdate: (ListFilesResponse d) {
|
||||||
|
log('_query');
|
||||||
Pupsi marked this conversation as resolved
Outdated
|
|||||||
if(!context.mounted) return; // prevent setState when widget is possibly already disposed
|
if(!context.mounted) return; // prevent setState when widget is possibly already disposed
|
||||||
Pupsi marked this conversation as resolved
Outdated
MineTec
commented
kommentar raus kommentar raus
|
|||||||
d.files.removeWhere((element) => element.name.isEmpty || element.name == widget.path.lastOrNull());
|
d.files.removeWhere((element) => element.name.isEmpty || element.name == widget.path.lastOrNull());
|
||||||
setState(() {
|
setState(() {
|
||||||
@ -99,6 +102,18 @@ class _FilesState extends State<Files> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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),
|
||||||
Pupsi marked this conversation as resolved
MineTec
commented
funktioniert das _query callback ohne die klammern()? funktioniert das _query callback ohne die klammern()?
MineTec
commented
wofüg irst das leere return hier? wofüg irst das leere return hier?
|
|||||||
|
);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
List<CacheableFile> files = data?.sortBy(
|
List<CacheableFile> files = data?.sortBy(
|
||||||
@ -149,7 +164,7 @@ class _FilesState extends State<Files> {
|
|||||||
children: [
|
children: [
|
||||||
Icon(SortOptions.getOption(key).icon, color: Theme.of(context).colorScheme.onSurface),
|
Icon(SortOptions.getOption(key).icon, color: Theme.of(context).colorScheme.onSurface),
|
||||||
const SizedBox(width: 15),
|
const SizedBox(width: 15),
|
||||||
Text(SortOptions.getOption(key).displayName)
|
Text(SortOptions.getOption(key).displayName),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
)).toList();
|
)).toList();
|
||||||
@ -204,7 +219,6 @@ class _FilesState extends State<Files> {
|
|||||||
leading: const Icon(Icons.upload_file),
|
leading: const Icon(Icons.upload_file),
|
||||||
title: const Text('Aus Dateien hochladen'),
|
title: const Text('Aus Dateien hochladen'),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.loaderOverlay.show();
|
|
||||||
MineTec marked this conversation as resolved
Outdated
MineTec
commented
gibts in irgendeiner form dann enien loadingindicator? die Galerie/ Dateiwahl kann bei einigen handys doch etwas länger dauern. gibts in irgendeiner form dann enien loadingindicator?
die Galerie/ Dateiwahl kann bei einigen handys doch etwas länger dauern.
Pupsi
commented
schwierig: wo soll der wieder versteckt werden? Irgendwie bekomme ich das nicht so gut hin. schwierig: wo soll der wieder versteckt werden? Irgendwie bekomme ich das nicht so gut hin.
|
|||||||
FilePick.documentPick().then(mediaUpload);
|
FilePick.documentPick().then(mediaUpload);
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
@ -215,9 +229,8 @@ class _FilesState extends State<Files> {
|
|||||||
leading: const Icon(Icons.add_a_photo_outlined),
|
leading: const Icon(Icons.add_a_photo_outlined),
|
||||||
title: const Text('Aus Gallerie hochladen'),
|
title: const Text('Aus Gallerie hochladen'),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.loaderOverlay.show();
|
|
||||||
MineTec marked this conversation as resolved
Outdated
MineTec
commented
hier ebenfalls hier ebenfalls
|
|||||||
FilePick.galleryPick().then((value) {
|
FilePick.galleryPick().then((value) {
|
||||||
mediaUpload(value?.path);
|
if(value != null) mediaUpload([value.path]);
|
||||||
});
|
});
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
@ -247,15 +260,4 @@ class _FilesState extends State<Files> {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
318
lib/view/pages/files/filesUploadDialog.dart
Normal file
@ -0,0 +1,318 @@
|
|||||||
|
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/focusBehaviour.dart';
|
||||||
|
|
||||||
|
class FilesUploadDialog extends StatefulWidget {
|
||||||
|
final List<String> filePaths;
|
||||||
|
final String remotePath;
|
||||||
|
final void Function(List<String> uploadedFilePaths) onUploadFinished;
|
||||||
Pupsi marked this conversation as resolved
MineTec
commented
wofür ist hier der Parameter wofür ist hier der Parameter `List<String> uploadedFilePaths`?
|
|||||||
|
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> {
|
||||||
|
final List<UploadableFile> _uploadableFiles = [];
|
||||||
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
|
|||||||
|
bool _isUploading = false;
|
||||||
|
double _progressValue = 0.0;
|
||||||
Pupsi marked this conversation as resolved
MineTec
commented
passenderer Variablenname passenderer Variablenname `_overallProgressValue`
damit klar ist das sie den Gesamtfortschritt wiederspiegelt
|
|||||||
|
String _infoText = '';
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
_uploadableFiles.addAll(widget.filePaths.map((filePath) {
|
||||||
|
String fileName = filePath.split(Platform.pathSeparator).last;
|
||||||
|
return UploadableFile(filePath, fileName);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
void showErrorMessage(int errorCode){
|
||||||
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
|
|||||||
|
showDialog(
|
||||||
Pupsi marked this conversation as resolved
MineTec
commented
ungenutzer code raus ungenutzer code raus
|
|||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: const Text('Ein Fehler ist aufgetreten'),
|
||||||
|
contentPadding: const EdgeInsets.all(10),
|
||||||
|
content: Text('Error code: $errorCode'),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
child: const Text('Schließen', textAlign: TextAlign.center),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void uploadSelectedFiles({bool override = false}) async {
|
||||||
Pupsi marked this conversation as resolved
MineTec
commented
dein ehemaliger name dein ehemaliger name `upload` ist in diesem kontext doch besser, vielleicht auch `uploadFiles`
|
|||||||
|
setState(() {
|
||||||
|
_isUploading = true;
|
||||||
|
_infoText = 'Vorbereiten';
|
||||||
|
});
|
||||||
|
|
||||||
|
for (var element in _uploadableFiles) {
|
||||||
|
setState(() {
|
||||||
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)`
|
|||||||
|
element.isConflicting = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
WebDavClient webdavClient = await WebdavApi.webdav;
|
||||||
|
|
||||||
|
if (!override) {
|
||||||
|
List<WebDavResponse> result = (await webdavClient.propfind(PathUri.parse(widget.remotePath))).responses;
|
||||||
|
List<UploadableFile> conflictingFiles = [];
|
||||||
|
|
||||||
|
for (var file in _uploadableFiles) {
|
||||||
Pupsi marked this conversation as resolved
MineTec
commented
umschreiben in fluente schreibweise
umschreiben in fluente schreibweise
```
conflictingFiles = _uploadableFiles.where(...)
```
|
|||||||
|
String fileName = file.fileName;
|
||||||
|
if (result.any((element) => Uri.decodeComponent(element.href!).endsWith('/$fileName'))) {
|
||||||
|
// konflikt
|
||||||
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
|
|||||||
|
conflictingFiles.add(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(conflictingFiles.isNotEmpty) {
|
||||||
|
bool replaceFiles = await showDialog(
|
||||||
|
context: context,
|
||||||
|
barrierDismissible: false,
|
||||||
|
builder: (context) {
|
||||||
|
return 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.\n'
|
||||||
|
'(Datei ${_uploadableFiles.indexOf(conflictingFiles.first)+1})',
|
||||||
|
textAlign: TextAlign.left,
|
||||||
|
) :
|
||||||
|
Text(
|
||||||
|
'${conflictingFiles.length} Dateien mit den Namen ${conflictingFiles.map((e) => e.fileName).toList()} existieren bereits.\n'
|
||||||
|
'(Dateien ${conflictingFiles.map((e) => _uploadableFiles.indexOf(e)+1).toList()})',
|
||||||
|
textAlign: TextAlign.left,
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(context, false);
|
||||||
|
},
|
||||||
|
child: const Text('Bearbeiten', textAlign: TextAlign.center),
|
||||||
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.
|
|||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(context, true);
|
||||||
|
},
|
||||||
|
child: const Text('Ersetzen', textAlign: TextAlign.center),
|
||||||
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
|
|||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if(!replaceFiles) {
|
||||||
|
for (var element in conflictingFiles) {
|
||||||
|
element.isConflicting = true;
|
||||||
|
}
|
||||||
|
setState(() {
|
||||||
|
_isUploading = false;
|
||||||
|
_progressValue = 0.0;
|
||||||
|
_infoText = '';
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> uploadetFilePaths = [];
|
||||||
|
for (var file in _uploadableFiles) {
|
||||||
|
String fileName = file.fileName;
|
||||||
|
String 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}';
|
||||||
|
|
||||||
|
String fullRemotePath = '${widget.remotePath}/$fileName';
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
_infoText = '${_uploadableFiles.indexOf(file) + 1}/${_uploadableFiles.length}';
|
||||||
|
});
|
||||||
|
|
||||||
|
HttpClientResponse uploadTask = await webdavClient.putFile(
|
||||||
|
File(filePath),
|
||||||
|
FileStat.statSync(filePath),
|
||||||
|
PathUri.parse(fullRemotePath),
|
||||||
|
onProgress: (progress) {
|
||||||
|
setState(() {
|
||||||
|
file._uploadProgress = progress;
|
||||||
|
_progressValue = ((progress + _uploadableFiles.indexOf(file)) / _uploadableFiles.length).toDouble();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if(uploadTask.statusCode < 200 || uploadTask.statusCode > 299) {
|
||||||
|
// error code
|
||||||
|
setState(() {
|
||||||
Pupsi marked this conversation as resolved
MineTec
commented
kommentar raus kommentar raus
|
|||||||
|
_isUploading = false;
|
||||||
|
_progressValue = 0.0;
|
||||||
|
_infoText = '';
|
||||||
|
});
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
showErrorMessage(uploadTask.statusCode);
|
||||||
|
} else {
|
||||||
|
uploadetFilePaths.add(fullRemotePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
_isUploading = false;
|
||||||
|
_progressValue = 0.0;
|
||||||
|
_infoText = '';
|
||||||
|
});
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
widget.onUploadFinished(uploadetFilePaths);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return 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,
|
||||||
|
leading: 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.close_outlined),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
trailing: Container(
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
child: IconButton(
|
||||||
|
tooltip: 'Namen löschen',
|
||||||
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.
|
|||||||
|
padding: EdgeInsets.zero,
|
||||||
|
onPressed: () {
|
||||||
|
if(!_isUploading) {
|
||||||
|
setState(() {
|
||||||
|
currentFile.fileName = '';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
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'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Expanded(child: SizedBox.shrink()),
|
||||||
|
Visibility(
|
||||||
|
visible: _isUploading,
|
||||||
|
replacement: TextButton(
|
||||||
|
onPressed: () => uploadSelectedFiles(override: widget.uniqueNames),
|
||||||
|
child: const Text('Hochladen'),
|
||||||
|
),
|
||||||
|
child: Stack(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
children: [
|
||||||
|
CircularProgressIndicator(value: _progressValue),
|
||||||
|
Center(child: Text(_infoText)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,9 @@
|
|||||||
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:loader_overlay/loader_overlay.dart';
|
|
||||||
import 'package:nextcloud/nextcloud.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:provider/provider.dart';
|
||||||
import 'package:uuid/uuid.dart';
|
|
||||||
|
|
||||||
import '../../../../api/marianumcloud/files-sharing/fileSharingApi.dart';
|
import '../../../../api/marianumcloud/files-sharing/fileSharingApi.dart';
|
||||||
import '../../../../api/marianumcloud/files-sharing/fileSharingApiParams.dart';
|
import '../../../../api/marianumcloud/files-sharing/fileSharingApiParams.dart';
|
||||||
@ -15,7 +14,7 @@ import '../../../../model/chatList/chatProps.dart';
|
|||||||
import '../../../../storage/base/settingsProvider.dart';
|
import '../../../../storage/base/settingsProvider.dart';
|
||||||
import '../../../../widget/filePick.dart';
|
import '../../../../widget/filePick.dart';
|
||||||
import '../../../../widget/focusBehaviour.dart';
|
import '../../../../widget/focusBehaviour.dart';
|
||||||
import '../../files/fileUploadDialog.dart';
|
import '../../files/filesUploadDialog.dart';
|
||||||
|
|
||||||
class ChatTextfield extends StatefulWidget {
|
class ChatTextfield extends StatefulWidget {
|
||||||
final String sendToToken;
|
final String sendToToken;
|
||||||
@ -34,34 +33,40 @@ class _ChatTextfieldState extends State<ChatTextfield> {
|
|||||||
Provider.of<ChatProps>(context, listen: false).run();
|
Provider.of<ChatProps>(context, listen: false).run();
|
||||||
}
|
}
|
||||||
|
|
||||||
void mediaUpload(String? path) async {
|
void share(String shareFolder, List<String> filePaths) {
|
||||||
context.loaderOverlay.hide();
|
for (var element in filePaths) {
|
||||||
|
String fileName = element.split(Platform.pathSeparator).last;
|
||||||
if(path == null) {
|
FileSharingApi().share(FileSharingApiParams(
|
||||||
return;
|
shareType: 10,
|
||||||
|
shareWith: widget.sendToToken,
|
||||||
|
path: '$shareFolder/$fileName',
|
||||||
|
)).then((value) => _query());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String filename = "${path.split("/").last.split(".").first}-${const Uuid().v4()}.${path.split(".").last}";
|
void mediaUpload(List<String>? paths) async {
|
||||||
|
if (paths == null) return;
|
||||||
|
|
||||||
String shareFolder = 'MarianumMobile';
|
String shareFolder = 'MarianumMobile';
|
||||||
WebdavApi.webdav.then((webdav) {
|
WebdavApi.webdav.then((webdav) {
|
||||||
webdav.mkcol(PathUri.parse('/$shareFolder'));
|
webdav.mkcol(PathUri.parse('/$shareFolder'));
|
||||||
});
|
});
|
||||||
|
|
||||||
showDialog(context: context, builder: (context) => FileUploadDialog(
|
pushScreen(
|
||||||
doShowFinish: false,
|
context,
|
||||||
fileName: filename,
|
withNavBar: false,
|
||||||
localPath: path,
|
screen: FilesUploadDialog(
|
||||||
remotePath: [shareFolder],
|
filePaths: paths,
|
||||||
onUploadFinished: () {
|
remotePath: shareFolder,
|
||||||
FileSharingApi().share(FileSharingApiParams(
|
onUploadFinished: (uploadedFilePaths) {
|
||||||
shareType: 10,
|
share(shareFolder, uploadedFilePaths);
|
||||||
shareWith: widget.sendToToken,
|
|
||||||
path: '$shareFolder/$filename',
|
|
||||||
)).then((value) => _query());
|
|
||||||
},
|
},
|
||||||
), barrierDismissible: false);
|
uniqueNames: true,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void setDraft(String text) {
|
void setDraft(String text) {
|
||||||
if(text.isNotEmpty) {
|
if(text.isNotEmpty) {
|
||||||
settings.val(write: true).talkSettings.drafts[widget.sendToToken] = text;
|
settings.val(write: true).talkSettings.drafts[widget.sendToToken] = text;
|
||||||
@ -98,7 +103,6 @@ class _ChatTextfieldState extends State<ChatTextfield> {
|
|||||||
leading: const Icon(Icons.file_open),
|
leading: const Icon(Icons.file_open),
|
||||||
title: const Text('Aus Dateien auswählen'),
|
title: const Text('Aus Dateien auswählen'),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.loaderOverlay.show();
|
|
||||||
FilePick.documentPick().then(mediaUpload);
|
FilePick.documentPick().then(mediaUpload);
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
@ -109,9 +113,8 @@ class _ChatTextfieldState extends State<ChatTextfield> {
|
|||||||
leading: const Icon(Icons.image),
|
leading: const Icon(Icons.image),
|
||||||
title: const Text('Aus Gallerie auswählen'),
|
title: const Text('Aus Gallerie auswählen'),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.loaderOverlay.show();
|
|
||||||
FilePick.galleryPick().then((value) {
|
FilePick.galleryPick().then((value) {
|
||||||
mediaUpload(value?.path);
|
if(value != null) mediaUpload([value.path]);
|
||||||
});
|
});
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
|
@ -13,8 +13,9 @@ class FilePick {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<String?> documentPick() async {
|
static Future<List<String>?> documentPick() async {
|
||||||
FilePickerResult? result = await FilePicker.platform.pickFiles();
|
FilePickerResult? result = await FilePicker.platform.pickFiles(allowMultiple: true);
|
||||||
return result?.files.single.path;
|
List<String?>? paths = result?.files.nonNulls.map((e) => e.path).toList();
|
||||||
|
return paths?.nonNulls.toList();
|
||||||
}
|
}
|
||||||
}
|
}
|
raus