dartlangeditorflutterflutter-appsflutter-examplesflutter-packageflutter-widgetquillquill-deltaquilljsreactquillrich-textrich-text-editorwysiwygwysiwyg-editor
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
288 lines
10 KiB
288 lines
10 KiB
import 'dart:io' as io show File; |
|
|
|
import 'package:flutter/material.dart'; |
|
import 'package:flutter_bloc/flutter_bloc.dart'; |
|
import 'package:flutter_quill/extensions.dart' show isAndroid, isIOS, isWeb; |
|
import 'package:flutter_quill/flutter_quill.dart'; |
|
import 'package:flutter_quill_extensions/flutter_quill_extensions.dart'; |
|
import 'package:image_cropper/image_cropper.dart'; |
|
import 'package:path/path.dart' as path; |
|
import 'package:path_provider/path_provider.dart' |
|
show getApplicationDocumentsDirectory; |
|
|
|
import '../settings/cubit/settings_cubit.dart'; |
|
import 'embeds/timestamp_embed.dart'; |
|
|
|
class MyQuillToolbar extends StatelessWidget { |
|
const MyQuillToolbar({ |
|
required this.controller, |
|
required this.focusNode, |
|
super.key, |
|
}); |
|
|
|
final QuillController controller; |
|
final FocusNode focusNode; |
|
|
|
Future<void> onImageInsertWithCropping( |
|
String image, |
|
QuillController controller, |
|
BuildContext context, |
|
) async { |
|
final croppedFile = await ImageCropper().cropImage( |
|
sourcePath: image, |
|
aspectRatioPresets: [ |
|
CropAspectRatioPreset.square, |
|
CropAspectRatioPreset.ratio3x2, |
|
CropAspectRatioPreset.original, |
|
CropAspectRatioPreset.ratio4x3, |
|
CropAspectRatioPreset.ratio16x9 |
|
], |
|
uiSettings: [ |
|
AndroidUiSettings( |
|
toolbarTitle: 'Cropper', |
|
toolbarColor: Colors.deepOrange, |
|
toolbarWidgetColor: Colors.white, |
|
initAspectRatio: CropAspectRatioPreset.original, |
|
lockAspectRatio: false, |
|
), |
|
IOSUiSettings( |
|
title: 'Cropper', |
|
), |
|
WebUiSettings( |
|
context: context, |
|
), |
|
], |
|
); |
|
final newImage = croppedFile?.path; |
|
if (newImage == null) { |
|
return; |
|
} |
|
if (isWeb()) { |
|
controller.insertImageBlock(imageSource: newImage); |
|
return; |
|
} |
|
final newSavedImage = await saveImage(io.File(newImage)); |
|
controller.insertImageBlock(imageSource: newSavedImage); |
|
} |
|
|
|
Future<void> onImageInsert(String image, QuillController controller) async { |
|
if (isWeb() || isHttpBasedUrl(image)) { |
|
controller.insertImageBlock(imageSource: image); |
|
return; |
|
} |
|
final newSavedImage = await saveImage(io.File(image)); |
|
controller.insertImageBlock(imageSource: newSavedImage); |
|
} |
|
|
|
/// For mobile platforms it will copies the picked file from temporary cache |
|
/// to applications directory |
|
/// |
|
/// for desktop platforms, it will do the same but from user files this time |
|
Future<String> saveImage(io.File file) async { |
|
final appDocDir = await getApplicationDocumentsDirectory(); |
|
final fileExt = path.extension(file.path); |
|
final newFileName = '${DateTime.now().toIso8601String()}$fileExt'; |
|
final newPath = path.join( |
|
appDocDir.path, |
|
newFileName, |
|
); |
|
final copiedFile = await file.copy(newPath); |
|
return copiedFile.path; |
|
} |
|
|
|
@override |
|
Widget build(BuildContext context) { |
|
return BlocBuilder<SettingsCubit, SettingsState>( |
|
buildWhen: (previous, current) => |
|
previous.useCustomQuillToolbar != current.useCustomQuillToolbar, |
|
builder: (context, state) { |
|
if (state.useCustomQuillToolbar) { |
|
// For more info |
|
// https://github.com/singerdmx/flutter-quill/blob/master/doc/custom_toolbar.md |
|
return QuillToolbar( |
|
configurations: const QuillToolbarConfigurations( |
|
buttonOptions: QuillToolbarButtonOptions( |
|
base: QuillToolbarBaseButtonOptions( |
|
globalIconSize: 20, |
|
globalIconButtonFactor: 1.4, |
|
), |
|
), |
|
), |
|
child: SingleChildScrollView( |
|
scrollDirection: Axis.horizontal, |
|
child: Row( |
|
children: [ |
|
IconButton( |
|
onPressed: () => context |
|
.read<SettingsCubit>() |
|
.updateSettings( |
|
state.copyWith(useCustomQuillToolbar: false)), |
|
icon: const Icon( |
|
Icons.width_normal, |
|
), |
|
), |
|
QuillToolbarHistoryButton( |
|
isUndo: true, |
|
controller: controller, |
|
), |
|
QuillToolbarHistoryButton( |
|
isUndo: false, |
|
controller: controller, |
|
), |
|
QuillToolbarToggleStyleButton( |
|
options: const QuillToolbarToggleStyleButtonOptions(), |
|
controller: controller, |
|
attribute: Attribute.bold, |
|
), |
|
QuillToolbarToggleStyleButton( |
|
options: const QuillToolbarToggleStyleButtonOptions(), |
|
controller: controller, |
|
attribute: Attribute.italic, |
|
), |
|
QuillToolbarToggleStyleButton( |
|
controller: controller, |
|
attribute: Attribute.underline, |
|
), |
|
QuillToolbarClearFormatButton( |
|
controller: controller, |
|
), |
|
const VerticalDivider(), |
|
QuillToolbarImageButton( |
|
controller: controller, |
|
), |
|
QuillToolbarCameraButton( |
|
controller: controller, |
|
), |
|
QuillToolbarVideoButton( |
|
controller: controller, |
|
), |
|
const VerticalDivider(), |
|
QuillToolbarColorButton( |
|
controller: controller, |
|
isBackground: false, |
|
), |
|
QuillToolbarColorButton( |
|
controller: controller, |
|
isBackground: true, |
|
), |
|
const VerticalDivider(), |
|
QuillToolbarSelectHeaderStyleButton( |
|
controller: controller, |
|
), |
|
const VerticalDivider(), |
|
QuillToolbarToggleCheckListButton( |
|
controller: controller, |
|
), |
|
QuillToolbarToggleStyleButton( |
|
controller: controller, |
|
attribute: Attribute.ol, |
|
), |
|
QuillToolbarToggleStyleButton( |
|
controller: controller, |
|
attribute: Attribute.ul, |
|
), |
|
QuillToolbarToggleStyleButton( |
|
controller: controller, |
|
attribute: Attribute.inlineCode, |
|
), |
|
QuillToolbarToggleStyleButton( |
|
controller: controller, |
|
attribute: Attribute.blockQuote, |
|
), |
|
QuillToolbarIndentButton( |
|
controller: controller, |
|
isIncrease: true, |
|
), |
|
QuillToolbarIndentButton( |
|
controller: controller, |
|
isIncrease: false, |
|
), |
|
const VerticalDivider(), |
|
QuillToolbarLinkStyleButton(controller: controller), |
|
], |
|
), |
|
), |
|
); |
|
} |
|
return QuillSimpleToolbar( |
|
configurations: QuillSimpleToolbarConfigurations( |
|
controller: controller, |
|
showAlignmentButtons: true, |
|
buttonOptions: QuillToolbarButtonOptions( |
|
base: QuillToolbarBaseButtonOptions( |
|
// Request editor focus when any button is pressed |
|
afterButtonPressed: focusNode.requestFocus, |
|
), |
|
), |
|
customButtons: [ |
|
QuillToolbarCustomButtonOptions( |
|
icon: const Icon(Icons.add_alarm_rounded), |
|
onPressed: () { |
|
controller.document |
|
.insert(controller.selection.extentOffset, '\n'); |
|
controller.updateSelection( |
|
TextSelection.collapsed( |
|
offset: controller.selection.extentOffset + 1, |
|
), |
|
ChangeSource.local, |
|
); |
|
|
|
controller.document.insert( |
|
controller.selection.extentOffset, |
|
TimeStampEmbed( |
|
DateTime.now().toString(), |
|
), |
|
); |
|
|
|
controller.updateSelection( |
|
TextSelection.collapsed( |
|
offset: controller.selection.extentOffset + 1, |
|
), |
|
ChangeSource.local, |
|
); |
|
|
|
controller.document |
|
.insert(controller.selection.extentOffset, ' '); |
|
controller.updateSelection( |
|
TextSelection.collapsed( |
|
offset: controller.selection.extentOffset + 1, |
|
), |
|
ChangeSource.local, |
|
); |
|
|
|
controller.document |
|
.insert(controller.selection.extentOffset, '\n'); |
|
controller.updateSelection( |
|
TextSelection.collapsed( |
|
offset: controller.selection.extentOffset + 1, |
|
), |
|
ChangeSource.local, |
|
); |
|
}, |
|
), |
|
QuillToolbarCustomButtonOptions( |
|
icon: const Icon(Icons.dashboard_customize), |
|
onPressed: () { |
|
context.read<SettingsCubit>().updateSettings( |
|
state.copyWith(useCustomQuillToolbar: true)); |
|
}, |
|
), |
|
], |
|
embedButtons: FlutterQuillEmbeds.toolbarButtons( |
|
imageButtonOptions: QuillToolbarImageButtonOptions( |
|
imageButtonConfigurations: QuillToolbarImageConfigurations( |
|
onImageInsertCallback: isAndroid(supportWeb: false) || |
|
isIOS(supportWeb: false) || |
|
isWeb() |
|
? (image, controller) => |
|
onImageInsertWithCropping(image, controller, context) |
|
: onImageInsert, |
|
), |
|
), |
|
), |
|
), |
|
); |
|
}, |
|
); |
|
} |
|
}
|
|
|