ChromeWeb 中 FlutterQuill quill_toobar 无法实现插入图片按钮?

问题描述 投票:0回答:1

我无法使用 Quill 实现从 url 插入图像。我捕获了一些错误:

import 'dart:convert';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:google_docsx/colors.dart';
import 'package:flutter_quill/flutter_quill.dart' as quill hide Text;
import 'package:flutter_quill/extensions.dart';
import 'package:flutter_quill_extensions/flutter_quill_extensions.dart';
import 'package:file_picker/file_picker.dart';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';

class DocumentScreen extends ConsumerStatefulWidget {
  final String id;
  const DocumentScreen({
    Key? key,
    required this.id,
  }) : super(key: key);

  @override
  ConsumerState<ConsumerStatefulWidget> createState() => _DocumentScreenState();
}

class _DocumentScreenState extends ConsumerState<DocumentScreen> {
  TextEditingController titleController = TextEditingController(text: 'Untitled Document');
  quill.QuillController? _controller = quill.QuillController.basic();
  final FocusNode _focusNode = FocusNode();


  @override
  void initState() {
    super.initState();
  }


  @override
  void dispose() {
    super.dispose();
    titleController.dispose();
  }


  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: kWhiteColor,
        elevation: 0,
        actions: [
          Padding(
            padding: const EdgeInsets.all(10.0),
            child: ElevatedButton.icon(
              onPressed: () {

              },
              icon: const Icon(
                Icons.lock,
                size: 16,
              ),
              label: const Text('Share'),
              style: ElevatedButton.styleFrom(
                backgroundColor: kBlueColor,
              ),
            ),
          ),
        ],
        title: Padding(
          padding: const EdgeInsets.symmetric(vertical: 9.0),
          child: Row(
            children: [
              GestureDetector(
                onTap: () {
                  // Routemaster.of(context).replace('/');
                },
                child: Image.asset(
                  'assets/images/docs-logo.png',
                  height: 40,
                ),
              ),
              const SizedBox(width: 10),
              SizedBox(
                width: 180,
                child: TextField(
                  controller: titleController,
                  decoration: const InputDecoration(
                    border: InputBorder.none,
                    focusedBorder: OutlineInputBorder(
                      borderSide: BorderSide(
                        color: kBlueColor,
                      ),
                    ),
                    contentPadding: EdgeInsets.only(left: 10),
                  ),
                  onSubmitted: (value) {}
                      // (value) => updateTitle(ref, value),
                ),
              ),
            ],
          ),
        ),
        bottom: PreferredSize(
          preferredSize: const Size.fromHeight(1),
          child: Container(
            decoration: BoxDecoration(
              border: Border.all(
                color: kGreyColor,
                width: 0.1,
              ),
            ),
          ),
        ),
      ),
      body: Center(
        child: Column(
          children: [
            const SizedBox(height: 10),
            quill.QuillToolbar.basic(
                controller: _controller!,
                embedButtons: FlutterQuillEmbeds.buttons(
                  // if omit, "image" button only allows adding images from url.
                  // onImagePickCallback: _onImagePickCallback,
                  // onVideoPickCallback: _onVideoPickCallback,
                ),
                afterButtonPressed: _focusNode.requestFocus,
            ),
            const SizedBox(height: 10),
            Expanded(
              child: SizedBox(
                width: 750,
                child: Card(
                  color: kWhiteColor,
                  elevation: 5,
                  child: Padding(
                    padding: const EdgeInsets.all(30.0),
                    child: quill.QuillEditor(
                      controller: _controller!,
                      readOnly: false,
                      embedBuilders: FlutterQuillEmbeds.builders(),
                      focusNode: _focusNode,
                      autoFocus: false,
                      expands: false,
                      scrollable: true,
                      padding: EdgeInsets.zero,
                      scrollController: ScrollController(),
                    ),
                  ),
                ),
              ),
            )
          ],
        ),
      ),
    );
  }
}

我已阅读所有有关 Quill flutter 的相关参考资料和文档以及向 Quill 添加图像的指南,但我失败了。我和他们一样做,但是错误发生得很奇怪。 “!kIsWeb。请提供用于 Web 的图像 EmbedBuilder”或“提供的嵌入构建器不支持可嵌入类型“图像””是日志错误,尽管我有“embedBuilders:FlutterQuillEmbeds.builders()”行。我尝试构建一个类似于以下源代码的新版本,但一切仍然像以前一样错误:

https://github.com/singerdmx/flutter-quill/blob/master/example/lib/pages/home_page.dart https://pub.dev/packages/flutter_quill

import 'dart:convert';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:google_docsx/colors.dart';
import 'package:flutter_quill/flutter_quill.dart' as quill hide Text;
import 'package:flutter_quill/extensions.dart';
import 'package:flutter_quill_extensions/flutter_quill_extensions.dart';
import 'package:file_picker/file_picker.dart';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';

class DocumentScreen extends ConsumerStatefulWidget {
  final String id;
  const DocumentScreen({
    Key? key,
    required this.id,
  }) : super(key: key);

  @override
  ConsumerState<ConsumerStatefulWidget> createState() => _DocumentScreenState();
}

class _DocumentScreenState extends ConsumerState<DocumentScreen> {
  TextEditingController titleController = TextEditingController(text: 'Untitled Document');
  quill.QuillController? _controller = quill.QuillController.basic();
  final FocusNode _focusNode = FocusNode();
  late Widget quillEditor;
  late Widget toolbar;
  // ErrorModel? errorModel;
  // SocketRepository socketRepository = SocketRepository();

  @override
  void initState() {
    super.initState();
  }

  
  @override
  void dispose() {
    super.dispose();
    titleController.dispose();
  }


  Future<String> _onImagePickCallback(File file) async {
    // Copies the picked file from temporary cache to applications directory
    final appDocDir = await getApplicationDocumentsDirectory();
    final copiedFile = await file.copy('${appDocDir.path}/${basename(file.path)}');
    return copiedFile.path.toString();
  }

  Future<String> _onVideoPickCallback(File file) async {
    // Copies the picked file from temporary cache to applications directory
    final appDocDir = await getApplicationDocumentsDirectory();
    final copiedFile = await file.copy('${appDocDir.path}/${basename(file.path)}');
    return copiedFile.path.toString();
  }

  Future<String?> _webImagePickImpl(
      OnImagePickCallback onImagePickCallback) async {
    final result = await FilePicker.platform.pickFiles();
    if (result == null) {
      return null;
    }
    // Take first, because we don't allow picking multiple files.
    final fileName = result.files.first.name;
    final file = File(fileName);

    return onImagePickCallback(file);
  }

  Future<String> _onImagePaste(Uint8List imageBytes) async {
    // Saves the image to applications directory
    final appDocDir = await getApplicationDocumentsDirectory();
    final file = await File(
        '${appDocDir.path}/${basename('${DateTime.now().millisecondsSinceEpoch}.png')}')
        .writeAsBytes(imageBytes, flush: true);
    return file.path.toString();
  }

  _buildWelcomeEditor(BuildContext context) {
    if (kIsWeb) {
      quillEditor = MouseRegion(
        cursor: SystemMouseCursors.text,
        child: quill.QuillEditor(
          controller: _controller!,
          scrollController: ScrollController(),
          scrollable: true,
          focusNode: _focusNode,
          autoFocus: false,
          readOnly: false,
          placeholder: 'Add content',
          expands: false,
          padding: EdgeInsets.zero,
          // onTapUp: (details, p1) {
          //   return _onTripleClickSelection();
          // },
          embedBuilders: FlutterQuillEmbeds.builders(),
        ),
      );
    }else{
      quillEditor = MouseRegion(
        cursor: SystemMouseCursors.text,
        child: quill.QuillEditor(
          controller: _controller!,
          scrollController: ScrollController(),
          scrollable: true,
          focusNode: _focusNode,
          autoFocus: false,
          readOnly: false,
          placeholder: 'Add content',
          enableSelectionToolbar: isMobile(),
          expands: false,
          padding: EdgeInsets.zero,
          onImagePaste: _onImagePaste,
          // onTapUp: (details, p1) {
          //   return _onTripleClickSelection();
          // },
          embedBuilders: FlutterQuillEmbeds.builders(),
        ),
      );
    }
    
    if (kIsWeb) {
      toolbar = quill.QuillToolbar.basic(
        controller: _controller!,
        embedButtons: FlutterQuillEmbeds.buttons(
          onImagePickCallback: _onImagePickCallback,
          webImagePickImpl: _webImagePickImpl,
        ),
        showAlignmentButtons: true,
        afterButtonPressed: _focusNode.requestFocus,
      );
    } else {
      toolbar = quill.QuillToolbar.basic(
        controller: _controller!,
        embedButtons: FlutterQuillEmbeds.buttons(
          onImagePickCallback: _onImagePickCallback,
          onVideoPickCallback: _onVideoPickCallback,
        ),
        showAlignmentButtons: true,
        afterButtonPressed: _focusNode.requestFocus,
      );
    }
  }

  @override
  Widget build(BuildContext context) {

    _buildWelcomeEditor(context);

    return Scaffold(
      appBar: AppBar(
        backgroundColor: kWhiteColor,
        elevation: 0,
        actions: [
          Padding(
            padding: const EdgeInsets.all(10.0),
            child: ElevatedButton.icon(
              onPressed: () {
              },
              icon: const Icon(
                Icons.lock,
                size: 16,
              ),
              label: const Text('Share'),
              style: ElevatedButton.styleFrom(
                backgroundColor: kBlueColor,
              ),
            ),
          ),
        ],
        title: Padding(
          padding: const EdgeInsets.symmetric(vertical: 9.0),
          child: Row(
            children: [
              GestureDetector(
                onTap: () {
                  // Routemaster.of(context).replace('/');
                },
                child: Image.asset(
                  'assets/images/docs-logo.png',
                  height: 40,
                ),
              ),
              const SizedBox(width: 10),
              SizedBox(
                width: 180,
                child: TextField(
                  controller: titleController,
                  decoration: const InputDecoration(
                    border: InputBorder.none,
                    focusedBorder: OutlineInputBorder(
                      borderSide: BorderSide(
                        color: kBlueColor,
                      ),
                    ),
                    contentPadding: EdgeInsets.only(left: 10),
                  ),
                  onSubmitted: (value) {}
                      // (value) => updateTitle(ref, value),
                ),
              ),
            ],
          ),
        ),
        bottom: PreferredSize(
          preferredSize: const Size.fromHeight(1),
          child: Container(
            decoration: BoxDecoration(
              border: Border.all(
                color: kGreyColor,
                width: 0.1,
              ),
            ),
          ),
        ),
      ),
      body: Center(
        child: Column(
          children: [
            const SizedBox(height: 10),
            // quill.QuillToolbar.basic(
            //     controller: _controller!,
            //     embedButtons: FlutterQuillEmbeds.buttons(
            //       // onImagePickCallback: _onImagePickCallback,
            //       // onVideoPickCallback: _onVideoPickCallback,
            //     ),
            //     afterButtonPressed: _focusNode.requestFocus,
            // ),
            toolbar,
            const SizedBox(height: 10),
            Expanded(
              child: SizedBox(
                width: 750,
                child: Card(
                  color: kWhiteColor,
                  elevation: 5,
                  child: Padding(
                    padding: const EdgeInsets.all(30.0),
                    // child: quill.QuillEditor(
                    //   controller: _controller!,
                    //   readOnly: false,
                    //   // embedBuilders: [
                    //   //   ...FlutterQuillEmbeds.builders(),
                    //   //   NotesEmbedBuilder(addEditNote: _addEditNote)
                    //   // ],
                    //   embedBuilders: FlutterQuillEmbeds.builders(),
                    //   focusNode: _focusNode,
                    //   autoFocus: false,
                    //   expands: false,
                    //   scrollable: true,
                    //   padding: EdgeInsets.zero,
                    //   scrollController: ScrollController(),
                    // ),
                    child: quillEditor,
                  ),
                ),
              ),
            )
          ],
        ),
      ),
    );
  }
}

class NotesEmbedBuilder implements quill.EmbedBuilder {
  NotesEmbedBuilder({required this.addEditNote});

  Future<void> Function(BuildContext context, {quill.Document? document}) addEditNote;

  @override
  String get key => 'notes';

  @override
  Widget build(
      BuildContext context,
      quill.QuillController controller,
      Embed node,
      bool readOnly,
      ) {
    final notes = NotesBlockEmbed(node.value.data).document;

    return Material(
      color: Colors.transparent,
      child: ListTile(
        title: Text(
          notes.toPlainText().replaceAll('\n', ' '),
          maxLines: 3,
          overflow: TextOverflow.ellipsis,
        ),
        leading: const Icon(Icons.notes),
        onTap: () => addEditNote(context, document: notes),
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(10),
          side: const BorderSide(color: Colors.grey),
        ),
      ),
    );
  }
}

class NotesBlockEmbed extends quill.CustomBlockEmbed {
  const NotesBlockEmbed(String value) : super(noteType, value);

  static const String noteType = 'notes';

  static NotesBlockEmbed fromDocument(quill.Document document) =>
      NotesBlockEmbed(jsonEncode(document.toDelta().toJson()));

  quill.Document get document => quill.Document.fromJson(jsonDecode(data));
}
══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
The following assertion was thrown building TextLine(dirty, dependencies: [MediaQuery,
_LocalizationsScope-[GlobalKey#0dd6d], _QuillPressedKeysAccess], state: _TextLineState#9d5f1):
Assertion failed:
file:///C:/Users/Kid/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_quill_extensions-0.1.0/lib/embeds/builders.dart:29:12
!kIsWeb
"Please provide image EmbedBuilder for Web"

The relevant error-causing widget was:
  TextLine
  TextLine:file:///C:/Users/Kid/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_quill-6.1.5/lib/src/widgets/raw_editor.dart:667:22
flutter dart web flutter-web quill
1个回答
0
投票
import 'dart:io';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_quill/flutter_quill.dart';
import 'package:flutter_quill/quill_delta.dart';
import 'package:flutter_quill_extensions/flutter_quill_embeds.dart';
import 'package:image_picker/image_picker.dart';

class ScreenBlogEditor extends StatefulWidget {
  const ScreenBlogEditor({Key? key}) : super(key: key);

  @override
  State<ScreenBlogEditor> createState() => _ScreenBlogEditorState();
}

class _ScreenBlogEditorState extends State<ScreenBlogEditor>
    with AutomaticKeepAliveClientMixin {
  QuillController _controller = QuillController.basic();

  Future<void> _pickImage() async {
    final ImagePicker _picker = ImagePicker();
    final XFile? pickedImage =
        await _picker.pickImage(source: ImageSource.gallery);

    if (pickedImage != null) {
      // Insert the selected image into the Quill editor
      final String imagePath = pickedImage.path;
      final File file = File(imagePath);
      final Delta imageDelta = Delta()
        ..insert( {
          'image': file.path.toString(),
        });

      // print("Image Delta ${delta.toJson()}");
      print("Image Delta ${imageDelta.toJson()}");
      // delta.compose(imageDelta);
      _controller.compose(imageDelta, TextSelection.collapsed(offset: imageDelta.length),
          ChangeSource.local);
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: Column(
        children: [
          Expanded(
            child: Padding(
              padding: const EdgeInsets.all(8.0),
              child: QuillEditor.basic(
                configurations: QuillEditorConfigurations(
                  controller: _controller,
                  readOnly: false,
                  showCursor: true,
                  embedBuilders: kIsWeb ? FlutterQuillEmbeds.editorWebBuilders() : FlutterQuillEmbeds.editorBuilders(),
                ),
              ),
            ),
          ),
          QuillToolbar(
            child: SingleChildScrollView(
              scrollDirection: Axis.horizontal,
              child: Row(
                children: [
                  QuillToolbarToggleStyleButton(
                      controller: _controller, attribute: Attribute.bold),
                  QuillToolbarToggleStyleButton(
                      controller: _controller, attribute: Attribute.italic),
                  QuillToolbarToggleStyleButton(
                      controller: _controller, attribute: Attribute.underline),
                  QuillToolbarLinkStyleButton(controller: _controller),
                  QuillToolbarCustomButton(
                    controller: _controller,
                    options: QuillToolbarCustomButtonOptions(
                      icon: const Icon(Icons.image),
                      onPressed: _pickImage,
                    ),
                  ),
                ],
              ),
            ),
          ),
        ],
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          // Get the Quill editor data as plain text
          String quillText = getQuillText();
          print("Quill Editor Text: $quillText");
        },
        child: Icon(Icons.check),
      ),
    );
  }

  String getQuillText() {
    // Retrieve the plain text from the Quill editor
    return "Chal Chal ${_controller.document.toDelta().toJson()}";
  }
}
© www.soinside.com 2019 - 2024. All rights reserved.