diff --git a/packages/neon/neon_files/lib/blocs/files.dart b/packages/neon/neon_files/lib/blocs/files.dart index 2711c40b..9e9488c6 100644 --- a/packages/neon/neon_files/lib/blocs/files.dart +++ b/packages/neon/neon_files/lib/blocs/files.dart @@ -3,6 +3,8 @@ part of '../neon_files.dart'; abstract interface class FilesBlocEvents { void uploadFile(final List path, final String localPath); + void uploadBytes(final List path, final Uint8List bytes); + void syncFile(final List path); void openFile(final List path, final String etag, final String? mimeType); @@ -159,7 +161,7 @@ class FilesBloc extends InteractiveBloc implements FilesBlocEvents, FilesBlocSta void uploadFile(final List path, final String localPath) { wrapAction( () async { - final task = FilesUploadTask( + final task = FilesUploadFileTask( path: path, file: File(localPath), ); @@ -171,6 +173,22 @@ class FilesBloc extends InteractiveBloc implements FilesBlocEvents, FilesBlocSta ); } + @override + void uploadBytes(final List path, final Uint8List bytes) { + wrapAction( + () async { + final task = FilesUploadBytesTask( + path: path, + bytes: bytes, + ); + tasks.add(tasks.value..add(task)); + await _uploadQueue.add(() => task.execute(account.client)); + tasks.add(tasks.value..removeWhere((final t) => t == task)); + }, + disableTimeout: true, + ); + } + Future _cacheFile(final List path, final String etag) async { final cacheDir = await getApplicationCacheDirectory(); final file = File(p.join(cacheDir.path, 'files', etag.replaceAll('"', ''), path.last)); @@ -190,7 +208,7 @@ class FilesBloc extends InteractiveBloc implements FilesBlocEvents, FilesBlocSta final List path, final File file, ) async { - final task = FilesDownloadTask( + final task = FilesDownloadFileTask( path: path, file: file, ); diff --git a/packages/neon/neon_files/lib/dialogs/choose_create.dart b/packages/neon/neon_files/lib/dialogs/choose_create.dart index 38ad27d5..e3d8e520 100644 --- a/packages/neon/neon_files/lib/dialogs/choose_create.dart +++ b/packages/neon/neon_files/lib/dialogs/choose_create.dart @@ -22,14 +22,24 @@ class _FilesChooseCreateDialogState extends State { ); if (result != null) { for (final file in result.files) { - await upload(File(file.path!)); + await upload( + file.name, + path: kIsWeb ? null : file.path, + bytes: file.bytes, + ); } } } - Future upload(final File file) async { + Future upload( + final String name, { + final String? path, + final Uint8List? bytes, + }) async { + assert((path == null) != (bytes == null), 'Provide either path or bytes'); final sizeWarning = widget.bloc.options.uploadSizeWarning.value; - if (sizeWarning != null) { + if (path != null && sizeWarning != null) { + final file = File(path); final stat = file.statSync(); if (stat.size > sizeWarning) { if (!(await showConfirmationDialog( @@ -43,7 +53,12 @@ class _FilesChooseCreateDialogState extends State { } } } - widget.bloc.uploadFile([...widget.basePath, p.basename(file.path)], file.path); + + if (path != null) { + widget.bloc.uploadFile([...widget.basePath, name], path); + } else { + widget.bloc.uploadBytes([...widget.basePath, name], bytes!); + } } @override @@ -90,7 +105,7 @@ class _FilesChooseCreateDialogState extends State { final picker = ImagePicker(); final result = await picker.pickImage(source: ImageSource.camera); if (result != null) { - await upload(File(result.path)); + await upload(result.name, path: result.path); } }, ), diff --git a/packages/neon/neon_files/lib/models/file_details.dart b/packages/neon/neon_files/lib/models/file_details.dart index c0d61b28..8f21e91a 100644 --- a/packages/neon/neon_files/lib/models/file_details.dart +++ b/packages/neon/neon_files/lib/models/file_details.dart @@ -29,8 +29,8 @@ class FileDetails { FileDetails.fromUploadTask({ required FilesUploadTask this.task, }) : path = task.path, - size = task.stat.size, - lastModified = task.stat.modified, + size = task.size, + lastModified = task.modified, isDirectory = false, etag = null, mimeType = null, diff --git a/packages/neon/neon_files/lib/neon_files.dart b/packages/neon/neon_files/lib/neon_files.dart index d6df44da..646716e9 100644 --- a/packages/neon/neon_files/lib/neon_files.dart +++ b/packages/neon/neon_files/lib/neon_files.dart @@ -6,6 +6,7 @@ import 'package:collection/collection.dart'; import 'package:file_icons/file_icons.dart'; import 'package:file_picker/file_picker.dart'; import 'package:filesize/filesize.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_material_design_icons/flutter_material_design_icons.dart'; import 'package:go_router/go_router.dart'; diff --git a/packages/neon/neon_files/lib/utils/task.dart b/packages/neon/neon_files/lib/utils/task.dart index 4d3c9fcc..0f1f4aad 100644 --- a/packages/neon/neon_files/lib/utils/task.dart +++ b/packages/neon/neon_files/lib/utils/task.dart @@ -3,13 +3,10 @@ part of '../neon_files.dart'; sealed class FilesTask { FilesTask({ required this.path, - required this.file, }); final List path; - final File file; - @protected final streamController = StreamController(); @@ -17,12 +14,18 @@ sealed class FilesTask { late final progress = streamController.stream.asBroadcastStream(); } -class FilesDownloadTask extends FilesTask { - FilesDownloadTask({ +abstract class FilesDownloadTask extends FilesTask { + FilesDownloadTask({required super.path}); +} + +class FilesDownloadFileTask extends FilesDownloadTask { + FilesDownloadFileTask({ required super.path, - required super.file, + required this.file, }); + final File file; + Future execute(final NextcloudClient client) async { await client.webdav.getFile( Uri(pathSegments: path), @@ -33,15 +36,31 @@ class FilesDownloadTask extends FilesTask { } } -class FilesUploadTask extends FilesTask { - FilesUploadTask({ +abstract class FilesUploadTask extends FilesTask { + FilesUploadTask({required super.path}); + + int get size; + + DateTime? get modified; +} + +class FilesUploadFileTask extends FilesUploadTask { + FilesUploadFileTask({ required super.path, - required super.file, + required this.file, }); + final File file; + FileStat? _stat; FileStat get stat => _stat ??= file.statSync(); + @override + int get size => stat.size; + + @override + DateTime? get modified => stat.modified; + Future execute(final NextcloudClient client) async { await client.webdav.putFile( file, @@ -53,3 +72,31 @@ class FilesUploadTask extends FilesTask { await streamController.close(); } } + +class FilesUploadBytesTask extends FilesUploadTask { + FilesUploadBytesTask({ + required super.path, + required this.bytes, + this.modified, + }); + + final Uint8List bytes; + + @override + int get size => bytes.lengthInBytes; + + @override + DateTime? modified; + + Future execute(final NextcloudClient client) async { + await client.webdav.putStream( + Stream.value(bytes), + Uri(pathSegments: path), + lastModified: modified, + contentLength: bytes.lengthInBytes, + onProgress: (final progress) { + streamController.add(progress); + }, + ); + } +}