diff --git a/lib/src/translations/toolbar.i18n.dart b/lib/src/translations/toolbar.i18n.dart index bf2732e77..32329f2d7 100644 --- a/lib/src/translations/toolbar.i18n.dart +++ b/lib/src/translations/toolbar.i18n.dart @@ -27,6 +27,7 @@ extension Localization on String { 'Large': 'Large', 'Huge': 'Huge', 'Clear': 'Clear', + 'Font': 'Font', }, 'en_us': { 'Paste a link': 'Paste a link', @@ -52,6 +53,7 @@ extension Localization on String { 'Large': 'Large', 'Huge': 'Huge', 'Clear': 'Clear', + 'Font': 'Font', }, 'ar': { 'Paste a link': 'نسخ الرابط', @@ -77,6 +79,7 @@ extension Localization on String { 'Large': 'Large', 'Huge': 'Huge', 'Clear': 'Clear', + 'Font': 'Font', }, 'da': { 'Paste a link': 'Indsæt link', @@ -102,6 +105,7 @@ extension Localization on String { 'Large': 'Large', 'Huge': 'Huge', 'Clear': 'Clear', + 'Font': 'Font', }, 'de': { 'Paste a link': 'Link hinzufügen', @@ -128,6 +132,7 @@ extension Localization on String { 'Large': 'Large', 'Huge': 'Huge', 'Clear': 'Clear', + 'Font': 'Font', }, 'fr': { 'Paste a link': 'Coller un lien', @@ -153,6 +158,7 @@ extension Localization on String { 'Large': 'Large', 'Huge': 'Huge', 'Clear': 'Clear', + 'Font': 'Font', }, 'zh_CN': { 'Paste a link': '粘贴链接', @@ -178,6 +184,7 @@ extension Localization on String { 'Large': 'Large', 'Huge': 'Huge', 'Clear': 'Clear', + 'Font': 'Font', }, 'ko': { 'Paste a link': '링크를 붙여넣어 주세요.', @@ -202,6 +209,7 @@ extension Localization on String { 'Large': 'Large', 'Huge': 'Huge', 'Clear': 'Clear', + 'Font': 'Font', }, 'ru': { 'Paste a link': 'Вставить ссылку', @@ -227,6 +235,7 @@ extension Localization on String { 'Large': 'Large', 'Huge': 'Huge', 'Clear': 'Clear', + 'Font': 'Font', }, 'es': { 'Paste a link': 'Pega un enlace', @@ -253,6 +262,7 @@ extension Localization on String { 'Large': 'Large', 'Huge': 'Huge', 'Clear': 'Clear', + 'Font': 'Font', }, 'tr': { 'Paste a link': 'Bağlantıyı Yapıştır', @@ -278,6 +288,7 @@ extension Localization on String { 'Large': 'Large', 'Huge': 'Huge', 'Clear': 'Clear', + 'Font': 'Font', }, 'uk': { 'Paste a link': 'Вставити посилання', @@ -303,6 +314,7 @@ extension Localization on String { 'Large': 'Large', 'Huge': 'Huge', 'Clear': 'Clear', + 'Font': 'Font', }, 'pt': { 'Paste a link': 'Colar um link', @@ -329,6 +341,7 @@ extension Localization on String { 'Large': 'Large', 'Huge': 'Huge', 'Clear': 'Clear', + 'Font': 'Font', }, 'pl': { 'Paste a link': 'Wklej link', @@ -355,6 +368,7 @@ extension Localization on String { 'Large': 'Large', 'Huge': 'Huge', 'Clear': 'Clear', + 'Font': 'Font', }, 'vi': { 'Paste a link': 'Chèn liên kết', @@ -381,6 +395,7 @@ extension Localization on String { 'Large': 'Large', 'Huge': 'Huge', 'Clear': 'Clear', + 'Font': 'Font', }, 'ur': { 'Paste a link': 'لنک پیسٹ کریں', @@ -406,6 +421,7 @@ extension Localization on String { 'Large': 'Large', 'Huge': 'Huge', 'Clear': 'Clear', + 'Font': 'Font', }, 'id': { 'Paste a link': 'Tempel tautan', @@ -431,6 +447,7 @@ extension Localization on String { 'Large': 'Large', 'Huge': 'Huge', 'Clear': 'Clear', + 'Font': 'Font', }, 'no': { 'Paste a link': 'Lim inn lenke', @@ -456,6 +473,7 @@ extension Localization on String { 'Large': 'Large', 'Huge': 'Huge', 'Clear': 'Clear', + 'Font': 'Font', }, 'fa': { 'Paste a link': 'جایگذاری لینک', @@ -481,6 +499,7 @@ extension Localization on String { 'Large': 'Large', 'Huge': 'Huge', 'Clear': 'Clear', + 'Font': 'Font', }, 'hi': { 'Paste a link': 'लिंक पेस्ट करें', @@ -506,6 +525,7 @@ extension Localization on String { 'Large': 'Large', 'Huge': 'Huge', 'Clear': 'Clear', + 'Font': 'Font', }, 'nl': { 'Paste a link': 'Plak een link', @@ -531,6 +551,7 @@ extension Localization on String { 'Large': 'Large', 'Huge': 'Huge', 'Clear': 'Clear', + 'Font': 'Font', }, 'zh_HK': { 'Paste a link': '貼上連結', @@ -556,6 +577,7 @@ extension Localization on String { 'Large': 'Large', 'Huge': 'Huge', 'Clear': 'Clear', + 'Font': 'Font', }, 'sr': { 'Paste a link': 'Nalepi vezu', @@ -581,6 +603,7 @@ extension Localization on String { 'Large': 'Large', 'Huge': 'Huge', 'Clear': 'Clear', + 'Font': 'Font', }, }; diff --git a/lib/src/widgets/toolbar.dart b/lib/src/widgets/toolbar.dart index d84ce3071..549511b53 100644 --- a/lib/src/widgets/toolbar.dart +++ b/lib/src/widgets/toolbar.dart @@ -19,6 +19,7 @@ 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_font_family_button.dart'; import 'toolbar/quill_font_size_button.dart'; import 'toolbar/quill_icon_button.dart'; import 'toolbar/select_alignment_button.dart'; @@ -78,6 +79,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { double toolbarSectionSpacing = 4, WrapAlignment toolbarIconAlignment = WrapAlignment.center, bool showDividers = true, + bool showFontFamily = true, bool showFontSize = true, bool showBoldButton = true, bool showItalicButton = true, @@ -119,6 +121,9 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { ///Map of font sizes in string Map? fontSizeValues, + ///Map of font families in string + Map? fontFamilyValues, + ///The theme to use for the icons in the toolbar, uses type [QuillIconTheme] QuillIconTheme? iconTheme, @@ -132,7 +137,8 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { Key? key, }) { final isButtonGroupShown = [ - showFontSize || + showFontFamily || + showFontSize || showBoldButton || showItalicButton || showSmallButton || @@ -164,6 +170,15 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { 'Clear'.i18n: '0' }; + //default font family values + final fontFamilies = fontFamilyValues ?? + { + 'Sans Serif': 'sans-serif', + 'Serif': 'serif', + 'Monospace': 'monospace', + 'Clear': 'Clear' + }; + return QuillToolbar( key: key, toolbarHeight: toolbarIconSize * 2, @@ -189,6 +204,29 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { undo: false, iconTheme: iconTheme, ), + if (showFontFamily) + QuillFontFamilyButton( + iconTheme: iconTheme, + iconSize: toolbarIconSize, + attribute: Attribute.font, + controller: controller, + items: [ + for (MapEntry fontFamily in fontFamilies.entries) + PopupMenuItem( + key: ValueKey(fontFamily.key), + value: fontFamily.value, + child: Text(fontFamily.key.toString(), + style: TextStyle( + color: + fontFamily.value == 'Clear' ? Colors.red : null)), + ), + ], + onSelected: (newFont) { + controller.formatSelection(Attribute.fromKeyValue( + 'font', newFont == 'Clear' ? null : newFont)); + }, + rawItemsMap: fontFamilies, + ), if (showFontSize) QuillFontSizeButton( iconTheme: iconTheme, diff --git a/lib/src/widgets/toolbar/quill_font_family_button.dart b/lib/src/widgets/toolbar/quill_font_family_button.dart new file mode 100644 index 000000000..86c7c1a53 --- /dev/null +++ b/lib/src/widgets/toolbar/quill_font_family_button.dart @@ -0,0 +1,160 @@ +import 'package:flutter/material.dart'; + +import '../../models/documents/attribute.dart'; +import '../../models/documents/style.dart'; +import '../../models/themes/quill_icon_theme.dart'; +import '../../translations/toolbar.i18n.dart'; +import '../controller.dart'; + +class QuillFontFamilyButton extends StatefulWidget { + const QuillFontFamilyButton({ + required this.items, + required this.rawItemsMap, + required this.attribute, + required this.controller, + required this.onSelected, + this.iconSize = 40, + this.fillColor, + this.hoverElevation = 1, + this.highlightElevation = 1, + this.iconTheme, + Key? key, + }) : super(key: key); + + final double iconSize; + final Color? fillColor; + final double hoverElevation; + final double highlightElevation; + final List> items; + final Map rawItemsMap; + final ValueChanged onSelected; + final QuillIconTheme? iconTheme; + final Attribute attribute; + final QuillController controller; + + @override + _QuillFontFamilyButtonState createState() => _QuillFontFamilyButtonState(); +} + +class _QuillFontFamilyButtonState extends State { + late String _defaultDisplayText; + late String _currentValue; + Style get _selectionStyle => widget.controller.getSelectionStyle(); + + @override + void initState() { + super.initState(); + _currentValue = _defaultDisplayText = 'Font'.i18n; + widget.controller.addListener(_didChangeEditingValue); + } + + @override + void dispose() { + widget.controller.removeListener(_didChangeEditingValue); + super.dispose(); + } + + @override + void didUpdateWidget(covariant QuillFontFamilyButton oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.controller != widget.controller) { + oldWidget.controller.removeListener(_didChangeEditingValue); + widget.controller.addListener(_didChangeEditingValue); + } + } + + void _didChangeEditingValue() { + final attribute = _selectionStyle.attributes[widget.attribute.key]; + if (attribute == null) { + setState(() => _currentValue = _defaultDisplayText); + return; + } + final keyName = _getKeyName(attribute.value); + setState(() => _currentValue = keyName ?? _defaultDisplayText); + } + + String? _getKeyName(String value) { + for (final entry in widget.rawItemsMap.entries) { + if (entry.value == value) { + return entry.key; + } + } + return null; + } + + @override + Widget build(BuildContext context) { + return ConstrainedBox( + constraints: BoxConstraints.tightFor(height: widget.iconSize * 1.81), + child: RawMaterialButton( + visualDensity: VisualDensity.compact, + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(widget.iconTheme?.borderRadius ?? 2)), + fillColor: widget.fillColor, + elevation: 0, + hoverElevation: widget.hoverElevation, + highlightElevation: widget.hoverElevation, + onPressed: _showMenu, + child: _buildContent(context), + ), + ); + } + + void _showMenu() { + final popupMenuTheme = PopupMenuTheme.of(context); + final button = context.findRenderObject() as RenderBox; + final overlay = + Overlay.of(context)!.context.findRenderObject() as RenderBox; + final position = RelativeRect.fromRect( + Rect.fromPoints( + button.localToGlobal(Offset.zero, ancestor: overlay), + button.localToGlobal(button.size.bottomLeft(Offset.zero), + ancestor: overlay), + ), + Offset.zero & overlay.size, + ); + showMenu( + context: context, + elevation: 4, + items: widget.items, + position: position, + shape: popupMenuTheme.shape, + color: popupMenuTheme.color, + ).then((newValue) { + if (!mounted) return; + if (newValue == null) { + return; + } + final keyName = _getKeyName(newValue); + setState(() { + _currentValue = keyName ?? _defaultDisplayText; + if (keyName != null) { + widget.onSelected(newValue); + } + }); + }); + } + + Widget _buildContent(BuildContext context) { + final theme = Theme.of(context); + return Padding( + padding: const EdgeInsets.fromLTRB(10, 0, 0, 0), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text(_currentValue, + style: TextStyle( + fontSize: widget.iconSize / 1.15, + color: widget.iconTheme?.iconUnselectedColor ?? + theme.iconTheme.color)), + const SizedBox(width: 3), + Icon(Icons.arrow_drop_down, + size: widget.iconSize / 1.15, + color: widget.iconTheme?.iconUnselectedColor ?? + theme.iconTheme.color) + ], + ), + ); + } +} diff --git a/lib/src/widgets/toolbar/quill_font_size_button.dart b/lib/src/widgets/toolbar/quill_font_size_button.dart index 553792654..957f56d96 100644 --- a/lib/src/widgets/toolbar/quill_font_size_button.dart +++ b/lib/src/widgets/toolbar/quill_font_size_button.dart @@ -65,9 +65,6 @@ class _QuillFontSizeButtonState extends State { } void _didChangeEditingValue() { - if (widget.attribute.key != Attribute.size.key) { - return; - } final attribute = _selectionStyle.attributes[widget.attribute.key]; if (attribute == null) { setState(() => _currentValue = _defaultDisplayText);