Support font family selection

pull/882/head
X Code 3 years ago
parent 6a462eee9c
commit 71d06f6b7b
  1. 23
      lib/src/translations/toolbar.i18n.dart
  2. 40
      lib/src/widgets/toolbar.dart
  3. 160
      lib/src/widgets/toolbar/quill_font_family_button.dart
  4. 3
      lib/src/widgets/toolbar/quill_font_size_button.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',
},
};

@ -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<String, String>? fontSizeValues,
///Map of font families in string
Map<String, String>? 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<String, String> fontFamily in fontFamilies.entries)
PopupMenuItem<String>(
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,

@ -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<PopupMenuEntry<String>> items;
final Map<String, String> rawItemsMap;
final ValueChanged<String> onSelected;
final QuillIconTheme? iconTheme;
final Attribute attribute;
final QuillController controller;
@override
_QuillFontFamilyButtonState createState() => _QuillFontFamilyButtonState();
}
class _QuillFontFamilyButtonState extends State<QuillFontFamilyButton> {
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<String>(
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)
],
),
);
}
}

@ -65,9 +65,6 @@ class _QuillFontSizeButtonState extends State<QuillFontSizeButton> {
}
void _didChangeEditingValue() {
if (widget.attribute.key != Attribute.size.key) {
return;
}
final attribute = _selectionStyle.attributes[widget.attribute.key];
if (attribute == null) {
setState(() => _currentValue = _defaultDisplayText);

Loading…
Cancel
Save