Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bug] Cannot capture AppFlowy Editor widget including images inserted as base64 encoded string #1003

Open
hjkim-mango opened this issue Dec 26, 2024 · 0 comments

Comments

@hjkim-mango
Copy link

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

  1. Insert an image as the base64 encoded in an editor.
  2. 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
Screenshot 2024-12-26 at 4 40 07 PM

Captured Image
Screenshot 2024-12-26 at 4 39 45 PM

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,
                  ),
                ),
              ]),
        )));
  }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant