import 'dart:async'; import 'dart:html'; import 'dart:typed_data'; import 'package:file_picker/file_picker.dart'; import 'package:flutter_web_plugins/flutter_web_plugins.dart'; class FilePickerWeb extends FilePicker { late Element _target; final String _kFilePickerInputsDomId = '__file_picker_web-file-input'; final int _readStreamChunkSize = 1000 * 1000; // 1 MB static final FilePickerWeb platform = FilePickerWeb._(); FilePickerWeb._() { _target = _ensureInitialized(_kFilePickerInputsDomId); } static void registerWith(Registrar registrar) { FilePicker.platform = platform; } /// Initializes a DOM container where we can host input elements. Element _ensureInitialized(String id) { Element? target = querySelector('#$id'); if (target == null) { final Element targetElement = Element.tag('flt-file-picker-inputs') ..id = id; querySelector('body')!.children.add(targetElement); target = targetElement; } return target; } @override Future pickFiles({ String? dialogTitle, FileType type = FileType.any, List? allowedExtensions, bool allowMultiple = false, Function(FilePickerStatus)? onFileLoading, bool? allowCompression, bool? withData = true, bool? withReadStream = false, }) async { if (type != FileType.custom && (allowedExtensions?.isNotEmpty ?? false)) { throw Exception( 'You are setting a type [$type]. Custom extension filters are only allowed with FileType.custom, please change it or remove filters.'); } final Completer?> filesCompleter = Completer?>(); String accept = _fileType(type, allowedExtensions); InputElement uploadInput = FileUploadInputElement() as InputElement; uploadInput.draggable = true; uploadInput.multiple = allowMultiple; uploadInput.accept = accept; bool changeEventTriggered = false; if (onFileLoading != null) { onFileLoading(FilePickerStatus.picking); } void changeEventListener(e) { if (changeEventTriggered) { return; } changeEventTriggered = true; final List files = uploadInput.files!; final List pickedFiles = []; void addPickedFile( File file, Uint8List? bytes, String? path, Stream>? readStream, ) { pickedFiles.add(PlatformFile( name: file.name, path: path, size: bytes != null ? bytes.length : file.size, bytes: bytes, readStream: readStream, )); if (pickedFiles.length >= files.length) { if (onFileLoading != null) { onFileLoading(FilePickerStatus.done); } filesCompleter.complete(pickedFiles); } } files.forEach((File file) { if (withReadStream!) { addPickedFile(file, null, null, _openFileReadStream(file)); return; } if (!withData!) { final FileReader reader = FileReader(); reader.onLoadEnd.listen((e) { addPickedFile(file, null, reader.result as String?, null); }); reader.readAsDataUrl(file); return; } final FileReader reader = FileReader(); reader.onLoadEnd.listen((e) { addPickedFile(file, reader.result as Uint8List?, null, null); }); reader.readAsArrayBuffer(file); }); } void cancelledEventListener(_) { window.removeEventListener('focus', cancelledEventListener); // This listener is called before the input changed event, // and the `uploadInput.files` value is still null // Wait for results from js to dart Future.delayed(Duration(milliseconds: 500)).then((value) { if (!changeEventTriggered) { changeEventTriggered = true; filesCompleter.complete(null); } }); } uploadInput.onChange.listen(changeEventListener); uploadInput.addEventListener('change', changeEventListener); // Listen focus event for cancelled window.addEventListener('focus', cancelledEventListener); //Add input element to the page body _target.children.clear(); _target.children.add(uploadInput); uploadInput.click(); final List? files = await filesCompleter.future; return files == null ? null : FilePickerResult(files); } static String _fileType(FileType type, List? allowedExtensions) { switch (type) { case FileType.any: return ''; case FileType.audio: return 'audio/*'; case FileType.image: return 'image/*'; case FileType.video: return 'video/*'; case FileType.media: return 'video/*|image/*'; case FileType.custom: return allowedExtensions! .fold('', (prev, next) => '${prev.isEmpty ? '' : '$prev,'} .$next'); } } Stream> _openFileReadStream(File file) async* { final reader = FileReader(); int start = 0; while (start < file.size) { final end = start + _readStreamChunkSize > file.size ? file.size : start + _readStreamChunkSize; final blob = file.slice(start, end); reader.readAsArrayBuffer(blob); await reader.onLoad.first; yield reader.result as List; start += _readStreamChunkSize; } } }