diff --git a/packages/flet/lib/src/controls/file_picker.dart b/packages/flet/lib/src/controls/file_picker.dart index 78b251c57..08b64e98a 100644 --- a/packages/flet/lib/src/controls/file_picker.dart +++ b/packages/flet/lib/src/controls/file_picker.dart @@ -6,9 +6,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; -import '../flet_app_services.dart'; import '../flet_control_backend.dart'; -import '../flet_server.dart'; import '../models/control.dart'; import '../utils/desktop.dart'; import '../utils/strings.dart'; @@ -78,12 +76,12 @@ class FilePickerControl extends StatefulWidget { State createState() => _FilePickerControlState(); } +List files = []; + class _FilePickerControlState extends State with FletStoreMixin { String? _state; - String? _upload; String? _path; - List? _files; @override Widget build(BuildContext context) { @@ -108,11 +106,8 @@ class _FilePickerControlState extends State } debugPrint("FilePicker _state: $_state, state: $state"); - - resetDialogState() { - _state = null; - widget.backend.updateControlState(widget.control.id, {"state": ""}); - } + debugPrint("FilePicker _path: $_path"); + debugPrint("FilePicker _files: $files"); sendEvent() { if (defaultTargetPlatform != TargetPlatform.windows || !isDesktop()) { @@ -123,8 +118,8 @@ class _FilePickerControlState extends State "result", json.encode(FilePickerResultEvent( path: _path, - files: _files - ?.map((f) => FilePickerFile( + files: files + .map((f) => FilePickerFile( name: f.name, path: kIsWeb ? null : f.path, size: f.size)) @@ -133,9 +128,8 @@ class _FilePickerControlState extends State if (_state != state) { _path = null; - _files = null; + // _files = null; _state = state; - if (isDesktop() && defaultTargetPlatform == TargetPlatform.windows) { resetDialogState(); } @@ -154,7 +148,10 @@ class _FilePickerControlState extends State withReadStream: true) .then((result) { debugPrint("pickFiles() completed"); - _files = result?.files; + + if (result != null) { + files = result.files; + } sendEvent(); }); } @@ -188,40 +185,39 @@ class _FilePickerControlState extends State _path = result; sendEvent(); }); + } else if (state?.toLowerCase() == "uploadfiles" && + upload != null && + files.isNotEmpty) { + uploadFiles(upload, files, pageArgs.pageUri!); } } - - // upload files - if (_upload != upload && upload != null && _files != null) { - _upload = upload; - uploadFiles( - upload, FletAppServices.of(context).server, pageArgs.pageUri!); - } - return widget.nextChild ?? const SizedBox.shrink(); }); } - Future uploadFiles(String filesJson, FletServer server, Uri pageUri) async { + Future uploadFiles( + String filesJson, List files, Uri pageUri) async { var uj = json.decode(filesJson); var uploadFiles = (uj as List).map((u) => FilePickerUploadFile( name: u["name"], uploadUrl: u["upload_url"], method: u["method"])); + List uploadedFiles = []; for (var uf in uploadFiles) { - var file = _files!.firstWhereOrNull((f) => f.name == uf.name); + var file = files.firstWhereOrNull((f) => f.name == uf.name); if (file != null) { try { await uploadFile( - file, server, getFullUploadUrl(pageUri, uf.uploadUrl), uf.method); - _files!.remove(file); + file, getFullUploadUrl(pageUri, uf.uploadUrl), uf.method); + uploadedFiles.add(file.name); } catch (e) { - sendProgress(server, file.name, null, e.toString()); + sendProgress(file.name, null, e.toString()); } } } + sendUploadFinishEvent(uploadedFiles); + resetDialogState(); } - Future uploadFile(PlatformFile file, FletServer server, String uploadUrl, - String method) async { + Future uploadFile(PlatformFile file, String uploadUrl, String method) async { final fileReadStream = file.readStream; if (fileReadStream == null) { throw Exception('Cannot read file from null stream'); @@ -234,7 +230,7 @@ class _FilePickerControlState extends State streamedRequest.contentLength = file.size; // send 0% - sendProgress(server, file.name, 0, null); + sendProgress(file.name, 0, null); double lastSent = 0; // send every 10% double progress = 0; @@ -247,7 +243,7 @@ class _FilePickerControlState extends State if (progress >= lastSent) { lastSent += 0.1; if (progress != 1.0) { - sendProgress(server, file.name, progress, null); + sendProgress(file.name, progress, null); } } }, onDone: () { @@ -257,16 +253,20 @@ class _FilePickerControlState extends State var streamedResponse = await streamedRequest.send(); var response = await http.Response.fromStream(streamedResponse); if (response.statusCode < 200 || response.statusCode > 204) { - sendProgress(server, file.name, null, + sendProgress(file.name, null, "Upload endpoint returned code ${response.statusCode}: ${response.body}"); } else { // send 100% - sendProgress(server, file.name, progress, null); + sendProgress(file.name, progress, null); } } - void sendProgress( - FletServer server, String name, double? progress, String? error) { + resetDialogState() { + _state = null; + widget.backend.updateControlState(widget.control.id, {"state": ""}); + } + + void sendProgress(String name, double? progress, String? error) { widget.backend.triggerControlEvent( widget.control.id, "upload", @@ -274,6 +274,11 @@ class _FilePickerControlState extends State name: name, progress: progress, error: error))); } + void sendUploadFinishEvent(List files) { + widget.backend.triggerControlEvent( + widget.control.id, "upload_finished", json.encode(files)); + } + String getFullUploadUrl(Uri pageUri, String uploadUrl) { Uri uploadUri = Uri.parse(uploadUrl); if (!uploadUri.hasAuthority) { diff --git a/sdk/python/packages/flet-core/src/flet_core/file_picker.py b/sdk/python/packages/flet-core/src/flet_core/file_picker.py index beaa2963c..164630e0d 100644 --- a/sdk/python/packages/flet-core/src/flet_core/file_picker.py +++ b/sdk/python/packages/flet-core/src/flet_core/file_picker.py @@ -20,6 +20,7 @@ class FilePickerState(Enum): PICK_FILES = "pickFiles" SAVE_FILE = "saveFile" GET_DIRECTORY_PATH = "getDirectoryPath" + UPLOADING_FILES = "uploadfiles" class FilePickerFileType(Enum): @@ -114,6 +115,7 @@ def __init__( self, on_result: Optional[Callable[[FilePickerResultEvent], None]] = None, on_upload: Optional[Callable[[FilePickerUploadEvent], None]] = None, + on_upload_finished: Optional[Callable[[ControlEvent], None]] = None, # # Control # @@ -138,6 +140,11 @@ def convert_result_event_data(e): self.__on_upload = EventHandler(lambda e: FilePickerUploadEvent(e)) self._add_event_handler("upload", self.__on_upload.get_handler()) + self.__on_upload_finished = EventHandler(lambda e: e) + self._add_event_handler( + "upload_finished", self.__on_upload_finished.get_handler() + ) + self.__result: Optional[FilePickerResultEvent] = None self.__upload: List[FilePickerUploadFile] = [] self.__allowed_extensions: Optional[List[str]] = None @@ -145,6 +152,7 @@ def convert_result_event_data(e): self.__file_type = None self.on_result = on_result self.on_upload = on_upload + self.on_upload_finished = on_upload_finished def _get_control_name(self): return "filepicker" @@ -248,6 +256,7 @@ async def get_directory_path_async( def upload(self, files: List[FilePickerUploadFile]): self.__upload = files + self.state = FilePickerState.UPLOADING_FILES self.update() @deprecated( @@ -345,3 +354,12 @@ def on_upload(self): @on_upload.setter def on_upload(self, handler: OptionalEventCallable[FilePickerUploadEvent]): self.__on_upload.handler = handler + + # on_upload_finished + @property + def on_upload_finished(self): + return self.__on_upload_finished.handler + + @on_upload_finished.setter + def on_upload_finished(self, handler: OptionalEventCallable[ControlEvent]): + self.__on_upload_finished.handler = handler