Rich text editor for Flutter
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.
 
 
 
 
 

513 lines
17 KiB

import 'dart:io';
import 'package:flutter/material.dart';
import 'package:i18n_extension/i18n_widget.dart';
import '../models/documents/attribute.dart';
import '../models/themes/quill_custom_icon.dart';
import '../models/themes/quill_dialog_theme.dart';
import '../models/themes/quill_icon_theme.dart';
import '../utils/font.dart';
import 'controller.dart';
import 'toolbar/arrow_indicated_button_list.dart';
import 'toolbar/camera_button.dart';
import 'toolbar/clear_format_button.dart';
import 'toolbar/color_button.dart';
import 'toolbar/history_button.dart';
import 'toolbar/image_button.dart';
import 'toolbar/image_video_utils.dart';
import 'toolbar/indent_button.dart';
import 'toolbar/link_style_button.dart';
import 'toolbar/quill_dropdown_button.dart';
import 'toolbar/quill_icon_button.dart';
import 'toolbar/select_alignment_button.dart';
import 'toolbar/select_header_style_button.dart';
import 'toolbar/toggle_check_list_button.dart';
import 'toolbar/toggle_style_button.dart';
import 'toolbar/video_button.dart';
export 'toolbar/clear_format_button.dart';
export 'toolbar/color_button.dart';
export 'toolbar/history_button.dart';
export 'toolbar/image_button.dart';
export 'toolbar/image_video_utils.dart';
export 'toolbar/indent_button.dart';
export 'toolbar/link_style_button.dart';
export 'toolbar/quill_dropdown_button.dart';
export 'toolbar/quill_icon_button.dart';
export 'toolbar/select_alignment_button.dart';
export 'toolbar/select_header_style_button.dart';
export 'toolbar/toggle_check_list_button.dart';
export 'toolbar/toggle_style_button.dart';
export 'toolbar/video_button.dart';
typedef OnImagePickCallback = Future<String?> Function(File file);
typedef OnVideoPickCallback = Future<String?> Function(File file);
typedef FilePickImpl = Future<String?> Function(BuildContext context);
typedef WebImagePickImpl = Future<String?> Function(
OnImagePickCallback onImagePickCallback);
typedef WebVideoPickImpl = Future<String?> Function(
OnVideoPickCallback onImagePickCallback);
typedef MediaPickSettingSelector = Future<MediaPickSetting?> Function(
BuildContext context);
// The default size of the icon of a button.
const double kDefaultIconSize = 18;
// The factor of how much larger the button is in relation to the icon.
const double kIconButtonFactor = 1.77;
class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
const QuillToolbar({
required this.children,
this.toolbarHeight = 36,
this.toolbarIconAlignment = WrapAlignment.center,
this.toolbarSectionSpacing = 4,
this.multiRowsDisplay = true,
this.color,
this.filePickImpl,
this.customIcons = const [],
this.locale,
Key? key,
}) : super(key: key);
factory QuillToolbar.basic({
required QuillController controller,
double toolbarIconSize = kDefaultIconSize,
double toolbarSectionSpacing = 4,
WrapAlignment toolbarIconAlignment = WrapAlignment.center,
bool showDividers = true,
bool showFontSize = true,
bool showBoldButton = true,
bool showItalicButton = true,
bool showSmallButton = false,
bool showUnderLineButton = true,
bool showStrikeThrough = true,
bool showInlineCode = true,
bool showColorButton = true,
bool showBackgroundColorButton = true,
bool showClearFormat = true,
bool showAlignmentButtons = false,
bool showLeftAlignment = true,
bool showCenterAlignment = true,
bool showRightAlignment = true,
bool showJustifyAlignment = true,
bool showHeaderStyle = true,
bool showListNumbers = true,
bool showListBullets = true,
bool showListCheck = true,
bool showCodeBlock = true,
bool showQuote = true,
bool showIndent = true,
bool showLink = true,
bool showUndo = true,
bool showRedo = true,
bool multiRowsDisplay = true,
bool showImageButton = true,
bool showVideoButton = true,
bool showCameraButton = true,
bool showDirection = false,
OnImagePickCallback? onImagePickCallback,
OnVideoPickCallback? onVideoPickCallback,
MediaPickSettingSelector? mediaPickSettingSelector,
FilePickImpl? filePickImpl,
WebImagePickImpl? webImagePickImpl,
WebVideoPickImpl? webVideoPickImpl,
List<QuillCustomIcon> customIcons = const [],
///Map of font sizes in string
Map<String, String>? fontSizeValues,
///The theme to use for the icons in the toolbar, uses type [QuillIconTheme]
QuillIconTheme? iconTheme,
///The theme to use for the theming of the [LinkDialog()],
///shown when embedding an image, for example
QuillDialogTheme? dialogTheme,
/// The locale to use for the editor toolbar, defaults to system locale
/// More at https://github.com/singerdmx/flutter-quill#translation
Locale? locale,
Key? key,
}) {
final isButtonGroupShown = [
showFontSize ||
showBoldButton ||
showItalicButton ||
showSmallButton ||
showUnderLineButton ||
showStrikeThrough ||
showInlineCode ||
showColorButton ||
showBackgroundColorButton ||
showClearFormat ||
onImagePickCallback != null ||
onVideoPickCallback != null,
showAlignmentButtons || showDirection,
showLeftAlignment,
showCenterAlignment,
showRightAlignment,
showJustifyAlignment,
showHeaderStyle,
showListNumbers || showListBullets || showListCheck || showCodeBlock,
showQuote || showIndent,
showLink
];
//default font size values
final fontSizes =
fontSizeValues ?? {'Small': 'small', 'Large': 'large', 'Huge': 'huge'};
return QuillToolbar(
key: key,
toolbarHeight: toolbarIconSize * 2,
toolbarSectionSpacing: toolbarSectionSpacing,
toolbarIconAlignment: toolbarIconAlignment,
multiRowsDisplay: multiRowsDisplay,
customIcons: customIcons,
locale: locale,
children: [
if (showUndo)
HistoryButton(
icon: Icons.undo_outlined,
iconSize: toolbarIconSize,
controller: controller,
undo: true,
iconTheme: iconTheme,
),
if (showRedo)
HistoryButton(
icon: Icons.redo_outlined,
iconSize: toolbarIconSize,
controller: controller,
undo: false,
iconTheme: iconTheme,
),
if (showFontSize)
QuillDropdownButton(
iconTheme: iconTheme,
iconSize: toolbarIconSize,
attribute: Attribute.size,
controller: controller,
items: [
for (MapEntry<String, String> fontSize in fontSizes.entries)
PopupMenuItem<String>(
key: ValueKey(fontSize.key),
value: fontSize.value,
child: Text(fontSize.key.toString()),
),
],
onSelected: (newSize) {
if (newSize != null) {
controller.formatSelection(
Attribute.fromKeyValue('size', getFontSize(newSize)));
}
},
rawItemsMap: fontSizes,
),
if (showBoldButton)
ToggleStyleButton(
attribute: Attribute.bold,
icon: Icons.format_bold,
iconSize: toolbarIconSize,
controller: controller,
iconTheme: iconTheme,
),
if (showItalicButton)
ToggleStyleButton(
attribute: Attribute.italic,
icon: Icons.format_italic,
iconSize: toolbarIconSize,
controller: controller,
iconTheme: iconTheme,
),
if (showSmallButton)
ToggleStyleButton(
attribute: Attribute.small,
icon: Icons.format_size,
iconSize: toolbarIconSize,
controller: controller,
iconTheme: iconTheme,
),
if (showUnderLineButton)
ToggleStyleButton(
attribute: Attribute.underline,
icon: Icons.format_underline,
iconSize: toolbarIconSize,
controller: controller,
iconTheme: iconTheme,
),
if (showStrikeThrough)
ToggleStyleButton(
attribute: Attribute.strikeThrough,
icon: Icons.format_strikethrough,
iconSize: toolbarIconSize,
controller: controller,
iconTheme: iconTheme,
),
if (showInlineCode)
ToggleStyleButton(
attribute: Attribute.inlineCode,
icon: Icons.code,
iconSize: toolbarIconSize,
controller: controller,
iconTheme: iconTheme,
),
if (showColorButton)
ColorButton(
icon: Icons.color_lens,
iconSize: toolbarIconSize,
controller: controller,
background: false,
iconTheme: iconTheme,
),
if (showBackgroundColorButton)
ColorButton(
icon: Icons.format_color_fill,
iconSize: toolbarIconSize,
controller: controller,
background: true,
iconTheme: iconTheme,
),
if (showClearFormat)
ClearFormatButton(
icon: Icons.format_clear,
iconSize: toolbarIconSize,
controller: controller,
iconTheme: iconTheme,
),
if (showImageButton)
ImageButton(
icon: Icons.image,
iconSize: toolbarIconSize,
controller: controller,
onImagePickCallback: onImagePickCallback,
filePickImpl: filePickImpl,
webImagePickImpl: webImagePickImpl,
mediaPickSettingSelector: mediaPickSettingSelector,
iconTheme: iconTheme,
dialogTheme: dialogTheme,
),
if (showVideoButton)
VideoButton(
icon: Icons.movie_creation,
iconSize: toolbarIconSize,
controller: controller,
onVideoPickCallback: onVideoPickCallback,
filePickImpl: filePickImpl,
webVideoPickImpl: webImagePickImpl,
mediaPickSettingSelector: mediaPickSettingSelector,
iconTheme: iconTheme,
dialogTheme: dialogTheme,
),
if ((onImagePickCallback != null || onVideoPickCallback != null) &&
showCameraButton)
CameraButton(
icon: Icons.photo_camera,
iconSize: toolbarIconSize,
controller: controller,
onImagePickCallback: onImagePickCallback,
onVideoPickCallback: onVideoPickCallback,
filePickImpl: filePickImpl,
webImagePickImpl: webImagePickImpl,
webVideoPickImpl: webVideoPickImpl,
iconTheme: iconTheme,
),
if (showDividers &&
isButtonGroupShown[0] &&
(isButtonGroupShown[1] ||
isButtonGroupShown[2] ||
isButtonGroupShown[3] ||
isButtonGroupShown[4] ||
isButtonGroupShown[5]))
VerticalDivider(
indent: 12,
endIndent: 12,
color: Colors.grey.shade400,
),
if (showAlignmentButtons)
SelectAlignmentButton(
controller: controller,
iconSize: toolbarIconSize,
iconTheme: iconTheme,
showLeftAlignment: showLeftAlignment,
showCenterAlignment: showCenterAlignment,
showRightAlignment: showRightAlignment,
showJustifyAlignment: showJustifyAlignment,
),
if (showDirection)
ToggleStyleButton(
attribute: Attribute.rtl,
controller: controller,
icon: Icons.format_textdirection_r_to_l,
iconSize: toolbarIconSize,
iconTheme: iconTheme,
),
if (showDividers &&
isButtonGroupShown[1] &&
(isButtonGroupShown[2] ||
isButtonGroupShown[3] ||
isButtonGroupShown[4] ||
isButtonGroupShown[5]))
VerticalDivider(
indent: 12,
endIndent: 12,
color: Colors.grey.shade400,
),
if (showHeaderStyle)
SelectHeaderStyleButton(
controller: controller,
iconSize: toolbarIconSize,
iconTheme: iconTheme,
),
if (showDividers &&
showHeaderStyle &&
isButtonGroupShown[2] &&
(isButtonGroupShown[3] ||
isButtonGroupShown[4] ||
isButtonGroupShown[5]))
VerticalDivider(
indent: 12,
endIndent: 12,
color: Colors.grey.shade400,
),
if (showListNumbers)
ToggleStyleButton(
attribute: Attribute.ol,
controller: controller,
icon: Icons.format_list_numbered,
iconSize: toolbarIconSize,
iconTheme: iconTheme,
),
if (showListBullets)
ToggleStyleButton(
attribute: Attribute.ul,
controller: controller,
icon: Icons.format_list_bulleted,
iconSize: toolbarIconSize,
iconTheme: iconTheme,
),
if (showListCheck)
ToggleCheckListButton(
attribute: Attribute.unchecked,
controller: controller,
icon: Icons.check_box,
iconSize: toolbarIconSize,
iconTheme: iconTheme,
),
if (showCodeBlock)
ToggleStyleButton(
attribute: Attribute.codeBlock,
controller: controller,
icon: Icons.code,
iconSize: toolbarIconSize,
iconTheme: iconTheme,
),
if (showDividers &&
isButtonGroupShown[3] &&
(isButtonGroupShown[4] || isButtonGroupShown[5]))
VerticalDivider(
indent: 12,
endIndent: 12,
color: Colors.grey.shade400,
),
if (showQuote)
ToggleStyleButton(
attribute: Attribute.blockQuote,
controller: controller,
icon: Icons.format_quote,
iconSize: toolbarIconSize,
iconTheme: iconTheme,
),
if (showIndent)
IndentButton(
icon: Icons.format_indent_increase,
iconSize: toolbarIconSize,
controller: controller,
isIncrease: true,
iconTheme: iconTheme,
),
if (showIndent)
IndentButton(
icon: Icons.format_indent_decrease,
iconSize: toolbarIconSize,
controller: controller,
isIncrease: false,
iconTheme: iconTheme,
),
if (showDividers && isButtonGroupShown[4] && isButtonGroupShown[5])
VerticalDivider(
indent: 12,
endIndent: 12,
color: Colors.grey.shade400,
),
if (showLink)
LinkStyleButton(
controller: controller,
iconSize: toolbarIconSize,
iconTheme: iconTheme,
dialogTheme: dialogTheme,
),
if (customIcons.isNotEmpty)
if (showDividers)
VerticalDivider(
indent: 12,
endIndent: 12,
color: Colors.grey.shade400,
),
for (var customIcon in customIcons)
QuillIconButton(
highlightElevation: 0,
hoverElevation: 0,
size: toolbarIconSize * kIconButtonFactor,
icon: Icon(customIcon.icon, size: toolbarIconSize),
borderRadius: iconTheme?.borderRadius ?? 2,
onPressed: customIcon.onTap),
],
);
}
final List<Widget> children;
final double toolbarHeight;
final double toolbarSectionSpacing;
final WrapAlignment toolbarIconAlignment;
final bool multiRowsDisplay;
/// The color of the toolbar.
///
/// Defaults to [ThemeData.canvasColor] of the current [Theme] if no color
/// is given.
final Color? color;
final FilePickImpl? filePickImpl;
/// The locale to use for the editor toolbar, defaults to system locale
/// More https://github.com/singerdmx/flutter-quill#translation
final Locale? locale;
/// List of custom icons
final List<QuillCustomIcon> customIcons;
@override
Size get preferredSize => Size.fromHeight(toolbarHeight);
@override
Widget build(BuildContext context) {
return I18n(
initialLocale: locale,
child: multiRowsDisplay
? Wrap(
alignment: toolbarIconAlignment,
runSpacing: 4,
spacing: toolbarSectionSpacing,
children: children,
)
: Container(
constraints:
BoxConstraints.tightFor(height: preferredSize.height),
color: color ?? Theme.of(context).canvasColor,
child: ArrowIndicatedButtonList(buttons: children),
),
);
}
}