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.
 
 
 
 
 

278 lines
8.4 KiB

import 'package:flutter/material.dart';
import '../../../../flutter_quill.dart';
import '../../../../translations.dart';
import '../../../utils/widgets.dart';
class QuillToolbarSelectHeaderStyleDropdownButton extends StatefulWidget {
const QuillToolbarSelectHeaderStyleDropdownButton({
required this.controller,
required this.options,
super.key,
});
/// 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;
final QuillToolbarSelectHeaderStyleDropdownButtonOptions options;
@override
State<QuillToolbarSelectHeaderStyleDropdownButton> createState() =>
_QuillToolbarSelectHeaderStyleDropdownButtonState();
}
class _QuillToolbarSelectHeaderStyleDropdownButtonState
extends State<QuillToolbarSelectHeaderStyleDropdownButton> {
Attribute? _selectedAttribute;
Style get _selectionStyle => controller.getSelectionStyle();
late final _valueToText = <Attribute, String>{
Attribute.header: context.loc.normal,
Attribute.h1: context.loc.heading1,
Attribute.h2: context.loc.heading2,
Attribute.h3: context.loc.heading3,
Attribute.h4: context.loc.heading4,
Attribute.h5: context.loc.heading5,
Attribute.h6: context.loc.heading6,
};
Map<Attribute, TextStyle>? _headerTextStyles;
QuillToolbarSelectHeaderStyleDropdownButtonOptions get options {
return widget.options;
}
QuillController get controller {
return widget.controller;
}
double get iconSize {
final baseFontSize = baseButtonExtraOptions.globalIconSize;
final iconSize = options.iconSize;
return iconSize ?? baseFontSize;
}
double get iconButtonFactor {
final baseIconFactor = baseButtonExtraOptions.globalIconButtonFactor;
final iconButtonFactor = options.iconButtonFactor;
return iconButtonFactor ?? baseIconFactor;
}
VoidCallback? get afterButtonPressed {
return options.afterButtonPressed ??
baseButtonExtraOptions.afterButtonPressed;
}
QuillIconTheme? get iconTheme {
return options.iconTheme ?? baseButtonExtraOptions.iconTheme;
}
QuillToolbarBaseButtonOptions get baseButtonExtraOptions {
return context.requireQuillToolbarBaseButtonOptions;
}
String get tooltip {
return options.tooltip ??
baseButtonExtraOptions.tooltip ??
context.loc.headerStyle;
}
List<Attribute> get _attrbuites {
return options.attributes ?? _valueToText.keys.toList();
}
@override
void dispose() {
controller.removeListener(_didChangeEditingValue);
super.dispose();
}
@override
void didUpdateWidget(
covariant QuillToolbarSelectHeaderStyleDropdownButton oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.controller != controller) {
oldWidget.controller.removeListener(_didChangeEditingValue);
controller.addListener(_didChangeEditingValue);
_selectedAttribute = _getHeaderValue();
}
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
if (_headerTextStyles == null) {
final defaultStyles = QuillStyles.getStyles(context, false);
_headerTextStyles = {
Attribute.h1: defaultStyles!.h1!.style,
Attribute.h2: defaultStyles.h2!.style,
Attribute.h3: defaultStyles.h3!.style,
Attribute.h4: defaultStyles.h4!.style,
Attribute.h5: defaultStyles.h5!.style,
Attribute.h6: defaultStyles.h6!.style,
};
}
}
@override
void initState() {
super.initState();
controller.addListener(_didChangeEditingValue);
_selectedAttribute = _getHeaderValue();
}
@override
Widget build(BuildContext context) {
assert(_attrbuites.every((element) => _valueToText.keys.contains(element)));
final baseButtonConfigurations =
context.requireQuillToolbarBaseButtonOptions;
final childBuilder =
options.childBuilder ?? baseButtonConfigurations.childBuilder;
if (childBuilder != null) {
return childBuilder(
options.copyWith(
iconSize: iconSize,
iconTheme: iconTheme,
tooltip: tooltip,
afterButtonPressed: afterButtonPressed,
),
QuillToolbarSelectHeaderStyleDropdownButtonExtraOptions(
currentValue: _selectedAttribute!,
controller: controller,
context: context,
onPressed: _onPressed,
),
);
}
return ConstrainedBox(
constraints: BoxConstraints.tightFor(
height: iconSize * 1.81,
width: options.width,
),
child: UtilityWidgets.maybeTooltip(
message: tooltip,
child: RawMaterialButton(
visualDensity: VisualDensity.compact,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(iconTheme?.borderRadius ?? 2),
),
fillColor: options.fillColor,
elevation: 0,
hoverElevation: options.hoverElevation,
highlightElevation: options.hoverElevation,
onPressed: _onPressed,
child: _buildContent(context),
),
),
);
}
void _didChangeEditingValue() {
setState(() {
_selectedAttribute = _getHeaderValue();
});
}
Attribute<dynamic> _getHeaderValue() {
final attr = controller.toolbarButtonToggler[Attribute.header.key];
if (attr != null) {
// checkbox tapping causes controller.selection to go to offset 0
controller.toolbarButtonToggler.remove(Attribute.header.key);
return attr;
}
return _selectionStyle.attributes[Attribute.header.key] ?? Attribute.header;
}
Widget _buildContent(BuildContext context) {
final theme = Theme.of(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(
_valueToText[_selectedAttribute]!,
overflow: options.labelOverflow,
style: options.style ??
TextStyle(
fontSize: iconSize / 1.15,
color:
iconTheme?.iconUnselectedColor ?? theme.iconTheme.color,
),
),
),
const SizedBox(width: 3),
Icon(
Icons.arrow_drop_down,
size: iconSize / 1.15,
color: iconTheme?.iconUnselectedColor ?? theme.iconTheme.color,
)
],
),
);
}
void _onPressed() {
_showMenu();
options.afterButtonPressed?.call();
}
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<Attribute>(
context: context,
elevation: 4,
items: [
for (final header in _valueToText.entries)
PopupMenuItem<Attribute>(
key: ValueKey(header.value),
value: header.key,
height: options.itemHeight ?? kMinInteractiveDimension,
padding: options.itemPadding,
child: Text(
header.value,
style: TextStyle(
fontSize: options.renderItemTextStyle
? _headerTextStyles![header.key]!.fontSize ??
DefaultTextStyle.of(context).style.fontSize ??
14
: null,
color: header.key == _selectedAttribute
? options.defaultItemColor
: null,
),
),
),
],
position: position,
shape: popupMenuTheme.shape,
color: popupMenuTheme.color,
);
if (newValue == null) {
return;
}
final attribute0 =
_selectedAttribute == newValue ? Attribute.header : newValue;
controller.formatSelection(attribute0);
afterButtonPressed?.call();
}
}