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 <noreply@anthropic.com>
This commit is contained in:
Tizian 「ティツィアーン」 2026-09-30 08:24:31 +02:00
commit a512a3ef88
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64
5 changed files with 74 additions and 51 deletions

View file

@ -435,12 +435,8 @@ class _HomePageState extends State<HomePage> {
); );
} }
} else { } else {
// for desktop // for desktop: file_picker v13 writes the file itself.
final path = await FilePicker.platform.saveFile( final Uint8List bytes;
fileName: 'document.${fileType.extension}',
);
if (path != null) {
await File(path).writeAsString(result);
if (fileType == ExportFileType.pdf) { if (fileType == ExportFileType.pdf) {
final pdf = await PdfHTMLEncoder( final pdf = await PdfHTMLEncoder(
fontFallback: [ fontFallback: [
@ -448,40 +444,42 @@ class _HomePageState extends State<HomePage> {
await PdfGoogleFonts.notoColorEmojiRegular(), await PdfGoogleFonts.notoColorEmojiRegular(),
], ],
).convert(result); ).convert(result);
bytes = await pdf.save();
await File(path).writeAsBytes(await pdf.save()); } else {
bytes = utf8.encode(result);
} }
final uri = await FilePicker.saveFile(
if (mounted) { fileName: 'document.${fileType.extension}',
bytes: bytes,
);
if (uri != null && mounted) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: Text('This document is saved to the $path'), content: Text('This document is saved to the ${uri.toFilePath()}'),
), ),
); );
} }
} }
} }
}
void _importFile(ExportFileType fileType) async { void _importFile(ExportFileType fileType) async {
final result = await FilePicker.platform.pickFiles( final file = await FilePicker.pickFile(
allowMultiple: false,
allowedExtensions: [fileType.extension], allowedExtensions: [fileType.extension],
type: FileType.custom, type: FileType.custom,
); );
if (file == null) {
return;
}
var plainText = ''; var plainText = '';
if (!kIsWeb) { if (!kIsWeb) {
final path = result?.files.single.path; final path = file.path;
if (path == null) { if (path == null) {
return; return;
} }
plainText = await File(path).readAsString(); plainText = await File(path).readAsString();
} else { } else {
final bytes = result?.files.first.bytes; // file_picker v13 reads a file's content on demand.
if (bytes == null) { plainText = const Utf8Decoder().convert(await file.readAsBytes());
return;
}
plainText = const Utf8Decoder().convert(bytes);
} }
var jsonString = ''; var jsonString = '';

View file

@ -42,7 +42,7 @@ dependencies:
google_fonts: ^6.1.0 google_fonts: ^6.1.0
flutter_localizations: flutter_localizations:
sdk: flutter sdk: flutter
file_picker: ^10.3.10 file_picker: ^13.1.0
universal_html: ^2.0.8 universal_html: ^2.0.8
highlight: ^0.7.0 highlight: ^0.7.0
http: ^1.1.0 http: ^1.1.0

View file

@ -335,15 +335,19 @@ class _UploadImageMenuState extends State<UploadImageMenu> {
dialogTitle: '', dialogTitle: '',
type: kIsWeb ? fp.FileType.custom : fp.FileType.image, type: kIsWeb ? fp.FileType.custom : fp.FileType.image,
allowedExtensions: kIsWeb ? allowedExtensions : null, allowedExtensions: kIsWeb ? allowedExtensions : null,
withData: kIsWeb,
); );
if (result != null && result.files.isNotEmpty) { 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(() { setState(() {
final bytes = result.files.first.bytes; if (bytes != null) {
if (kIsWeb && bytes != null) {
_imagePathOrContent = base64String(bytes); _imagePathOrContent = base64String(bytes);
} else { } else {
_imagePathOrContent = result.files.first.path; _imagePathOrContent = file.path;
} }
}); });
} }

View file

@ -1,12 +1,19 @@
import 'dart:typed_data';
import 'package:appflowy_editor/src/editor/util/file_picker/file_picker_service.dart'; import 'package:appflowy_editor/src/editor/util/file_picker/file_picker_service.dart';
import 'package:file_picker/file_picker.dart' as fp; 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 { class FilePicker implements FilePickerService {
@override @override
Future<String?> getDirectoryPath({String? title}) { Future<String?> 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 @override
Future<FilePickerResult?> pickFiles({ Future<FilePickerResult?> pickFiles({
String? dialogTitle, String? dialogTitle,
@ -19,21 +26,31 @@ class FilePicker implements FilePickerService {
bool withReadStream = false, bool withReadStream = false,
bool lockParentWindow = false, bool lockParentWindow = false,
}) async { }) async {
final result = await fp.FilePicker.platform.pickFiles( if (allowMultiple) {
final files = await fp.FilePicker.pickFiles(
dialogTitle: dialogTitle, dialogTitle: dialogTitle,
initialDirectory: initialDirectory, initialDirectory: initialDirectory,
type: type, type: type,
allowedExtensions: allowedExtensions, allowedExtensions: allowedExtensions,
onFileLoading: onFileLoading, onFileLoading: onFileLoading,
allowMultiple: allowMultiple,
withData: withData,
withReadStream: withReadStream,
lockParentWindow: lockParentWindow,
); );
return FilePickerResult(files);
return FilePickerResult(result?.files ?? []);
} }
final file = await fp.FilePicker.pickFile(
dialogTitle: dialogTitle,
initialDirectory: initialDirectory,
type: type,
allowedExtensions: allowedExtensions,
onFileLoading: onFileLoading,
);
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 @override
Future<String?> saveFile({ Future<String?> saveFile({
String? dialogTitle, String? dialogTitle,
@ -42,14 +59,18 @@ class FilePicker implements FilePickerService {
fp.FileType type = fp.FileType.any, fp.FileType type = fp.FileType.any,
List<String>? allowedExtensions, List<String>? allowedExtensions,
bool lockParentWindow = false, bool lockParentWindow = false,
}) { }) async {
return fp.FilePicker.platform.saveFile( final uri = await fp.FilePicker.saveFile(
fileName: fileName ?? 'untitled',
bytes: Uint8List(0),
dialogTitle: dialogTitle, dialogTitle: dialogTitle,
fileName: fileName,
initialDirectory: initialDirectory, initialDirectory: initialDirectory,
type: type, type: type,
allowedExtensions: allowedExtensions, allowedExtensions: allowedExtensions,
lockParentWindow: lockParentWindow,
); );
if (uri == null) {
return null;
}
return uri.scheme == 'file' ? uri.toFilePath() : uri.toString();
} }
} }

View file

@ -5,17 +5,17 @@ version: 6.1.0
homepage: "https://github.com/AppFlowy-IO/appflowy-editor" homepage: "https://github.com/AppFlowy-IO/appflowy-editor"
environment: environment:
sdk: ">=3.6.0 <4.0.0" sdk: ">=3.11.0 <4.0.0"
flutter: ">=3.32.0" flutter: ">=3.41.6"
description: A highly customizable rich-text editor for Flutter. The AppFlowy description: A highly customizable rich-text editor for Flutter. The AppFlowy
Editor project for AppFlowy and beyond. Editor project for AppFlowy and beyond.
dependencies: dependencies:
collection: "^1.18.0" collection: "^1.18.0"
device_info_plus: ^12.3.0 device_info_plus: ^13.2.0
diff_match_patch: "^0.4.1" diff_match_patch: "^0.4.1"
file_picker: ^10.3.10 file_picker: ^13.1.0
flutter: flutter:
sdk: flutter sdk: flutter
flutter_svg: "^2.0.6" flutter_svg: "^2.0.6"