appflowy-editor
appflowy-editor copied to clipboard
[Bug] Cannot capture AppFlowy Editor widget including images inserted as base64 encoded string
Bug Description
I want to export the image file from the current editor content. So I used Screenshot package to capture it. It’s no issue to capture a visible widget. But there is an issue only to capture an invisible widget. I have to capture an invisible widget because my contents are over the screen. The result shows the base64-encoded images to [X] mark. If I insert images as an url or a local path, no issue for same case. It’s a problem just only for base64 encoded data.
How to Reproduce
- Insert an image as the base64 encoded in an editor.
- Capture from AppFlowy Editor widget with the current document.
Expected Behavior
Capture image show the same contents with the current editor.
Operating System
iOS, Android
AppFlowy Editor Version(s)
4.0.0
Screenshots
Current AppFlowy Editor Contents
Captured Image
Additional Context
appflowy_editor_widget.dart
import 'package:feasibility_markdown/appflowy_editor/save_image_page.dart';
import 'package:flutter/material.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:image_picker/image_picker.dart';
import 'dart:io';
import 'dart:convert';
class AppFlowyEditorWidget extends StatefulWidget {
const AppFlowyEditorWidget({super.key});
@override
State<AppFlowyEditorWidget> createState() => _AppFlowyEditorWidgetState();
}
class _AppFlowyEditorWidgetState extends State<AppFlowyEditorWidget> {
final _scaffoldKey = GlobalKey<ScaffoldState>();
late EditorState _editorState;
final _localToolbarItems = [
textDecorationMobileToolbarItem,
headingMobileToolbarItem,
todoListMobileToolbarItem,
listMobileToolbarItem,
linkMobileToolbarItem,
quoteMobileToolbarItem,
codeMobileToolbarItem,
blocksMobileToolbarItem,
textDecorationMobileToolbarItemV2
];
@override
void initState() {
super.initState();
_editorState = EditorState.blank();
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: const Text('AppFlowy Editor'),
),
body: Column(children: [
Expanded(
child: AppFlowyEditor(
characterShortcutEvents: standardCharacterShortcutEvents,
editorStyle: const EditorStyle.mobile(),
editorState: _editorState)),
Padding(
padding: const EdgeInsets.all(5),
child:
Row(mainAxisAlignment: MainAxisAlignment.center, children: [
InkWell(
child: const Icon(Icons.image),
onTap: () async {
final ImagePicker picker = ImagePicker();
final XFile? pickedImage =
await picker.pickImage(source: ImageSource.gallery);
if (pickedImage != null) {
var imageBytes =
File(pickedImage.path).readAsBytesSync();
_editorState.insertImageNode(base64Encode(imageBytes));
}
}),
InkWell(
child: const Icon(Icons.save_alt),
onTap: () async {
Navigator.of(context).push(MaterialPageRoute(
builder: (_) =>
SaveImagePage(editorState: _editorState)));
}),
])),
MobileToolbar(
editorState: _editorState, toolbarItems: _localToolbarItems)
]));
}
}
save_image_page.dart
import 'dart:io';
import 'package:appflowy_editor/appflowy_editor.dart' as appflowy;
import 'package:flutter/material.dart';
import 'package:screenshot/screenshot.dart';
import 'package:gal/gal.dart';
class SaveImagePage extends StatefulWidget {
const SaveImagePage({super.key, required this.editorState});
final appflowy.EditorState editorState;
@override
State<SaveImagePage> createState() => _SaveImagePageState();
}
class _SaveImagePageState extends State<SaveImagePage> {
final _scaffoldKey = GlobalKey<ScaffoldState>();
late ScreenshotController _screenshotController;
late appflowy.EditorState _editorState;
@override
void initState() {
super.initState();
_editorState = appflowy.EditorState(
document:
appflowy.Document.fromJson(widget.editorState.document.toJson()));
_screenshotController = ScreenshotController();
WidgetsBinding.instance.addPostFrameCallback((_) => _saveImage());
}
Widget _captureToSaveToImageWidget() {
const physicalPixelBaseLength = 512.0;
final logicalPixelBaseLength =
physicalPixelBaseLength / MediaQuery.of(context).devicePixelRatio;
return Container(
color: Colors.white,
width: logicalPixelBaseLength,
constraints: BoxConstraints(minHeight: logicalPixelBaseLength),
child: MediaQuery(
data: const MediaQueryData(),
child: IntrinsicHeight(
child: appflowy.AppFlowyEditor(
characterShortcutEvents:
appflowy.standardCharacterShortcutEvents,
editorStyle: const appflowy.EditorStyle.mobile(),
disableKeyboardService: true,
disableSelectionService: true,
shrinkWrap: true,
editorScrollController: appflowy.EditorScrollController(
editorState: _editorState, shrinkWrap: true),
editorState: _editorState),
// )),
)),
);
}
void _saveImage() async {
_screenshotController
.captureFromLongWidget(_captureToSaveToImageWidget(),
pixelRatio: MediaQuery.of(context).devicePixelRatio,
delay: const Duration(seconds: 1))
.then((capturedImage) async {
if (!mounted) return;
final file =
await File('${Directory.systemTemp.path}/flutter_image.png').create();
await file.writeAsBytes(capturedImage);
await Gal.putImage(file.path);
Navigator.of(context).pop();
});
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
backgroundColor: Colors.white,
body: const SafeArea(
child: SizedBox(
width: double.infinity,
height: double.infinity,
child: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"Saving ...",
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.black,
fontWeight: FontWeight.w600,
fontSize: 14.0,
),
),
]),
)));
}
}