我无法使用 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
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()}";
}
}