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.
 
 
 
 
 

319 lines
9.7 KiB

import 'package:flutter/material.dart';
import '../../../../extensions.dart';
import '../../../extensions/quill_configurations_ext.dart';
import '../../../l10n/extensions/localizations.dart';
import '../../../models/documents/attribute.dart';
import '../../../models/themes/quill_icon_theme.dart';
import '../../quill/quill_controller.dart';
import '../base_toolbar.dart';
class QuillToolbarFontFamilyButton extends StatefulWidget {
QuillToolbarFontFamilyButton({
required this.controller,
required this.defaultDisplayText,
this.options = const QuillToolbarFontFamilyButtonOptions(),
super.key,
}) : assert(options.rawItemsMap?.isNotEmpty ?? (true)),
assert(
options.initialValue == null || options.initialValue!.isNotEmpty,
);
final QuillToolbarFontFamilyButtonOptions options;
@Deprecated('Please use the default display text from the options')
final String defaultDisplayText;
/// Since we can't get the state from the instace of the widget for comparing
/// in [didUpdateWidget] then we will have to store reference here
final QuillController controller;
@override
QuillToolbarFontFamilyButtonState createState() =>
QuillToolbarFontFamilyButtonState();
}
class QuillToolbarFontFamilyButtonState
extends State<QuillToolbarFontFamilyButton> {
var _currentValue = '';
QuillToolbarFontFamilyButtonOptions get options {
return widget.options;
}
// Style get _selectionStyle => controller.getSelectionStyle();
@override
void initState() {
super.initState();
_initState();
}
void _initState() {
_currentValue = _defaultDisplayText;
// controller.addListener(_didChangeEditingValue);
}
// @override
// void dispose() {
// controller.removeListener(_didChangeEditingValue);
// super.dispose();
// }
String get _defaultDisplayText {
return options.initialValue ??
widget.options.defaultDisplayText ??
// ignore: deprecated_member_use_from_same_package
widget.defaultDisplayText;
}
// @override
// void didUpdateWidget(covariant QuillToolbarFontFamilyButton oldWidget) {
// super.didUpdateWidget(oldWidget);
// if (oldWidget.controller == controller) {
// return;
// }
// controller
// ..removeListener(_didChangeEditingValue)
// ..addListener(_didChangeEditingValue);
// }
// void _didChangeEditingValue() {
// final attribute = _selectionStyle.attributes[options.attribute.key];
// if (attribute == null) {
// setState(() => _currentValue = _defaultDisplayText);
// return;
// }
// final keyName = _getKeyName(attribute.value);
// setState(() => _currentValue = keyName ?? _defaultDisplayText);
// }
Map<String, String> get rawItemsMap {
final rawItemsMap =
context.quillSimpleToolbarConfigurations?.fontFamilyValues ??
options.rawItemsMap ??
{
'Sans Serif': 'sans-serif',
'Serif': 'serif',
'Monospace': 'monospace',
'Ibarra Real Nova': 'ibarra-real-nova',
'SquarePeg': 'square-peg',
'Nunito': 'nunito',
'Pacifico': 'pacifico',
'Roboto Mono': 'roboto-mono',
context.loc.clear: 'Clear'
};
return rawItemsMap;
}
String? _getKeyName(String value) {
for (final entry in rawItemsMap.entries) {
if (entry.value == value) {
return entry.key;
}
}
return null;
}
QuillController get controller {
return widget.controller;
}
double get iconSize {
final baseFontSize = context.quillToolbarBaseButtonOptions?.globalIconSize;
final iconSize = options.iconSize;
return iconSize ?? baseFontSize ?? kDefaultIconSize;
}
VoidCallback? get afterButtonPressed {
return options.afterButtonPressed ??
context.quillToolbarBaseButtonOptions?.afterButtonPressed;
}
QuillIconTheme? get iconTheme {
return options.iconTheme ??
context.quillToolbarBaseButtonOptions?.iconTheme;
}
String get tooltip {
return options.tooltip ??
context.quillToolbarBaseButtonOptions?.tooltip ??
context.loc.fontFamily;
}
void _onPressed() {
_showMenu();
options.afterButtonPressed?.call();
}
@override
Widget build(BuildContext context) {
final baseButtonConfigurations = context.quillToolbarBaseButtonOptions;
final childBuilder =
options.childBuilder ?? baseButtonConfigurations?.childBuilder;
if (childBuilder != null) {
return childBuilder(
options.copyWith(
iconSize: iconSize,
rawItemsMap: rawItemsMap,
iconTheme: iconTheme,
tooltip: tooltip,
afterButtonPressed: afterButtonPressed,
),
QuillToolbarFontFamilyButtonExtraOptions(
currentValue: _currentValue,
defaultDisplayText: _defaultDisplayText,
controller: controller,
context: context,
onPressed: _onPressed,
),
);
}
return ConstrainedBox(
constraints: BoxConstraints.tightFor(
height: iconSize * 1.81,
width: options.width,
),
child: UtilityWidgets.maybeWidget(
enabled: tooltip.isNotEmpty || options.overrideTooltipByFontFamily,
wrapper: (child) {
var effectiveTooltip = tooltip;
if (options.overrideTooltipByFontFamily) {
effectiveTooltip = effectiveTooltip.isNotEmpty
? '$effectiveTooltip: $_currentValue'
: '${context.loc.font}: $_currentValue';
}
return Tooltip(message: effectiveTooltip, child: child);
},
child: Builder(
builder: (context) {
final isMaterial3 = Theme.of(context).useMaterial3;
if (!isMaterial3) {
return RawMaterialButton(
onPressed: _onPressed,
child: _buildContent(context),
);
}
return QuillToolbarIconButton(
isSelected: false,
iconTheme: iconTheme?.copyWith(
iconButtonSelectedData: const IconButtonData(
visualDensity: VisualDensity.compact,
),
iconButtonUnselectedData: const IconButtonData(
visualDensity: VisualDensity.compact,
),
),
onPressed: _onPressed,
icon: _buildContent(context),
);
},
),
),
);
}
Future<void> _showMenu() async {
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,
);
final newValue = await showMenu<String>(
context: context,
elevation: 4,
items: [
for (final MapEntry<String, String> fontFamily in rawItemsMap.entries)
PopupMenuItem<String>(
key: ValueKey(fontFamily.key),
value: fontFamily.value,
height: options.itemHeight ?? kMinInteractiveDimension,
padding: options.itemPadding,
onTap: () {
if (fontFamily.value == 'Clear') {
controller.selectFontFamily(null);
return;
}
controller.selectFontFamily(fontFamily.value);
},
child: Text(
fontFamily.key.toString(),
style: TextStyle(
fontFamily:
options.renderFontFamilies ? fontFamily.value : null,
color: fontFamily.value == 'Clear'
? options.defaultItemColor
: null,
),
),
),
],
position: position,
shape: popupMenuTheme.shape,
color: popupMenuTheme.color,
);
if (!mounted) {
return;
}
if (newValue == null) {
return;
}
final keyName = _getKeyName(newValue);
setState(() {
if (keyName != 'Clear') {
_currentValue = keyName ?? _defaultDisplayText;
} else {
_currentValue = _defaultDisplayText;
}
if (keyName != null) {
controller.formatSelection(
Attribute.fromKeyValue(
Attribute.font.key,
newValue == 'Clear' ? null : newValue,
),
);
options.onSelected?.call(newValue);
}
});
}
Widget _buildContent(BuildContext context) {
final hasFinalWidth = options.width != null;
return Padding(
padding: options.padding ?? const EdgeInsets.fromLTRB(10, 0, 0, 0),
child: Row(
mainAxisSize: !hasFinalWidth ? MainAxisSize.min : MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
UtilityWidgets.maybeWidget(
enabled: hasFinalWidth,
wrapper: (child) => Expanded(child: child),
child: Text(
widget.controller.selectedFontFamily ?? _currentValue,
maxLines: 1,
overflow: options.labelOverflow,
style: options.style ??
TextStyle(
fontSize: iconSize / 1.15,
// color: iconTheme?.iconUnselectedFillColor ??
// theme.iconTheme.color,
),
),
),
const SizedBox(width: 3),
Icon(
Icons.arrow_drop_down,
size: iconSize / 1.15,
// color: iconTheme?.iconUnselectedFillColor ?? theme.iconTheme.color,
)
],
),
);
}
}