I’m developing a Flutter application where I use the flutter_quill package to manage a text editor. I need to allow users to upload images from the device’s gallery and insert them into the document as base64 text. Next, I need to convert this base64 text to an image when the document loads.
I wrote a function to choose and insert the image as base64:
class EditorialPlaneScreen extends StatefulWidget {
const EditorialPlaneScreen({Key? key, required this.clientId})
: super(key: key);
final String clientId;
@override
State<EditorialPlaneScreen> createState() => _EditorialPlaneScreenState();
}
final storage = new FlutterSecureStorage();
bool isPdfLoading = false;
class _EditorialPlaneScreenState extends State<EditorialPlaneScreen> {
QuillController _controller = QuillController.basic();
final currentUser = FirebaseAuth.instance.currentUser!;
@override
void dispose() {
_controller.dispose();
super.dispose();
}
Future<void> _loadDocumentFromServer(String textFromServer) async {
try {
var jsonFromServer = jsonDecode(textFromServer);
setState(() {
_controller = QuillController(
document: Document.fromJson(jsonFromServer),
selection: const TextSelection.collapsed(offset: 0),
);
});
} catch (e) {
print(e);
}
}
Future<void> _pickAndInsertImage() async {
final ImagePicker _picker = ImagePicker();
final XFile? image = await _picker.pickImage(source: ImageSource.gallery);
if (image != null) {
final bytes = File(image.path).readAsBytesSync();
String base64Image = base64Encode(bytes);
// Inserisci l'immagine nel documento
final index = _controller.selection.baseOffset;
final length = _controller.selection.extentOffset - index;
_controller.replaceText(index, length, BlockEmbed.image(base64Image),
TextSelection.collapsed(offset: index + 1));
}
}
printPlane() async {
setState(() {
isPdfLoading = true;
});
final pdf = pw.Document();
pdf.addPage(
pw.MultiPage(
pageFormat: PdfPageFormat.a4,
margin: const pw.EdgeInsets.all(
32), // Aggiungi un margine per una migliore leggibilità
build: (pw.Context context) => [
pw.Paragraph(
text: _controller.document
.toPlainText(), // Utilizza il testo puro qui
),
],
),
);
// Visualizza il PDF
await Printing.layoutPdf(
onLayout: (PdfPageFormat format) async => pdf.save(),
);
setState(() {
isPdfLoading = false;
});
}
Widget _buildEditor(bool isAdmin, Translations translations) {
return Container(
decoration: const BoxDecoration(color: AppColors.secondaryColor),
child: SafeArea(
child: Column(
children: [
const SizedBox(height: Dimension.paddingS),
if (isAdmin)
QuillToolbar.simple(
configurations: QuillSimpleToolbarConfigurations(
controller: _controller,
showInlineCode: false,
showSubscript: false,
showSuperscript: false,
showSearchButton: false,
showCodeBlock: false,
sectionDividerSpace: 0,
showFontFamily: false,
toolbarSectionSpacing: 0,
sharedConfigurations: const QuillSharedConfigurations(
locale: Locale('it'),
),
customButtons: [
QuillToolbarCustomButtonOptions(
icon: Icon(
Icons.image,
),
onPressed: () {
_pickAndInsertImage();
},
),
QuillToolbarCustomButtonOptions(
icon: isPdfLoading
? const CircularProgressIndicator(
color: AppColors.primaryColor,
)
: const Icon(
Icons.print,
),
onPressed: () {
if (!isPdfLoading) {
printPlane();
} else {
null;
}
},
),
QuillToolbarCustomButtonOptions(
icon: const Icon(Icons.save),
onPressed: () {
editData(context, translations, widget.clientId);
},
)
]),
),
Expanded(
child: QuillEditor.basic(
configurations: QuillEditorConfigurations(
controller: _controller,
readOnly: isAdmin ? false : true,
expands: false,
onLaunchUrl: (String url) {
final Uri _url = Uri.parse(url);
openLink(_url);
},
placeholder: translations.addEditorialPlane,
customStyles: const DefaultStyles(color: Colors.white),
padding: const EdgeInsets.all(Dimension.paddingXS)),
scrollController: ScrollController(),
focusNode: FocusNode(),
),
),
],
),
),
);
}
void openLink(_url) async {
if (!await launchUrl(
_url,
mode: LaunchMode.externalApplication,
)) throw 'Could not launch $_url';
}
Widget _buildNoData(bool isAdmin, Translations translations) {
return Container(
decoration: const BoxDecoration(color: AppColors.secondaryColor),
child: SafeArea(
child: Column(
children: [
const SizedBox(height: Dimension.paddingS),
if (isAdmin)
QuillToolbar.simple(
configurations: QuillSimpleToolbarConfigurations(
controller: _controller,
showInlineCode: false,
showSubscript: false,
showSuperscript: false,
showSearchButton: false,
showCodeBlock: false,
sectionDividerSpace: 0,
toolbarSectionSpacing: 0,
showFontFamily: false,
sharedConfigurations: const QuillSharedConfigurations(
locale: Locale('it'),
),
customButtons: [
QuillToolbarCustomButtonOptions(
icon: Icon(
Icons.image,
),
onPressed: () {
_pickAndInsertImage();
},
),
QuillToolbarCustomButtonOptions(
icon: isPdfLoading
? const CircularProgressIndicator(
color: AppColors.primaryColor,
)
: const Icon(
Icons.print,
),
onPressed: () {
if (!isPdfLoading) {
printPlane();
} else {
null;
}
},
),
QuillToolbarCustomButtonOptions(
icon: const Icon(Icons.save),
onPressed: () {
insertData(context, translations, widget.clientId);
},
),
]),
),
Expanded(
child: QuillEditor.basic(
configurations: QuillEditorConfigurations(
controller: _controller,
readOnly: isAdmin ? false : true,
expands: false,
onLaunchUrl: (String url) {
final Uri _url = Uri.parse(url);
openLink(_url);
},
placeholder: translations.addEditorialPlane,
customStyles: const DefaultStyles(color: Colors.white),
padding: const EdgeInsets.all(Dimension.paddingXS)),
scrollController: ScrollController(),
focusNode: FocusNode(),
),
),
],
),
),
);
}
@override
Widget build(BuildContext context) {
Translations translations = Translations.of(context);
return Scaffold(
backgroundColor: AppColors.primaryColor,
appBar: AppBar(
elevation: 0,
title: Text(
translations.pianoEditoriale,
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
),
centerTitle: true,
backgroundColor: AppColors.primaryColor,
),
body: FutureBuilder<bool>(
future: Provider.of<AuthProvider>(context, listen: false)
.checkUserRole(currentUser.email.toString()),
builder: (context, snapshot) {
bool isAdmin = snapshot.data ?? false;
return StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance
.collection(CustomCollectionPath.editorialPlane)
.where('clientID', isEqualTo: widget.clientId)
.snapshots(),
builder: (context, snapshot) {
if (snapshot.hasError) {
return Center(
child: Text('Errore: ${snapshot.error}'),
);
}
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(
child: CircularProgressIndicator(),
);
}
if (snapshot.hasData && snapshot.data!.docs.isNotEmpty) {
final firstDocument = snapshot.data!.docs[0];
final textFromServer = firstDocument['text'];
if (_controller.document.isEmpty()) {
WidgetsBinding.instance.addPostFrameCallback((_) {
_loadDocumentFromServer(textFromServer);
});
}
return _buildEditor(isAdmin, translations);
} else {
if (isAdmin) {
return _buildNoData(isAdmin, translations);
} else {
return Center(
child: Text(
translations.noEditorialPlane,
style: const TextStyle(color: AppColors.secondaryColor),
),
);
}
}
},
);
},
),
);
}
Future<void> insertData(context, translations, clientId) async {
if (!_controller.document.isEmpty()) {
try {
CollectionReference collection = FirebaseFirestore.instance
.collection(CustomCollectionPath.editorialPlane);
Map<String, dynamic> data = {
'clientID': clientId,
'text': jsonEncode(_controller.document.toDelta().toJson()),
'data': DateTime.now()
};
await collection.add(data);
NetKarpetUtils.showSnackbarSuccess(context);
Navigator.pop(context);
} catch (e) {
print(e);
NetKarpetUtils.showSnackbarError(context);
}
}
}
Future<void> editData(context, translations, clientId) async {
if (!_controller.document.isEmpty()) {
try {
CollectionReference collection = FirebaseFirestore.instance
.collection(CustomCollectionPath.editorialPlane);
QuerySnapshot querySnapshot =
await collection.where('clientID', isEqualTo: clientId).get();
DocumentSnapshot documentSnapshot = querySnapshot.docs[0];
DocumentReference documentReference = documentSnapshot.reference;
Map<String, dynamic> updateData = {
'text': jsonEncode(_controller.document.toDelta().toJson()),
'data': DateTime.now()
};
await documentReference.update(updateData);
NetKarpetUtils.showSnackbarSuccess(context);
Navigator.pop(context);
} catch (e) {
print(e);
NetKarpetUtils.showSnackbarError(context);
}
}
}
}
This looks like an error to me: “The embedable type ‘image’ is not supported by the provided embed constructors. You must pass your constructor function to the embedBuilders property of the QuillEditor or QuillField widgets or specify an unknownEmbedBuilder.”