Major update 2 (#1446)
parent
a73fca1f76
commit
3a82930bbe
49 changed files with 1104 additions and 589 deletions
@ -0,0 +1,23 @@ |
|||||||
|
#!/bin/bash |
||||||
|
|
||||||
|
# Run Flutter analyze |
||||||
|
echo "Running 'flutter analyze'..." |
||||||
|
flutter analyze |
||||||
|
|
||||||
|
# Run Flutter test |
||||||
|
echo "Running 'flutter test'..." |
||||||
|
flutter test |
||||||
|
|
||||||
|
# Check if package is ready for publishing |
||||||
|
echo "Running 'flutter pub publish --dry-run'..." |
||||||
|
flutter pub publish --dry-run |
||||||
|
|
||||||
|
# Apply Dart fixes |
||||||
|
echo "Running 'dart fix --apply'..." |
||||||
|
dart fix --apply |
||||||
|
|
||||||
|
# Format Dart code |
||||||
|
echo "Running 'dart format .'" |
||||||
|
dart format . |
||||||
|
|
||||||
|
echo "Script completed." |
@ -0,0 +1,21 @@ |
|||||||
|
import 'package:flutter/foundation.dart' show immutable; |
||||||
|
|
||||||
|
/// The configurations for the quill editor widget of flutter quill |
||||||
|
@immutable |
||||||
|
class QuillEditorConfigurations { |
||||||
|
const QuillEditorConfigurations({ |
||||||
|
this.placeholder, |
||||||
|
this.readOnly = false, |
||||||
|
}); |
||||||
|
|
||||||
|
/// The text placeholder in the quill editor |
||||||
|
final String? placeholder; |
||||||
|
|
||||||
|
/// Whether the text can be changed. |
||||||
|
/// |
||||||
|
/// When this is set to `true`, the text cannot be modified |
||||||
|
/// by any shortcut or keyboard operation. The text is still selectable. |
||||||
|
/// |
||||||
|
/// Defaults to `false`. Must not be `null`. |
||||||
|
final bool readOnly; |
||||||
|
} |
@ -0,0 +1,21 @@ |
|||||||
|
import 'package:flutter/material.dart' show Color, Colors, Locale; |
||||||
|
import './editor/configurations.dart' show QuillEditorConfigurations; |
||||||
|
import './toolbar/configurations.dart' show QuillToolbarConfigurations; |
||||||
|
|
||||||
|
/// The shared configurations between [QuillEditorConfigurations] and |
||||||
|
/// [QuillToolbarConfigurations] so we don't duplicate things |
||||||
|
class QuillSharedConfigurations { |
||||||
|
const QuillSharedConfigurations({ |
||||||
|
this.dialogBarrierColor = Colors.black54, |
||||||
|
this.locale, |
||||||
|
}); |
||||||
|
|
||||||
|
// This is just example or showcase of this major update to make the library |
||||||
|
// more maintanable, flexible, and customizable |
||||||
|
/// The barrier color of the shown dialogs |
||||||
|
final Color dialogBarrierColor; |
||||||
|
|
||||||
|
/// The locale to use for the editor and toolbar, defaults to system locale |
||||||
|
/// More https://github.com/singerdmx/flutter-quill#translation |
||||||
|
final Locale? locale; |
||||||
|
} |
@ -0,0 +1,48 @@ |
|||||||
|
import 'package:flutter/foundation.dart' show VoidCallback, immutable; |
||||||
|
import 'package:flutter/widgets.dart' show IconData, Widget; |
||||||
|
|
||||||
|
import '../../../../../flutter_quill.dart' show QuillController, QuillProvider; |
||||||
|
import '../../../themes/quill_icon_theme.dart' show QuillIconTheme; |
||||||
|
import '../../quill_configurations.dart' show kDefaultIconSize; |
||||||
|
|
||||||
|
/// The [T] is the options for the button, usually should refresnce itself |
||||||
|
/// it's used in [childBuilder] so the developer can custmize this when using it |
||||||
|
/// The [I] is extra options for the button, usually for it's state |
||||||
|
@immutable |
||||||
|
class QuillToolbarBaseButtonOptions<T, I> { |
||||||
|
const QuillToolbarBaseButtonOptions({ |
||||||
|
this.iconData, |
||||||
|
this.globalIconSize = kDefaultIconSize, |
||||||
|
this.afterButtonPressed, |
||||||
|
this.tooltip, |
||||||
|
this.iconTheme, |
||||||
|
this.childBuilder, |
||||||
|
this.controller, |
||||||
|
}); |
||||||
|
|
||||||
|
/// By default it will use a Icon data from Icons which comes from material |
||||||
|
/// library, to change this, please pass a different value |
||||||
|
/// If there is no Icon in this button then pass null in the child class |
||||||
|
final IconData? iconData; |
||||||
|
|
||||||
|
/// To change the the icon size pass a different value, by default will be |
||||||
|
/// [kDefaultIconSize] |
||||||
|
/// this will be used for all the buttons but you can override this |
||||||
|
final double globalIconSize; |
||||||
|
|
||||||
|
/// To do extra logic after pressing the button |
||||||
|
final VoidCallback? afterButtonPressed; |
||||||
|
|
||||||
|
/// By default it will use the default tooltip which already localized |
||||||
|
final String? tooltip; |
||||||
|
|
||||||
|
/// Use custom theme |
||||||
|
final QuillIconTheme? iconTheme; |
||||||
|
|
||||||
|
/// If you want to dispaly a differnet widget based using a builder |
||||||
|
final Widget Function(T options, I extraOptions)? childBuilder; |
||||||
|
|
||||||
|
/// By default it will be from the one in [QuillProvider] |
||||||
|
/// To override it you must pass not null controller |
||||||
|
final QuillController? controller; |
||||||
|
} |
@ -0,0 +1,79 @@ |
|||||||
|
import 'package:flutter/foundation.dart' show immutable; |
||||||
|
import 'package:flutter/material.dart' show Colors, PopupMenuEntry; |
||||||
|
import 'package:flutter/widgets.dart' |
||||||
|
show |
||||||
|
Color, |
||||||
|
ValueChanged, |
||||||
|
EdgeInsetsGeometry, |
||||||
|
TextStyle, |
||||||
|
EdgeInsets, |
||||||
|
TextOverflow; |
||||||
|
|
||||||
|
import '../../../../../flutter_quill.dart'; |
||||||
|
|
||||||
|
@immutable |
||||||
|
class QuillToolbarFontFamilyButtonExtraOptions { |
||||||
|
const QuillToolbarFontFamilyButtonExtraOptions({ |
||||||
|
required this.defaultDisplayText, |
||||||
|
required this.currentValue, |
||||||
|
}); |
||||||
|
final String defaultDisplayText; |
||||||
|
final String currentValue; |
||||||
|
} |
||||||
|
|
||||||
|
class QuillToolbarFontFamilyButtonOptions extends QuillToolbarBaseButtonOptions< |
||||||
|
QuillToolbarFontFamilyButtonOptions, |
||||||
|
QuillToolbarFontFamilyButtonExtraOptions> { |
||||||
|
const QuillToolbarFontFamilyButtonOptions({ |
||||||
|
required this.attribute, |
||||||
|
this.rawItemsMap, |
||||||
|
super.controller, |
||||||
|
super.iconData, |
||||||
|
super.afterButtonPressed, |
||||||
|
super.tooltip, |
||||||
|
super.iconTheme, |
||||||
|
super.childBuilder, |
||||||
|
this.onSelected, |
||||||
|
this.padding, |
||||||
|
this.style, |
||||||
|
this.width, |
||||||
|
this.initialValue, |
||||||
|
this.labelOverflow = TextOverflow.visible, |
||||||
|
this.overrideTooltipByFontFamily = false, |
||||||
|
this.itemHeight, |
||||||
|
this.itemPadding, |
||||||
|
this.defaultItemColor = Colors.red, |
||||||
|
this.renderFontFamilies = true, |
||||||
|
@Deprecated('It is not required because of `rawItemsMap`') this.items, |
||||||
|
this.highlightElevation = 1, |
||||||
|
this.hoverElevation = 1, |
||||||
|
this.fillColor, |
||||||
|
this.iconSize, |
||||||
|
}); |
||||||
|
|
||||||
|
final Color? fillColor; |
||||||
|
final double hoverElevation; |
||||||
|
final double highlightElevation; |
||||||
|
@Deprecated('It is not required because of `rawItemsMap`') |
||||||
|
final List<PopupMenuEntry<String>>? items; |
||||||
|
|
||||||
|
/// By default it will be [fontFamilyValues] from [QuillToolbarConfigurations] |
||||||
|
/// You can override this if you want |
||||||
|
final Map<String, String>? rawItemsMap; |
||||||
|
final ValueChanged<String>? onSelected; |
||||||
|
final Attribute attribute; |
||||||
|
|
||||||
|
final EdgeInsetsGeometry? padding; |
||||||
|
final TextStyle? style; |
||||||
|
final double? width; |
||||||
|
final bool renderFontFamilies; |
||||||
|
final String? initialValue; |
||||||
|
final TextOverflow labelOverflow; |
||||||
|
final bool overrideTooltipByFontFamily; |
||||||
|
final double? itemHeight; |
||||||
|
final EdgeInsets? itemPadding; |
||||||
|
final Color? defaultItemColor; |
||||||
|
|
||||||
|
/// By default will use [globalIconSize] |
||||||
|
final double? iconSize; |
||||||
|
} |
@ -0,0 +1,39 @@ |
|||||||
|
import 'package:flutter/foundation.dart' show VoidCallback, immutable; |
||||||
|
|
||||||
|
import '../../../../../flutter_quill.dart'; |
||||||
|
|
||||||
|
@immutable |
||||||
|
class HistoryButtonExtraOptions { |
||||||
|
const HistoryButtonExtraOptions({ |
||||||
|
required this.onPressed, |
||||||
|
required this.canPressed, |
||||||
|
}); |
||||||
|
|
||||||
|
/// When the button pressed |
||||||
|
final VoidCallback onPressed; |
||||||
|
|
||||||
|
/// If it can redo or undo |
||||||
|
final bool canPressed; |
||||||
|
} |
||||||
|
|
||||||
|
@immutable |
||||||
|
class QuillToolbarHistoryButtonOptions extends QuillToolbarBaseButtonOptions< |
||||||
|
QuillToolbarHistoryButtonOptions, HistoryButtonExtraOptions> { |
||||||
|
const QuillToolbarHistoryButtonOptions({ |
||||||
|
required this.isUndo, |
||||||
|
super.iconData, |
||||||
|
super.controller, |
||||||
|
super.iconTheme, |
||||||
|
super.afterButtonPressed, |
||||||
|
super.tooltip, |
||||||
|
super.childBuilder, |
||||||
|
this.iconSize, |
||||||
|
}); |
||||||
|
|
||||||
|
/// If this true then it will be the undo button |
||||||
|
/// otherwise it will be redo |
||||||
|
final bool isUndo; |
||||||
|
|
||||||
|
/// By default will use [globalIconSize] |
||||||
|
final double? iconSize; |
||||||
|
} |
@ -0,0 +1,15 @@ |
|||||||
|
import 'package:flutter/foundation.dart' show immutable; |
||||||
|
import 'package:flutter/widgets.dart' show IconData; |
||||||
|
|
||||||
|
import 'base.dart'; |
||||||
|
|
||||||
|
@immutable |
||||||
|
class QuillToolbarToggleStyleButtonOptions |
||||||
|
extends QuillToolbarBaseButtonOptions { |
||||||
|
const QuillToolbarToggleStyleButtonOptions({ |
||||||
|
required this.iconData, |
||||||
|
}); |
||||||
|
|
||||||
|
@override |
||||||
|
final IconData iconData; |
||||||
|
} |
@ -0,0 +1,93 @@ |
|||||||
|
import 'package:flutter/foundation.dart' show immutable; |
||||||
|
|
||||||
|
import '../../documents/attribute.dart'; |
||||||
|
import 'buttons/base.dart'; |
||||||
|
import 'buttons/font_family.dart'; |
||||||
|
import 'buttons/history.dart'; |
||||||
|
|
||||||
|
export './buttons/base.dart'; |
||||||
|
export './buttons/history.dart'; |
||||||
|
export './buttons/toggle_style.dart'; |
||||||
|
|
||||||
|
/// The default size of the icon of a button. |
||||||
|
const double kDefaultIconSize = 18; |
||||||
|
|
||||||
|
/// The default size for the toolbar (width, height) |
||||||
|
const double defaultToolbarSize = kDefaultIconSize * 2; |
||||||
|
|
||||||
|
/// The factor of how much larger the button is in relation to the icon. |
||||||
|
const double kIconButtonFactor = 1.77; |
||||||
|
|
||||||
|
/// The horizontal margin between the contents of each toolbar section. |
||||||
|
const double kToolbarSectionSpacing = 4; |
||||||
|
|
||||||
|
/// The configurations for the toolbar widget of flutter quill |
||||||
|
@immutable |
||||||
|
class QuillToolbarConfigurations { |
||||||
|
const QuillToolbarConfigurations({ |
||||||
|
this.buttonOptions = const QuillToolbarButtonOptions(), |
||||||
|
this.multiRowsDisplay = true, |
||||||
|
this.fontFamilyValues, |
||||||
|
|
||||||
|
/// By default it will calculated based on the [baseOptions] iconSize |
||||||
|
/// You can change it but the the change only apply if |
||||||
|
/// the [multiRowsDisplay] is false, if [multiRowsDisplay] then the value |
||||||
|
/// will be [kDefaultIconSize] * 2 |
||||||
|
double? toolbarSize, |
||||||
|
}) : _toolbarSize = toolbarSize; |
||||||
|
|
||||||
|
final double? _toolbarSize; |
||||||
|
|
||||||
|
/// The toolbar size, by default it will be `baseButtonOptions.iconSize * 2` |
||||||
|
double get toolbarSize { |
||||||
|
final alternativeToolbarSize = _toolbarSize; |
||||||
|
if (alternativeToolbarSize != null) { |
||||||
|
return alternativeToolbarSize; |
||||||
|
} |
||||||
|
return buttonOptions.baseButtonOptions.globalIconSize * 2; |
||||||
|
} |
||||||
|
|
||||||
|
/// If you want change spesefic buttons or all of them |
||||||
|
/// then you came to the right place |
||||||
|
final QuillToolbarButtonOptions buttonOptions; |
||||||
|
final bool multiRowsDisplay; |
||||||
|
|
||||||
|
/// By default will be final |
||||||
|
/// ``` |
||||||
|
/// { |
||||||
|
/// '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', |
||||||
|
/// 'Clear'.i18n: 'Clear' |
||||||
|
/// }; |
||||||
|
/// ``` |
||||||
|
final Map<String, String>? fontFamilyValues; |
||||||
|
} |
||||||
|
|
||||||
|
/// The configurations for the buttons of the toolbar widget of flutter quill |
||||||
|
@immutable |
||||||
|
class QuillToolbarButtonOptions { |
||||||
|
const QuillToolbarButtonOptions({ |
||||||
|
this.baseButtonOptions = const QuillToolbarBaseButtonOptions(), |
||||||
|
this.undoHistoryButtonOptions = const QuillToolbarHistoryButtonOptions( |
||||||
|
isUndo: true, |
||||||
|
), |
||||||
|
this.redoHistoryButtonOptions = const QuillToolbarHistoryButtonOptions( |
||||||
|
isUndo: false, |
||||||
|
), |
||||||
|
this.fontFamilyButtonOptions = const QuillToolbarFontFamilyButtonOptions( |
||||||
|
attribute: Attribute.font, |
||||||
|
), |
||||||
|
}); |
||||||
|
|
||||||
|
/// The base configurations for all the buttons |
||||||
|
final QuillToolbarBaseButtonOptions baseButtonOptions; |
||||||
|
final QuillToolbarHistoryButtonOptions undoHistoryButtonOptions; |
||||||
|
final QuillToolbarHistoryButtonOptions redoHistoryButtonOptions; |
||||||
|
final QuillToolbarFontFamilyButtonOptions fontFamilyButtonOptions; |
||||||
|
} |
@ -0,0 +1,17 @@ |
|||||||
|
import 'package:flutter/widgets.dart' show BuildContext; |
||||||
|
|
||||||
|
import '../../../flutter_quill.dart' show QuillController, QuillProvider; |
||||||
|
import 'build_context.dart'; |
||||||
|
|
||||||
|
extension QuillControllerExt on QuillController? { |
||||||
|
/// Simple logic to use the current passed controller if not null |
||||||
|
/// if null then we will have to use the default one from [QuillProvider] |
||||||
|
/// using the [context] |
||||||
|
QuillController notNull(BuildContext context) { |
||||||
|
final controller = this; |
||||||
|
if (controller != null) { |
||||||
|
return controller; |
||||||
|
} |
||||||
|
return context.requireQuillController; |
||||||
|
} |
||||||
|
} |
@ -1,9 +1,9 @@ |
|||||||
import 'package:flutter/material.dart'; |
import 'package:flutter/material.dart'; |
||||||
|
|
||||||
import '../../models/documents/attribute.dart'; |
import '../../../models/documents/attribute.dart'; |
||||||
import '../../models/themes/quill_icon_theme.dart'; |
import '../../../models/themes/quill_icon_theme.dart'; |
||||||
import '../controller.dart'; |
import '../../controller.dart'; |
||||||
import '../toolbar.dart'; |
import '../../toolbar.dart'; |
||||||
|
|
||||||
class ClearFormatButton extends StatefulWidget { |
class ClearFormatButton extends StatefulWidget { |
||||||
const ClearFormatButton({ |
const ClearFormatButton({ |
@ -1,13 +1,13 @@ |
|||||||
import 'package:flutter/material.dart'; |
import 'package:flutter/material.dart'; |
||||||
import 'package:flutter_colorpicker/flutter_colorpicker.dart'; |
import 'package:flutter_colorpicker/flutter_colorpicker.dart'; |
||||||
|
|
||||||
import '../../models/documents/attribute.dart'; |
import '../../../models/documents/attribute.dart'; |
||||||
import '../../models/documents/style.dart'; |
import '../../../models/documents/style.dart'; |
||||||
import '../../models/themes/quill_icon_theme.dart'; |
import '../../../models/themes/quill_icon_theme.dart'; |
||||||
import '../../translations/toolbar.i18n.dart'; |
import '../../../translations/toolbar.i18n.dart'; |
||||||
import '../../utils/color.dart'; |
import '../../../utils/color.dart'; |
||||||
import '../controller.dart'; |
import '../../controller.dart'; |
||||||
import '../toolbar.dart'; |
import '../../toolbar.dart'; |
||||||
|
|
||||||
/// Controls color styles. |
/// Controls color styles. |
||||||
/// |
/// |
@ -1,7 +1,7 @@ |
|||||||
import 'package:flutter/material.dart'; |
import 'package:flutter/material.dart'; |
||||||
|
|
||||||
import '../../models/themes/quill_icon_theme.dart'; |
import '../../../models/themes/quill_icon_theme.dart'; |
||||||
import '../toolbar.dart'; |
import '../../toolbar.dart'; |
||||||
|
|
||||||
class CustomButton extends StatelessWidget { |
class CustomButton extends StatelessWidget { |
||||||
const CustomButton({ |
const CustomButton({ |
@ -0,0 +1,276 @@ |
|||||||
|
import 'package:flutter/material.dart'; |
||||||
|
|
||||||
|
import '../../../../extensions.dart'; |
||||||
|
import '../../../models/config/toolbar/buttons/font_family.dart'; |
||||||
|
import '../../../models/documents/attribute.dart'; |
||||||
|
import '../../../models/documents/style.dart'; |
||||||
|
import '../../../translations/toolbar.i18n.dart'; |
||||||
|
import '../../../utils/extensions/build_context.dart'; |
||||||
|
import '../../../utils/extensions/quill_controller.dart'; |
||||||
|
import '../../controller.dart'; |
||||||
|
|
||||||
|
class QuillToolbarFontFamilyButton extends StatefulWidget { |
||||||
|
QuillToolbarFontFamilyButton({ |
||||||
|
required this.options, |
||||||
|
super.key, |
||||||
|
}) : assert(options.rawItemsMap?.isNotEmpty ?? (true)), |
||||||
|
assert( |
||||||
|
options.initialValue == null || options.initialValue!.isNotEmpty, |
||||||
|
); |
||||||
|
|
||||||
|
final QuillToolbarFontFamilyButtonOptions options; |
||||||
|
|
||||||
|
@override |
||||||
|
_QuillToolbarFontFamilyButtonState createState() => |
||||||
|
_QuillToolbarFontFamilyButtonState(); |
||||||
|
} |
||||||
|
|
||||||
|
class _QuillToolbarFontFamilyButtonState |
||||||
|
extends State<QuillToolbarFontFamilyButton> { |
||||||
|
late String _defaultDisplayText; |
||||||
|
String _currentValue = ''; |
||||||
|
|
||||||
|
QuillToolbarFontFamilyButtonOptions get options { |
||||||
|
return widget.options; |
||||||
|
} |
||||||
|
|
||||||
|
/// Since t's not safe to call anything related to the context in dispose |
||||||
|
/// then we will save a reference to the [controller] |
||||||
|
/// and update it in [didChangeDependencies] |
||||||
|
/// and use it in dispose method |
||||||
|
late QuillController _controller; |
||||||
|
|
||||||
|
QuillController get controller { |
||||||
|
return options.controller.notNull(context); |
||||||
|
} |
||||||
|
|
||||||
|
Style get _selectionStyle => controller.getSelectionStyle(); |
||||||
|
|
||||||
|
@override |
||||||
|
void initState() { |
||||||
|
super.initState(); |
||||||
|
_initState(); |
||||||
|
} |
||||||
|
|
||||||
|
Future<void> _initState() async { |
||||||
|
if (isFlutterTest()) { |
||||||
|
// We don't need to listen for changes in the tests |
||||||
|
return; |
||||||
|
} |
||||||
|
await Future.delayed(Duration.zero); |
||||||
|
setState(() { |
||||||
|
_currentValue = _defaultDisplayText = options.initialValue ?? 'Font'.i18n; |
||||||
|
}); |
||||||
|
controller.addListener(_didChangeEditingValue); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
void didChangeDependencies() { |
||||||
|
super.didChangeDependencies(); |
||||||
|
_controller = controller; |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
void dispose() { |
||||||
|
_controller.removeListener(_didChangeEditingValue); |
||||||
|
super.dispose(); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
void didUpdateWidget(covariant QuillToolbarFontFamilyButton oldWidget) { |
||||||
|
super.didUpdateWidget(oldWidget); |
||||||
|
if (controller != controller) { |
||||||
|
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 = 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', |
||||||
|
'Clear'.i18n: 'Clear' |
||||||
|
}; |
||||||
|
return rawItemsMap; |
||||||
|
} |
||||||
|
|
||||||
|
String? _getKeyName(String value) { |
||||||
|
for (final entry in rawItemsMap.entries) { |
||||||
|
if (entry.value == value) { |
||||||
|
return entry.key; |
||||||
|
} |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
double get iconSize { |
||||||
|
final iconSize = options.iconSize; |
||||||
|
return iconSize ?? 40; |
||||||
|
// final baseFontSize = |
||||||
|
// context.requireQuillToolbarBaseButtonOptions.globalIconSize; |
||||||
|
// if (baseFontSize != iconSize) { |
||||||
|
// return 40; |
||||||
|
// } |
||||||
|
// return iconSize ?? baseFontSize; |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
Widget build(BuildContext context) { |
||||||
|
final baseButtonConfigurations = |
||||||
|
context.requireQuillToolbarBaseButtonOptions; |
||||||
|
final childBuilder = |
||||||
|
options.childBuilder ?? baseButtonConfigurations.childBuilder; |
||||||
|
if (childBuilder != null) { |
||||||
|
return childBuilder( |
||||||
|
options, |
||||||
|
QuillToolbarFontFamilyButtonExtraOptions( |
||||||
|
currentValue: _currentValue, |
||||||
|
defaultDisplayText: _defaultDisplayText, |
||||||
|
), |
||||||
|
); |
||||||
|
} |
||||||
|
return ConstrainedBox( |
||||||
|
constraints: BoxConstraints.tightFor( |
||||||
|
height: iconSize * 1.81, |
||||||
|
width: options.width, |
||||||
|
), |
||||||
|
child: UtilityWidgets.maybeWidget( |
||||||
|
enabled: (options.tooltip ?? '').isNotEmpty || |
||||||
|
options.overrideTooltipByFontFamily, |
||||||
|
wrapper: (child) { |
||||||
|
var effectiveTooltip = options.tooltip ?? ''; |
||||||
|
if (options.overrideTooltipByFontFamily) { |
||||||
|
effectiveTooltip = effectiveTooltip.isNotEmpty |
||||||
|
? '$effectiveTooltip: $_currentValue' |
||||||
|
: '${'Font'.i18n}: $_currentValue'; |
||||||
|
} |
||||||
|
return Tooltip(message: effectiveTooltip, child: child); |
||||||
|
}, |
||||||
|
child: RawMaterialButton( |
||||||
|
visualDensity: VisualDensity.compact, |
||||||
|
shape: RoundedRectangleBorder( |
||||||
|
borderRadius: |
||||||
|
BorderRadius.circular(options.iconTheme?.borderRadius ?? 2), |
||||||
|
), |
||||||
|
fillColor: options.fillColor, |
||||||
|
elevation: 0, |
||||||
|
hoverElevation: options.hoverElevation, |
||||||
|
highlightElevation: options.hoverElevation, |
||||||
|
onPressed: () { |
||||||
|
_showMenu(); |
||||||
|
options.afterButtonPressed?.call(); |
||||||
|
}, |
||||||
|
child: _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, |
||||||
|
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(() { |
||||||
|
_currentValue = keyName ?? _defaultDisplayText; |
||||||
|
if (keyName != null) { |
||||||
|
controller.formatSelection( |
||||||
|
Attribute.fromKeyValue('font', newValue == 'Clear' ? null : newValue), |
||||||
|
); |
||||||
|
options.onSelected?.call(newValue); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
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( |
||||||
|
_currentValue, |
||||||
|
maxLines: 1, |
||||||
|
overflow: options.labelOverflow, |
||||||
|
style: options.style ?? |
||||||
|
TextStyle( |
||||||
|
fontSize: iconSize / 1.15, |
||||||
|
color: options.iconTheme?.iconUnselectedColor ?? |
||||||
|
theme.iconTheme.color, |
||||||
|
), |
||||||
|
), |
||||||
|
), |
||||||
|
const SizedBox(width: 3), |
||||||
|
Icon( |
||||||
|
Icons.arrow_drop_down, |
||||||
|
size: iconSize / 1.15, |
||||||
|
color: |
||||||
|
options.iconTheme?.iconUnselectedColor ?? theme.iconTheme.color, |
||||||
|
) |
||||||
|
], |
||||||
|
), |
||||||
|
); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,143 @@ |
|||||||
|
import 'package:flutter/material.dart'; |
||||||
|
|
||||||
|
import '../../../../extensions.dart'; |
||||||
|
import '../../../../translations.dart'; |
||||||
|
import '../../../utils/extensions/build_context.dart'; |
||||||
|
import '../../../utils/extensions/quill_controller.dart'; |
||||||
|
import '../../controller.dart'; |
||||||
|
import '../../toolbar.dart'; |
||||||
|
|
||||||
|
class QuillToolbarHistoryButton extends StatefulWidget { |
||||||
|
const QuillToolbarHistoryButton({ |
||||||
|
required this.options, |
||||||
|
super.key, |
||||||
|
}); |
||||||
|
|
||||||
|
final QuillToolbarHistoryButtonOptions options; |
||||||
|
|
||||||
|
@override |
||||||
|
_QuillToolbarHistoryButtonState createState() => |
||||||
|
_QuillToolbarHistoryButtonState(); |
||||||
|
} |
||||||
|
|
||||||
|
class _QuillToolbarHistoryButtonState extends State<QuillToolbarHistoryButton> { |
||||||
|
late ThemeData theme; |
||||||
|
var _canPressed = false; |
||||||
|
|
||||||
|
QuillToolbarHistoryButtonOptions get options { |
||||||
|
return widget.options; |
||||||
|
} |
||||||
|
|
||||||
|
QuillController get controller { |
||||||
|
return options.controller.notNull(context); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
void initState() { |
||||||
|
super.initState(); |
||||||
|
_listenForChanges(); // Listen for changes and change it |
||||||
|
} |
||||||
|
|
||||||
|
Future<void> _listenForChanges() async { |
||||||
|
if (isFlutterTest()) { |
||||||
|
// We don't need to listen for changes in the tests |
||||||
|
return; |
||||||
|
} |
||||||
|
await Future.delayed(Duration.zero); // Wait for the widget to built |
||||||
|
_updateCanPressed(); // Set the init state |
||||||
|
|
||||||
|
// Listen for changes and change it |
||||||
|
controller.changes.listen((event) async { |
||||||
|
_updateCanPressed(); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
Widget build(BuildContext context) { |
||||||
|
theme = Theme.of(context); |
||||||
|
|
||||||
|
final baseButtonConfigurations = |
||||||
|
context.requireQuillToolbarBaseButtonOptions; |
||||||
|
final tooltip = options.tooltip ?? |
||||||
|
baseButtonConfigurations.tooltip ?? |
||||||
|
(options.isUndo ? 'Undo'.i18n : 'Redo'.i18n); |
||||||
|
final iconData = options.iconData ?? |
||||||
|
baseButtonConfigurations.iconData ?? |
||||||
|
(options.isUndo ? Icons.undo_outlined : Icons.redo_outlined); |
||||||
|
final childBuilder = |
||||||
|
options.childBuilder ?? baseButtonConfigurations.childBuilder; |
||||||
|
final iconSize = options.iconSize ?? |
||||||
|
context.requireQuillToolbarBaseButtonOptions.globalIconSize; |
||||||
|
final iconTheme = options.iconTheme ?? baseButtonConfigurations.iconTheme; |
||||||
|
|
||||||
|
final fillColor = iconTheme?.iconUnselectedFillColor ?? theme.canvasColor; |
||||||
|
|
||||||
|
final afterButtonPressed = options.afterButtonPressed ?? |
||||||
|
baseButtonConfigurations.afterButtonPressed; |
||||||
|
|
||||||
|
if (childBuilder != null) { |
||||||
|
return childBuilder( |
||||||
|
QuillToolbarHistoryButtonOptions( |
||||||
|
isUndo: options.isUndo, |
||||||
|
afterButtonPressed: afterButtonPressed, |
||||||
|
controller: controller, |
||||||
|
iconData: iconData, |
||||||
|
iconSize: iconSize, |
||||||
|
iconTheme: iconTheme, |
||||||
|
tooltip: tooltip, |
||||||
|
), |
||||||
|
HistoryButtonExtraOptions( |
||||||
|
onPressed: () { |
||||||
|
_updateHistory(); |
||||||
|
afterButtonPressed?.call(); |
||||||
|
}, |
||||||
|
canPressed: _canPressed, |
||||||
|
), |
||||||
|
); |
||||||
|
} |
||||||
|
return QuillIconButton( |
||||||
|
tooltip: tooltip, |
||||||
|
highlightElevation: 0, |
||||||
|
hoverElevation: 0, |
||||||
|
size: iconSize * kIconButtonFactor, |
||||||
|
icon: Icon( |
||||||
|
iconData, |
||||||
|
size: iconSize, |
||||||
|
color: _canPressed |
||||||
|
? iconTheme?.iconUnselectedColor ?? theme.iconTheme.color |
||||||
|
: iconTheme?.disabledIconColor ?? theme.disabledColor, |
||||||
|
), |
||||||
|
fillColor: fillColor, |
||||||
|
borderRadius: iconTheme?.borderRadius ?? 2, |
||||||
|
onPressed: _updateHistory, |
||||||
|
afterPressed: afterButtonPressed, |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
void _updateCanPressed() { |
||||||
|
if (!mounted) return; |
||||||
|
|
||||||
|
setState(() { |
||||||
|
if (options.isUndo) { |
||||||
|
_canPressed = controller.hasUndo; |
||||||
|
return; |
||||||
|
} |
||||||
|
_canPressed = controller.hasRedo; |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
void _updateHistory() { |
||||||
|
if (options.isUndo) { |
||||||
|
if (controller.hasUndo) { |
||||||
|
controller.undo(); |
||||||
|
} |
||||||
|
// _updateCanPressed(); // We are already listeneting for the changes |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
if (controller.hasRedo) { |
||||||
|
controller.redo(); |
||||||
|
// _updateCanPressed(); // We are already listeneting for the changes |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -1,8 +1,8 @@ |
|||||||
import 'package:flutter/material.dart'; |
import 'package:flutter/material.dart'; |
||||||
|
|
||||||
import '../../models/themes/quill_icon_theme.dart'; |
import '../../../models/themes/quill_icon_theme.dart'; |
||||||
import '../controller.dart'; |
import '../../controller.dart'; |
||||||
import '../toolbar.dart'; |
import '../../toolbar.dart'; |
||||||
|
|
||||||
class IndentButton extends StatefulWidget { |
class IndentButton extends StatefulWidget { |
||||||
const IndentButton({ |
const IndentButton({ |
@ -1,14 +1,14 @@ |
|||||||
import 'package:flutter/material.dart'; |
import 'package:flutter/material.dart'; |
||||||
|
|
||||||
import '../../models/documents/attribute.dart'; |
import '../../../models/documents/attribute.dart'; |
||||||
import '../../models/rules/insert.dart'; |
import '../../../models/rules/insert.dart'; |
||||||
import '../../models/structs/link_dialog_action.dart'; |
import '../../../models/structs/link_dialog_action.dart'; |
||||||
import '../../models/themes/quill_dialog_theme.dart'; |
import '../../../models/themes/quill_dialog_theme.dart'; |
||||||
import '../../models/themes/quill_icon_theme.dart'; |
import '../../../models/themes/quill_icon_theme.dart'; |
||||||
import '../../translations/toolbar.i18n.dart'; |
import '../../../translations/toolbar.i18n.dart'; |
||||||
import '../controller.dart'; |
import '../../controller.dart'; |
||||||
import '../link.dart'; |
import '../../link.dart'; |
||||||
import '../toolbar.dart'; |
import '../../toolbar.dart'; |
||||||
|
|
||||||
class LinkStyleButton extends StatefulWidget { |
class LinkStyleButton extends StatefulWidget { |
||||||
const LinkStyleButton({ |
const LinkStyleButton({ |
@ -1,12 +1,12 @@ |
|||||||
import 'package:flutter/material.dart'; |
import 'package:flutter/material.dart'; |
||||||
|
|
||||||
import '../../models/documents/attribute.dart'; |
import '../../../models/documents/attribute.dart'; |
||||||
import '../../models/documents/style.dart'; |
import '../../../models/documents/style.dart'; |
||||||
import '../../models/themes/quill_icon_theme.dart'; |
import '../../../models/themes/quill_icon_theme.dart'; |
||||||
import '../../translations/toolbar.i18n.dart'; |
import '../../../translations/toolbar.i18n.dart'; |
||||||
import '../../utils/font.dart'; |
import '../../../utils/font.dart'; |
||||||
import '../../utils/widgets.dart'; |
import '../../../utils/widgets.dart'; |
||||||
import '../controller.dart'; |
import '../../controller.dart'; |
||||||
|
|
||||||
class QuillFontSizeButton extends StatefulWidget { |
class QuillFontSizeButton extends StatefulWidget { |
||||||
const QuillFontSizeButton({ |
const QuillFontSizeButton({ |
@ -1,6 +1,6 @@ |
|||||||
import 'package:flutter/material.dart'; |
import 'package:flutter/material.dart'; |
||||||
|
|
||||||
import '../../utils/widgets.dart'; |
import '../../../utils/widgets.dart'; |
||||||
|
|
||||||
class QuillIconButton extends StatelessWidget { |
class QuillIconButton extends StatelessWidget { |
||||||
const QuillIconButton({ |
const QuillIconButton({ |
@ -1,10 +1,10 @@ |
|||||||
import 'package:flutter/material.dart'; |
import 'package:flutter/material.dart'; |
||||||
|
|
||||||
import '../../models/themes/quill_dialog_theme.dart'; |
import '../../../models/themes/quill_dialog_theme.dart'; |
||||||
import '../../models/themes/quill_icon_theme.dart'; |
import '../../../models/themes/quill_icon_theme.dart'; |
||||||
import '../controller.dart'; |
import '../../controller.dart'; |
||||||
import '../toolbar.dart'; |
import '../../toolbar.dart'; |
||||||
import 'search_dialog.dart'; |
import '../search_dialog.dart'; |
||||||
|
|
||||||
class SearchButton extends StatelessWidget { |
class SearchButton extends StatelessWidget { |
||||||
const SearchButton({ |
const SearchButton({ |
@ -1,13 +1,13 @@ |
|||||||
import 'package:flutter/foundation.dart'; |
import 'package:flutter/foundation.dart'; |
||||||
import 'package:flutter/material.dart'; |
import 'package:flutter/material.dart'; |
||||||
|
|
||||||
import '../../models/documents/attribute.dart'; |
import '../../../models/documents/attribute.dart'; |
||||||
import '../../models/documents/style.dart'; |
import '../../../models/documents/style.dart'; |
||||||
import '../../models/themes/quill_icon_theme.dart'; |
import '../../../models/themes/quill_icon_theme.dart'; |
||||||
import '../../utils/widgets.dart'; |
import '../../../utils/widgets.dart'; |
||||||
import '../controller.dart'; |
import '../../controller.dart'; |
||||||
import '../toolbar.dart'; |
import '../../toolbar.dart'; |
||||||
import 'enum.dart'; |
import '../enum.dart'; |
||||||
|
|
||||||
class SelectAlignmentButton extends StatefulWidget { |
class SelectAlignmentButton extends StatefulWidget { |
||||||
const SelectAlignmentButton({ |
const SelectAlignmentButton({ |
@ -1,12 +1,12 @@ |
|||||||
import 'package:flutter/foundation.dart'; |
import 'package:flutter/foundation.dart'; |
||||||
import 'package:flutter/material.dart'; |
import 'package:flutter/material.dart'; |
||||||
|
|
||||||
import '../../models/documents/attribute.dart'; |
import '../../../models/documents/attribute.dart'; |
||||||
import '../../models/documents/style.dart'; |
import '../../../models/documents/style.dart'; |
||||||
import '../../models/themes/quill_icon_theme.dart'; |
import '../../../models/themes/quill_icon_theme.dart'; |
||||||
import '../../utils/widgets.dart'; |
import '../../../utils/widgets.dart'; |
||||||
import '../controller.dart'; |
import '../../controller.dart'; |
||||||
import '../toolbar.dart'; |
import '../../toolbar.dart'; |
||||||
|
|
||||||
class SelectHeaderStyleButton extends StatefulWidget { |
class SelectHeaderStyleButton extends StatefulWidget { |
||||||
const SelectHeaderStyleButton({ |
const SelectHeaderStyleButton({ |
@ -1,11 +1,11 @@ |
|||||||
import 'package:flutter/material.dart'; |
import 'package:flutter/material.dart'; |
||||||
|
|
||||||
import '../../models/documents/attribute.dart'; |
import '../../../models/documents/attribute.dart'; |
||||||
import '../../models/documents/style.dart'; |
import '../../../models/documents/style.dart'; |
||||||
import '../../models/themes/quill_icon_theme.dart'; |
import '../../../models/themes/quill_icon_theme.dart'; |
||||||
import '../../utils/widgets.dart'; |
import '../../../utils/widgets.dart'; |
||||||
import '../controller.dart'; |
import '../../controller.dart'; |
||||||
import '../toolbar.dart'; |
import '../../toolbar.dart'; |
||||||
|
|
||||||
class ToggleCheckListButton extends StatefulWidget { |
class ToggleCheckListButton extends StatefulWidget { |
||||||
const ToggleCheckListButton({ |
const ToggleCheckListButton({ |
@ -1,11 +1,11 @@ |
|||||||
import 'package:flutter/material.dart'; |
import 'package:flutter/material.dart'; |
||||||
|
|
||||||
import '../../models/documents/attribute.dart'; |
import '../../../models/documents/attribute.dart'; |
||||||
import '../../models/documents/style.dart'; |
import '../../../models/documents/style.dart'; |
||||||
import '../../models/themes/quill_icon_theme.dart'; |
import '../../../models/themes/quill_icon_theme.dart'; |
||||||
import '../../utils/widgets.dart'; |
import '../../../utils/widgets.dart'; |
||||||
import '../controller.dart'; |
import '../../controller.dart'; |
||||||
import '../toolbar.dart'; |
import '../../toolbar.dart'; |
||||||
|
|
||||||
typedef ToggleStyleButtonBuilder = Widget Function( |
typedef ToggleStyleButtonBuilder = Widget Function( |
||||||
BuildContext context, |
BuildContext context, |
@ -1,89 +0,0 @@ |
|||||||
import 'package:flutter/material.dart'; |
|
||||||
|
|
||||||
import '../../models/themes/quill_icon_theme.dart'; |
|
||||||
import '../controller.dart'; |
|
||||||
import '../toolbar.dart'; |
|
||||||
|
|
||||||
class HistoryButton extends StatefulWidget { |
|
||||||
const HistoryButton({ |
|
||||||
required this.icon, |
|
||||||
required this.controller, |
|
||||||
required this.undo, |
|
||||||
this.iconSize = kDefaultIconSize, |
|
||||||
this.iconTheme, |
|
||||||
this.afterButtonPressed, |
|
||||||
this.tooltip, |
|
||||||
Key? key, |
|
||||||
}) : super(key: key); |
|
||||||
|
|
||||||
final IconData icon; |
|
||||||
final double iconSize; |
|
||||||
final bool undo; |
|
||||||
final QuillController controller; |
|
||||||
final QuillIconTheme? iconTheme; |
|
||||||
final VoidCallback? afterButtonPressed; |
|
||||||
final String? tooltip; |
|
||||||
|
|
||||||
@override |
|
||||||
_HistoryButtonState createState() => _HistoryButtonState(); |
|
||||||
} |
|
||||||
|
|
||||||
class _HistoryButtonState extends State<HistoryButton> { |
|
||||||
Color? _iconColor; |
|
||||||
late ThemeData theme; |
|
||||||
|
|
||||||
@override |
|
||||||
Widget build(BuildContext context) { |
|
||||||
theme = Theme.of(context); |
|
||||||
_setIconColor(); |
|
||||||
|
|
||||||
final fillColor = |
|
||||||
widget.iconTheme?.iconUnselectedFillColor ?? theme.canvasColor; |
|
||||||
widget.controller.changes.listen((event) async { |
|
||||||
_setIconColor(); |
|
||||||
}); |
|
||||||
return QuillIconButton( |
|
||||||
tooltip: widget.tooltip, |
|
||||||
highlightElevation: 0, |
|
||||||
hoverElevation: 0, |
|
||||||
size: widget.iconSize * kIconButtonFactor, |
|
||||||
icon: Icon(widget.icon, size: widget.iconSize, color: _iconColor), |
|
||||||
fillColor: fillColor, |
|
||||||
borderRadius: widget.iconTheme?.borderRadius ?? 2, |
|
||||||
onPressed: _changeHistory, |
|
||||||
afterPressed: widget.afterButtonPressed, |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
void _setIconColor() { |
|
||||||
if (!mounted) return; |
|
||||||
|
|
||||||
if (widget.undo) { |
|
||||||
setState(() { |
|
||||||
_iconColor = widget.controller.hasUndo |
|
||||||
? widget.iconTheme?.iconUnselectedColor ?? theme.iconTheme.color |
|
||||||
: widget.iconTheme?.disabledIconColor ?? theme.disabledColor; |
|
||||||
}); |
|
||||||
} else { |
|
||||||
setState(() { |
|
||||||
_iconColor = widget.controller.hasRedo |
|
||||||
? widget.iconTheme?.iconUnselectedColor ?? theme.iconTheme.color |
|
||||||
: widget.iconTheme?.disabledIconColor ?? theme.disabledColor; |
|
||||||
}); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
void _changeHistory() { |
|
||||||
if (widget.undo) { |
|
||||||
if (widget.controller.hasUndo) { |
|
||||||
widget.controller.undo(); |
|
||||||
} |
|
||||||
} else { |
|
||||||
if (widget.controller.hasRedo) { |
|
||||||
widget.controller.redo(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
_setIconColor(); |
|
||||||
} |
|
||||||
} |
|
@ -1,238 +0,0 @@ |
|||||||
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 '../../utils/widgets.dart'; |
|
||||||
import '../controller.dart'; |
|
||||||
|
|
||||||
class QuillFontFamilyButton extends StatefulWidget { |
|
||||||
const QuillFontFamilyButton({ |
|
||||||
required this.rawItemsMap, |
|
||||||
required this.attribute, |
|
||||||
required this.controller, |
|
||||||
@Deprecated('It is not required because of `rawItemsMap`') this.items, |
|
||||||
this.onSelected, |
|
||||||
this.iconSize = 40, |
|
||||||
this.fillColor, |
|
||||||
this.hoverElevation = 1, |
|
||||||
this.highlightElevation = 1, |
|
||||||
this.iconTheme, |
|
||||||
this.afterButtonPressed, |
|
||||||
this.tooltip, |
|
||||||
this.padding, |
|
||||||
this.style, |
|
||||||
this.width, |
|
||||||
this.renderFontFamilies = true, |
|
||||||
this.initialValue, |
|
||||||
this.labelOverflow = TextOverflow.visible, |
|
||||||
this.overrideTooltipByFontFamily = false, |
|
||||||
this.itemHeight, |
|
||||||
this.itemPadding, |
|
||||||
this.defaultItemColor = Colors.red, |
|
||||||
Key? key, |
|
||||||
}) : assert(rawItemsMap.length > 0), |
|
||||||
assert(initialValue == null || initialValue.length > 0), |
|
||||||
super(key: key); |
|
||||||
|
|
||||||
final double iconSize; |
|
||||||
final Color? fillColor; |
|
||||||
final double hoverElevation; |
|
||||||
final double highlightElevation; |
|
||||||
@Deprecated('It is not required because of `rawItemsMap`') |
|
||||||
final List<PopupMenuEntry<String>>? items; |
|
||||||
final Map<String, String> rawItemsMap; |
|
||||||
final ValueChanged<String>? onSelected; |
|
||||||
final QuillIconTheme? iconTheme; |
|
||||||
final Attribute attribute; |
|
||||||
final QuillController controller; |
|
||||||
final VoidCallback? afterButtonPressed; |
|
||||||
final String? tooltip; |
|
||||||
final EdgeInsetsGeometry? padding; |
|
||||||
final TextStyle? style; |
|
||||||
final double? width; |
|
||||||
final bool renderFontFamilies; |
|
||||||
final String? initialValue; |
|
||||||
final TextOverflow labelOverflow; |
|
||||||
final bool overrideTooltipByFontFamily; |
|
||||||
final double? itemHeight; |
|
||||||
final EdgeInsets? itemPadding; |
|
||||||
final Color? defaultItemColor; |
|
||||||
|
|
||||||
@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 = widget.initialValue ?? '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, |
|
||||||
width: widget.width, |
|
||||||
), |
|
||||||
child: UtilityWidgets.maybeWidget( |
|
||||||
enabled: (widget.tooltip ?? '').isNotEmpty || |
|
||||||
widget.overrideTooltipByFontFamily, |
|
||||||
wrapper: (child) { |
|
||||||
var effectiveTooltip = widget.tooltip ?? ''; |
|
||||||
if (widget.overrideTooltipByFontFamily) { |
|
||||||
effectiveTooltip = effectiveTooltip.isNotEmpty |
|
||||||
? '$effectiveTooltip: $_currentValue' |
|
||||||
: '${'Font'.i18n}: $_currentValue'; |
|
||||||
} |
|
||||||
return Tooltip(message: effectiveTooltip, child: child); |
|
||||||
}, |
|
||||||
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(); |
|
||||||
widget.afterButtonPressed?.call(); |
|
||||||
}, |
|
||||||
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: [ |
|
||||||
for (final MapEntry<String, String> fontFamily |
|
||||||
in widget.rawItemsMap.entries) |
|
||||||
PopupMenuItem<String>( |
|
||||||
key: ValueKey(fontFamily.key), |
|
||||||
value: fontFamily.value, |
|
||||||
height: widget.itemHeight ?? kMinInteractiveDimension, |
|
||||||
padding: widget.itemPadding, |
|
||||||
child: Text( |
|
||||||
fontFamily.key.toString(), |
|
||||||
style: TextStyle( |
|
||||||
fontFamily: widget.renderFontFamilies ? fontFamily.value : null, |
|
||||||
color: fontFamily.value == 'Clear' |
|
||||||
? widget.defaultItemColor |
|
||||||
: null, |
|
||||||
), |
|
||||||
), |
|
||||||
), |
|
||||||
], |
|
||||||
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.controller.formatSelection(Attribute.fromKeyValue( |
|
||||||
'font', newValue == 'Clear' ? null : newValue)); |
|
||||||
widget.onSelected?.call(newValue); |
|
||||||
} |
|
||||||
}); |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
Widget _buildContent(BuildContext context) { |
|
||||||
final theme = Theme.of(context); |
|
||||||
final hasFinalWidth = widget.width != null; |
|
||||||
return Padding( |
|
||||||
padding: widget.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( |
|
||||||
_currentValue, |
|
||||||
maxLines: 1, |
|
||||||
overflow: widget.labelOverflow, |
|
||||||
style: widget.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) |
|
||||||
], |
|
||||||
), |
|
||||||
); |
|
||||||
} |
|
||||||
} |
|
Loading…
Reference in new issue