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:
parent
01eccc6ee3
commit
a512a3ef88
5 changed files with 74 additions and 51 deletions
|
|
@ -435,53 +435,51 @@ 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;
|
||||||
|
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}',
|
fileName: 'document.${fileType.extension}',
|
||||||
|
bytes: bytes,
|
||||||
);
|
);
|
||||||
if (path != null) {
|
if (uri != null && mounted) {
|
||||||
await File(path).writeAsString(result);
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
if (fileType == ExportFileType.pdf) {
|
SnackBar(
|
||||||
final pdf = await PdfHTMLEncoder(
|
content: Text('This document is saved to the ${uri.toFilePath()}'),
|
||||||
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'),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 = '';
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
initialDirectory: initialDirectory,
|
||||||
|
type: type,
|
||||||
|
allowedExtensions: allowedExtensions,
|
||||||
|
onFileLoading: onFileLoading,
|
||||||
|
);
|
||||||
|
return FilePickerResult(files);
|
||||||
|
}
|
||||||
|
|
||||||
|
final file = await fp.FilePicker.pickFile(
|
||||||
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([?file]);
|
||||||
return FilePickerResult(result?.files ?? []);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue