diff --git a/lib/src/widgets/toolbar/buttons/dropdown_header_style.dart b/lib/src/widgets/toolbar/buttons/dropdown_header_style.dart index afcba118..6eb92ab1 100644 --- a/lib/src/widgets/toolbar/buttons/dropdown_header_style.dart +++ b/lib/src/widgets/toolbar/buttons/dropdown_header_style.dart @@ -1,8 +1,118 @@ -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import '../../../../flutter_quill.dart'; import '../../../../translations.dart'; +import '../../../utils/widgets.dart'; + +class QuillToolbarSelectHeaderStyleDropdownButtonExtraOptions + extends QuillToolbarBaseButtonExtraOptions { + const QuillToolbarSelectHeaderStyleDropdownButtonExtraOptions({ + required this.currentValue, + required super.controller, + required super.context, + required super.onPressed, + }); + final Attribute currentValue; +} + +class QuillToolbarSelectHeaderStyleDropdownButtonOptions + extends QuillToolbarBaseButtonOptions< + QuillToolbarSelectHeaderStyleDropdownButtonOptions, + QuillToolbarSelectHeaderStyleDropdownButtonExtraOptions> { + const QuillToolbarSelectHeaderStyleDropdownButtonOptions({ + super.controller, + super.iconData, + super.afterButtonPressed, + super.tooltip, + super.iconTheme, + super.childBuilder, + this.iconSize, + this.iconButtonFactor, + this.fillColor, + this.hoverElevation = 1, + this.highlightElevation = 1, + this.rawItemsMap, + this.onSelected, + this.attributes, + this.padding, + this.style, + this.width, + this.initialValue, + this.labelOverflow = TextOverflow.visible, + this.itemHeight, + this.itemPadding, + this.defaultItemColor, + }); + + final double? iconSize; + final double? iconButtonFactor; + final Color? fillColor; + final double hoverElevation; + final double highlightElevation; + final Map? rawItemsMap; + final ValueChanged? onSelected; + final List? attributes; + final EdgeInsetsGeometry? padding; + final TextStyle? style; + final double? width; + final String? initialValue; + final TextOverflow labelOverflow; + final double? itemHeight; + final EdgeInsets? itemPadding; + final Color? defaultItemColor; + + QuillToolbarSelectHeaderStyleDropdownButtonOptions copyWith({ + Color? fillColor, + double? hoverElevation, + double? highlightElevation, + List>? items, + Map? rawItemsMap, + ValueChanged? onSelected, + List? attributes, + EdgeInsetsGeometry? padding, + TextStyle? style, + double? width, + String? initialValue, + TextOverflow? labelOverflow, + bool? renderFontFamilies, + bool? overrideTooltipByFontFamily, + double? itemHeight, + EdgeInsets? itemPadding, + Color? defaultItemColor, + double? iconSize, + double? iconButtonFactor, + // Add properties to override inherited properties + QuillController? controller, + IconData? iconData, + VoidCallback? afterButtonPressed, + String? tooltip, + QuillIconTheme? iconTheme, + }) { + return QuillToolbarSelectHeaderStyleDropdownButtonOptions( + attributes: attributes ?? this.attributes, + rawItemsMap: rawItemsMap ?? this.rawItemsMap, + controller: controller ?? this.controller, + iconData: iconData ?? this.iconData, + afterButtonPressed: afterButtonPressed ?? this.afterButtonPressed, + tooltip: tooltip ?? this.tooltip, + iconTheme: iconTheme ?? this.iconTheme, + onSelected: onSelected ?? this.onSelected, + padding: padding ?? this.padding, + style: style ?? this.style, + width: width ?? this.width, + initialValue: initialValue ?? this.initialValue, + labelOverflow: labelOverflow ?? this.labelOverflow, + itemHeight: itemHeight ?? this.itemHeight, + itemPadding: itemPadding ?? this.itemPadding, + defaultItemColor: defaultItemColor ?? this.defaultItemColor, + iconSize: iconSize ?? this.iconSize, + iconButtonFactor: iconButtonFactor ?? this.iconButtonFactor, + fillColor: fillColor ?? this.fillColor, + hoverElevation: hoverElevation ?? this.hoverElevation, + highlightElevation: highlightElevation ?? this.highlightElevation, + ); + } +} class QuillToolbarSelectHeaderStyleDropdownButton extends StatefulWidget { const QuillToolbarSelectHeaderStyleDropdownButton({ @@ -11,8 +121,10 @@ class QuillToolbarSelectHeaderStyleDropdownButton extends StatefulWidget { 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 QuillToolbarSelectHeaderStyleButtonsOptions options; + final QuillToolbarSelectHeaderStyleDropdownButtonOptions options; @override State createState() => @@ -26,13 +138,13 @@ class _QuillToolbarSelectHeaderStyleDropdownButtonState Style get _selectionStyle => controller.getSelectionStyle(); final _valueToText = { - Attribute.header: 'N', + Attribute.header: 'Normal', Attribute.h1: 'H1', Attribute.h2: 'H2', Attribute.h3: 'H3', }; - QuillToolbarSelectHeaderStyleButtonsOptions get options { + QuillToolbarSelectHeaderStyleDropdownButtonOptions get options { return widget.options; } @@ -71,14 +183,6 @@ class _QuillToolbarSelectHeaderStyleDropdownButtonState context.loc.headerStyle; } - Axis get axis { - return options.axis ?? - context.quillToolbarConfigurations?.axis ?? - context.quillBaseToolbarConfigurations?.axis ?? - (throw ArgumentError( - 'There is no default value for the Axis of the toolbar')); - } - List get _attrbuites { return options.attributes ?? const [ @@ -109,9 +213,7 @@ class _QuillToolbarSelectHeaderStyleDropdownButtonState @override void initState() { super.initState(); - setState(() { - _selectedAttribute = _getHeaderValue(); - }); + _selectedAttribute = _getHeaderValue(); controller.addListener(_didChangeEditingValue); } @@ -124,76 +226,45 @@ class _QuillToolbarSelectHeaderStyleDropdownButtonState 'All attributes must be one of them: header, h1, h2 or h3', ); - final style = TextStyle( - fontWeight: FontWeight.w600, - fontSize: iconSize * 0.7, - ); - + final baseButtonConfigurations = + context.requireQuillToolbarBaseButtonOptions; final childBuilder = - options.childBuilder ?? baseButtonExtraOptions.childBuilder; - - for (final attribute in _attrbuites) { - if (childBuilder != null) { - return childBuilder( - QuillToolbarSelectHeaderStyleButtonsOptions( - afterButtonPressed: afterButtonPressed, - attributes: _attrbuites, - axis: axis, - iconSize: iconSize, - iconButtonFactor: iconButtonFactor, - iconTheme: iconTheme, - tooltip: tooltip, - ), - QuillToolbarSelectHeaderStyleButtonExtraOptions( - controller: controller, - context: context, - onPressed: () => _sharedOnPressed(attribute), - ), - ); - } + 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, + ), + ); } - final theme = Theme.of(context); - return ConstrainedBox( constraints: BoxConstraints.tightFor( - width: iconSize * iconButtonFactor, - height: iconSize * iconButtonFactor, + height: iconSize * 1.81, + width: options.width, ), - child: Tooltip( + child: UtilityWidgets.maybeTooltip( message: tooltip, - child: DropdownButtonHideUnderline( - child: DropdownButton( - value: _selectedAttribute, - items: _valueToText.entries - .map((header) => DropdownMenuItem( - value: header.key, - child: Text( - header.value, - style: style, - ), - )) - .toList(), - selectedItemBuilder: (context) => - _valueToText.entries.map((header) { - final isSelected = _selectedAttribute == header.key; - return Text( - header.value, - style: style.copyWith( - color: isSelected - ? (iconTheme?.iconSelectedColor ?? - theme.primaryIconTheme.color) - : (iconTheme?.iconUnselectedColor ?? - theme.iconTheme.color), - ), - ); - }).toList(), - elevation: 0, + child: RawMaterialButton( + visualDensity: VisualDensity.compact, + shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(iconTheme?.borderRadius ?? 2), - padding: - const EdgeInsets.symmetric(horizontal: !kIsWeb ? 1.0 : 5.0), - onChanged: (attribute) => _sharedOnPressed(attribute!), ), + fillColor: options.fillColor, + elevation: 0, + hoverElevation: options.hoverElevation, + highlightElevation: options.hoverElevation, + onPressed: _onPressed, + child: _buildContent(context), ), ), ); @@ -215,9 +286,85 @@ class _QuillToolbarSelectHeaderStyleDropdownButtonState return _selectionStyle.attributes[Attribute.header.key] ?? Attribute.header; } - void _sharedOnPressed(Attribute attribute) { + 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( + _selectedAttribute!.key, + 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 _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( + context: context, + elevation: 4, + items: [ + for (final header in _valueToText.entries) + PopupMenuItem( + key: ValueKey(header.value), + value: header.key, + height: options.itemHeight ?? kMinInteractiveDimension, + padding: options.itemPadding, + child: Text( + header.value, + style: TextStyle( + color: header.value == 'N' ? options.defaultItemColor : null, + ), + ), + ), + ], + position: position, + shape: popupMenuTheme.shape, + color: popupMenuTheme.color, + ); + if (newValue == null) { + return; + } + final attribute0 = - _selectedAttribute == attribute ? Attribute.header : attribute; + _selectedAttribute == newValue ? Attribute.header : newValue; controller.formatSelection(attribute0); afterButtonPressed?.call(); }