From a512a3ef88db876e02c5f7093f68bd0b7a818b5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tizian=20=E3=80=8C=E3=83=86=E3=82=A3=E3=83=84=E3=82=A3?= =?UTF-8?q?=E3=82=A2=E3=83=BC=E3=83=B3=E3=80=8D?= Date: Wed, 30 Sep 2026 08:24:31 +0200 Subject: [PATCH] Move to file_picker v13 and device_info_plus v13 (win32 v6) Both depend on win32 v6, like current Serverpod, so an app that also uses Serverpod can resolve the editor in one pub workspace; file_picker v10 and device_info_plus v12 need win32 v5. file_picker v12 made its API static, returns the picked files directly and reads their content on demand (PlatformFile.readAsBytes()); v13 removed the parameters v12 had deprecated. The FilePickerService implementation keeps its interface: withData, withReadStream and lockParentWindow are ignored, allowMultiple chooses between pickFiles() and pickFile(), and saveFile() saves an empty file, since file_picker now writes the file itself and needs its content up front. The image upload reads the picked file's bytes on the web; the example app saves and opens files with the new API. Raises the minimum to Dart v3.11 and Flutter v3.41.6 (device_info_plus v13). Co-Authored-By: Claude Opus 5.5 --- example/lib/home_page.dart | 58 +++++++++---------- example/pubspec.yaml | 2 +- .../image_upload_widget.dart | 12 ++-- .../util/file_picker/file_picker_impl.dart | 45 ++++++++++---- pubspec.yaml | 8 +-- 5 files changed, 74 insertions(+), 51 deletions(-) diff --git a/example/lib/home_page.dart b/example/lib/home_page.dart index 678b5143..f02f4615 100644 --- a/example/lib/home_page.dart +++ b/example/lib/home_page.dart @@ -435,53 +435,51 @@ class _HomePageState extends State { ); } } else { - // for desktop - final path = await FilePicker.platform.saveFile( + // for desktop: file_picker v13 writes the file itself. + final Uint8List bytes; + if (fileType == ExportFileType.pdf) { + final pdf = await PdfHTMLEncoder( + fontFallback: [ + await PdfGoogleFonts.notoColorEmoji(), + await PdfGoogleFonts.notoColorEmojiRegular(), + ], + ).convert(result); + bytes = await pdf.save(); + } else { + bytes = utf8.encode(result); + } + final uri = await FilePicker.saveFile( fileName: 'document.${fileType.extension}', + bytes: bytes, ); - if (path != null) { - await File(path).writeAsString(result); - if (fileType == ExportFileType.pdf) { - final pdf = await PdfHTMLEncoder( - fontFallback: [ - await PdfGoogleFonts.notoColorEmoji(), - await PdfGoogleFonts.notoColorEmojiRegular(), - ], - ).convert(result); - - await File(path).writeAsBytes(await pdf.save()); - } - - if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('This document is saved to the $path'), - ), - ); - } + if (uri != null && mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('This document is saved to the ${uri.toFilePath()}'), + ), + ); } } } void _importFile(ExportFileType fileType) async { - final result = await FilePicker.platform.pickFiles( - allowMultiple: false, + final file = await FilePicker.pickFile( allowedExtensions: [fileType.extension], type: FileType.custom, ); + if (file == null) { + return; + } var plainText = ''; if (!kIsWeb) { - final path = result?.files.single.path; + final path = file.path; if (path == null) { return; } plainText = await File(path).readAsString(); } else { - final bytes = result?.files.first.bytes; - if (bytes == null) { - return; - } - plainText = const Utf8Decoder().convert(bytes); + // file_picker v13 reads a file's content on demand. + plainText = const Utf8Decoder().convert(await file.readAsBytes()); } var jsonString = ''; diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 92da2365..bf3021ed 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -42,7 +42,7 @@ dependencies: google_fonts: ^6.1.0 flutter_localizations: sdk: flutter - file_picker: ^10.3.10 + file_picker: ^13.1.0 universal_html: ^2.0.8 highlight: ^0.7.0 http: ^1.1.0 diff --git a/lib/src/editor/block_component/image_block_component/image_upload_widget.dart b/lib/src/editor/block_component/image_block_component/image_upload_widget.dart index 7c5a592a..881e332d 100644 --- a/lib/src/editor/block_component/image_block_component/image_upload_widget.dart +++ b/lib/src/editor/block_component/image_block_component/image_upload_widget.dart @@ -335,15 +335,19 @@ class _UploadImageMenuState extends State { dialogTitle: '', type: kIsWeb ? fp.FileType.custom : fp.FileType.image, allowedExtensions: kIsWeb ? allowedExtensions : null, - withData: kIsWeb, ); if (result != null && result.files.isNotEmpty) { + final file = result.files.first; + // file_picker v13 reads a file's content on demand. + final bytes = kIsWeb ? await file.readAsBytes() : null; + if (!mounted) { + return; + } setState(() { - final bytes = result.files.first.bytes; - if (kIsWeb && bytes != null) { + if (bytes != null) { _imagePathOrContent = base64String(bytes); } else { - _imagePathOrContent = result.files.first.path; + _imagePathOrContent = file.path; } }); } diff --git a/lib/src/editor/util/file_picker/file_picker_impl.dart b/lib/src/editor/util/file_picker/file_picker_impl.dart index e36d3020..cb55f42d 100644 --- a/lib/src/editor/util/file_picker/file_picker_impl.dart +++ b/lib/src/editor/util/file_picker/file_picker_impl.dart @@ -1,12 +1,19 @@ +import 'dart:typed_data'; + import 'package:appflowy_editor/src/editor/util/file_picker/file_picker_service.dart'; import 'package:file_picker/file_picker.dart' as fp; +/// [FilePickerService] on top of file_picker v13, whose API is static and +/// returns the picked files directly. class FilePicker implements FilePickerService { @override Future getDirectoryPath({String? title}) { - return fp.FilePicker.platform.getDirectoryPath(); + return fp.FilePicker.getDirectoryPath(dialogTitle: title); } + /// [withData], [withReadStream] and [lockParentWindow] no longer exist in + /// file_picker v13 and are ignored: read a picked file's content with + /// `PlatformFile.readAsBytes()` or `PlatformFile.readAsByteStream()`. @override Future pickFiles({ String? dialogTitle, @@ -19,21 +26,31 @@ class FilePicker implements FilePickerService { bool withReadStream = false, bool lockParentWindow = false, }) async { - final result = await fp.FilePicker.platform.pickFiles( + if (allowMultiple) { + final files = await fp.FilePicker.pickFiles( + dialogTitle: dialogTitle, + initialDirectory: initialDirectory, + type: type, + allowedExtensions: allowedExtensions, + onFileLoading: onFileLoading, + ); + return FilePickerResult(files); + } + + final file = await fp.FilePicker.pickFile( dialogTitle: dialogTitle, initialDirectory: initialDirectory, type: type, allowedExtensions: allowedExtensions, onFileLoading: onFileLoading, - allowMultiple: allowMultiple, - withData: withData, - withReadStream: withReadStream, - lockParentWindow: lockParentWindow, ); - - return FilePickerResult(result?.files ?? []); + return FilePickerResult([?file]); } + /// file_picker v13 writes the file itself and needs its content up front, + /// while this service only asks where to save. So it saves an empty file + /// there and returns its path (or its URI where there is no local path) for + /// the caller to write to. [lockParentWindow] is ignored. @override Future saveFile({ String? dialogTitle, @@ -42,14 +59,18 @@ class FilePicker implements FilePickerService { fp.FileType type = fp.FileType.any, List? allowedExtensions, bool lockParentWindow = false, - }) { - return fp.FilePicker.platform.saveFile( + }) async { + final uri = await fp.FilePicker.saveFile( + fileName: fileName ?? 'untitled', + bytes: Uint8List(0), dialogTitle: dialogTitle, - fileName: fileName, initialDirectory: initialDirectory, type: type, allowedExtensions: allowedExtensions, - lockParentWindow: lockParentWindow, ); + if (uri == null) { + return null; + } + return uri.scheme == 'file' ? uri.toFilePath() : uri.toString(); } } diff --git a/pubspec.yaml b/pubspec.yaml index fddb79ca..931109e4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -5,17 +5,17 @@ version: 6.1.0 homepage: "https://github.com/AppFlowy-IO/appflowy-editor" environment: - sdk: ">=3.6.0 <4.0.0" - flutter: ">=3.32.0" + sdk: ">=3.11.0 <4.0.0" + flutter: ">=3.41.6" description: A highly customizable rich-text editor for Flutter. The AppFlowy Editor project for AppFlowy and beyond. dependencies: collection: "^1.18.0" - device_info_plus: ^12.3.0 + device_info_plus: ^13.2.0 diff_match_patch: "^0.4.1" - file_picker: ^10.3.10 + file_picker: ^13.1.0 flutter: sdk: flutter flutter_svg: "^2.0.6"