TF-1545: Update pick color dialog in rich text
(cherry picked from commit e70c9871c3e47ac2d0f2ed47265cae398665d984)
This commit is contained in:
@@ -192,6 +192,39 @@ extension AppColor on Color {
|
|||||||
.toRadixString(16)
|
.toRadixString(16)
|
||||||
.padLeft(6, '0')
|
.padLeft(6, '0')
|
||||||
.toUpperCase()}';
|
.toUpperCase()}';
|
||||||
|
|
||||||
|
static List<Color> get listColorsPicker {
|
||||||
|
return [
|
||||||
|
...Colors.grey.listShadeColors,
|
||||||
|
...listMaterialColors.map((color) => color.shade900).toList(),
|
||||||
|
...listMaterialColors.map((color) => color.shade800).toList(),
|
||||||
|
...listMaterialColors.map((color) => color.shade700).toList(),
|
||||||
|
...listMaterialColors.map((color) => color.shade600).toList(),
|
||||||
|
...listMaterialColors.map((color) => color.shade500).toList(),
|
||||||
|
...listMaterialColors.map((color) => color.shade400).toList(),
|
||||||
|
...listMaterialColors.map((color) => color.shade300).toList(),
|
||||||
|
...listMaterialColors.map((color) => color.shade200).toList(),
|
||||||
|
...listMaterialColors.map((color) => color.shade100).toList(),
|
||||||
|
...listMaterialColors.map((color) => color.shade50).toList(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<MaterialColor> get listMaterialColors {
|
||||||
|
return [
|
||||||
|
Colors.cyan,
|
||||||
|
Colors.blue,
|
||||||
|
Colors.deepPurple,
|
||||||
|
Colors.purple,
|
||||||
|
Colors.pink,
|
||||||
|
Colors.red,
|
||||||
|
Colors.deepOrange,
|
||||||
|
Colors.orange,
|
||||||
|
Colors.amber,
|
||||||
|
Colors.yellow,
|
||||||
|
Colors.lime,
|
||||||
|
Colors.green
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ColorNullableExtension on Color? {
|
extension ColorNullableExtension on Color? {
|
||||||
@@ -203,3 +236,23 @@ extension ColorNullableExtension on Color? {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension MaterialColorExtension on MaterialColor {
|
||||||
|
|
||||||
|
List<Color> get listShadeColors {
|
||||||
|
return [
|
||||||
|
const Color(0xFFFFFFFF),
|
||||||
|
const Color(0xFFFCFCFC),
|
||||||
|
shade50,
|
||||||
|
shade100,
|
||||||
|
shade200,
|
||||||
|
shade300,
|
||||||
|
shade400,
|
||||||
|
shade500,
|
||||||
|
shade600,
|
||||||
|
shade700,
|
||||||
|
shade800,
|
||||||
|
shade900
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,31 +1,35 @@
|
|||||||
|
|
||||||
import 'package:core/presentation/extensions/color_extension.dart';
|
import 'package:core/presentation/extensions/color_extension.dart';
|
||||||
import 'package:core/presentation/views/button/icon_button_web.dart';
|
import 'package:core/presentation/views/button/icon_button_web.dart';
|
||||||
import 'package:flex_color_picker/flex_color_picker.dart';
|
import 'package:core/presentation/views/pick_color/color_code_field.dart';
|
||||||
|
import 'package:core/presentation/views/pick_color/color_picker_action_buttons.dart';
|
||||||
|
import 'package:core/presentation/views/pick_color/color_picker_copy_past_behaivor.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:pointer_interceptor/pointer_interceptor.dart';
|
import 'package:pointer_interceptor/pointer_interceptor.dart';
|
||||||
|
|
||||||
typedef SelectColorActionCallback = Function(Color? colorSelected);
|
typedef SelectColorActionCallback = Function(Color? colorSelected);
|
||||||
|
|
||||||
class ColorPickerDialogBuilder {
|
class ColorPickerDialogBuilder {
|
||||||
|
|
||||||
final SelectColorActionCallback? setColorActionCallback;
|
final SelectColorActionCallback? setColorActionCallback;
|
||||||
final VoidCallback? cancelActionCallback;
|
final VoidCallback? cancelActionCallback;
|
||||||
final VoidCallback? resetToDefaultActionCallback;
|
final VoidCallback? resetToDefaultActionCallback;
|
||||||
final BuildContext _context;
|
final BuildContext _context;
|
||||||
final Color defaultColor;
|
final Color defaultColor;
|
||||||
final Color _currentColor;
|
final ValueNotifier<Color> _currentColor;
|
||||||
final String? title;
|
final String? title;
|
||||||
final String? textActionSetColor;
|
final String? textActionSetColor;
|
||||||
final String? textActionCancel;
|
final String? textActionCancel;
|
||||||
final String? textActionResetDefault;
|
final String? textActionResetDefault;
|
||||||
|
final Function(Color)? onSelected;
|
||||||
|
|
||||||
Color? _colorSelected;
|
bool _shouldUpdate = false;
|
||||||
|
Color _colorCode = Colors.black;
|
||||||
|
|
||||||
ColorPickerDialogBuilder(
|
ColorPickerDialogBuilder(
|
||||||
this._context,
|
this._context,
|
||||||
this._currentColor,
|
this._currentColor,
|
||||||
{
|
{
|
||||||
|
this.onSelected,
|
||||||
this.title,
|
this.title,
|
||||||
this.textActionSetColor,
|
this.textActionSetColor,
|
||||||
this.textActionCancel,
|
this.textActionCancel,
|
||||||
@@ -35,22 +39,23 @@ class ColorPickerDialogBuilder {
|
|||||||
this.cancelActionCallback,
|
this.cancelActionCallback,
|
||||||
this.resetToDefaultActionCallback
|
this.resetToDefaultActionCallback
|
||||||
}
|
}
|
||||||
) : _colorSelected = _currentColor;
|
);
|
||||||
|
|
||||||
Future show() async {
|
Future show() async {
|
||||||
await showDialog(context: _context, builder: (BuildContext context) {
|
await showDialog(context: _context, builder: (BuildContext context) {
|
||||||
return PointerInterceptor(
|
return PointerInterceptor(
|
||||||
child: AlertDialog(
|
child: AlertDialog(
|
||||||
title: Text(title ?? '',
|
title: Text(
|
||||||
textAlign: TextAlign.center,
|
title ?? '',
|
||||||
style: const TextStyle(
|
textAlign: TextAlign.center,
|
||||||
fontWeight: FontWeight.bold,
|
style: const TextStyle(
|
||||||
fontSize: 20,
|
|
||||||
color: Colors.black)),
|
|
||||||
titleTextStyle: const TextStyle(
|
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
color: Colors.black),
|
color: Colors.black)),
|
||||||
|
titleTextStyle: const TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 20,
|
||||||
|
color: Colors.black),
|
||||||
titlePadding: const EdgeInsets.symmetric(vertical: 16, horizontal: 16),
|
titlePadding: const EdgeInsets.symmetric(vertical: 16, horizontal: 16),
|
||||||
contentPadding: const EdgeInsets.symmetric(vertical: 0, horizontal: 16),
|
contentPadding: const EdgeInsets.symmetric(vertical: 0, horizontal: 16),
|
||||||
actionsPadding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
|
actionsPadding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
|
||||||
@@ -59,27 +64,55 @@ class ColorPickerDialogBuilder {
|
|||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)),
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)),
|
||||||
scrollable: true,
|
scrollable: true,
|
||||||
elevation: 10,
|
elevation: 10,
|
||||||
content: ColorPicker(
|
content: ValueListenableBuilder(
|
||||||
color: _currentColor,
|
valueListenable: _currentColor,
|
||||||
onColorChanged: (color) => _colorSelected = color,
|
builder: (context, _, __) {
|
||||||
width: 40,
|
return Column(
|
||||||
height: 40,
|
children: [
|
||||||
spacing: 0,
|
Container(
|
||||||
runSpacing: 0,
|
width: 600,
|
||||||
borderRadius: 0,
|
decoration: const BoxDecoration(
|
||||||
wheelDiameter: 165,
|
borderRadius: BorderRadius.all(Radius.circular(4)),
|
||||||
enableOpacity: false,
|
color: Colors.white
|
||||||
showColorCode: true,
|
),
|
||||||
colorCodeHasColor: true,
|
child: ClipRRect(
|
||||||
pickersEnabled: const <ColorPickerType, bool>{
|
borderRadius: const BorderRadius.all(Radius.circular(4)),
|
||||||
ColorPickerType.wheel: true,
|
child: Wrap(children: AppColor.listColorsPicker
|
||||||
|
.map((color) => _itemColorWidget(context, color))
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||||
|
child: ColorCodeField(
|
||||||
|
color: _currentColor.value,
|
||||||
|
colorCodeHasColor: true,
|
||||||
|
shouldUpdate: _shouldUpdate,
|
||||||
|
onColorChanged: (Color color) {
|
||||||
|
if (AppColor.listColorsPicker.any((element) => element.value == color.value)) {
|
||||||
|
_shouldUpdate = true;
|
||||||
|
_currentColor.value = color;
|
||||||
|
} else {
|
||||||
|
_shouldUpdate = false;
|
||||||
|
_currentColor.value = Colors.black;
|
||||||
|
_colorCode = color;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onEditFocused: (bool editInFocus) {
|
||||||
|
_shouldUpdate = editInFocus ? true : false;
|
||||||
|
},
|
||||||
|
copyPasteBehavior: const ColorPickerCopyPasteBehavior(
|
||||||
|
parseShortHexCode: true,
|
||||||
|
),
|
||||||
|
toolIcons: const ColorPickerActionButtons(
|
||||||
|
dialogActionButtons: true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
},
|
},
|
||||||
copyPasteBehavior: const ColorPickerCopyPasteBehavior(
|
|
||||||
parseShortHexCode: true,
|
|
||||||
),
|
|
||||||
actionButtons: const ColorPickerActionButtons(
|
|
||||||
dialogActionButtons: true,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
actions: <Widget>[
|
actions: <Widget>[
|
||||||
buildButtonWrapText(
|
buildButtonWrapText(
|
||||||
@@ -108,13 +141,42 @@ class ColorPickerDialogBuilder {
|
|||||||
radius: 5,
|
radius: 5,
|
||||||
height: 30,
|
height: 30,
|
||||||
textStyle: const TextStyle(
|
textStyle: const TextStyle(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontWeight: FontWeight.w500),
|
fontWeight: FontWeight.w500),
|
||||||
onTap: () => setColorActionCallback?.call(_colorSelected))
|
onTap: () {
|
||||||
|
if (!_shouldUpdate) {
|
||||||
|
setColorActionCallback?.call(_colorCode);
|
||||||
|
} else {
|
||||||
|
setColorActionCallback?.call(_currentColor.value);
|
||||||
|
}
|
||||||
|
})
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _itemColorWidget(BuildContext context, Color color) {
|
||||||
|
return Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () {
|
||||||
|
_shouldUpdate = true;
|
||||||
|
_currentColor.value = color;
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: color,
|
||||||
|
border: Border.all(
|
||||||
|
color: _currentColor.value == color ? Colors.white : Colors.transparent,
|
||||||
|
width: 8,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
width: 40,
|
||||||
|
height: 40,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,356 @@
|
|||||||
|
import 'package:core/presentation/views/pick_color/color_picker_action_buttons.dart';
|
||||||
|
import 'package:core/presentation/views/pick_color/color_picker_copy_past_behaivor.dart';
|
||||||
|
import 'package:core/presentation/views/pick_color/color_picker_extensions.dart';
|
||||||
|
import 'package:core/presentation/views/pick_color/dry_intrinsic.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
|
/// Color code entry and display field used by the color picker.
|
||||||
|
class ColorCodeField extends StatefulWidget {
|
||||||
|
/// Default const constructor.
|
||||||
|
const ColorCodeField({
|
||||||
|
Key? key,
|
||||||
|
required this.color,
|
||||||
|
this.readOnly = false,
|
||||||
|
required this.onColorChanged,
|
||||||
|
required this.onEditFocused,
|
||||||
|
this.textStyle,
|
||||||
|
this.prefixStyle,
|
||||||
|
this.colorCodeHasColor = false,
|
||||||
|
this.toolIcons = const ColorPickerActionButtons(),
|
||||||
|
this.copyPasteBehavior = const ColorPickerCopyPasteBehavior(),
|
||||||
|
this.enableTooltips = true,
|
||||||
|
this.shouldUpdate = false,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
/// Current color value for the field.
|
||||||
|
final Color color;
|
||||||
|
|
||||||
|
/// Is in read only mode, we should not be able to select either.
|
||||||
|
final bool readOnly;
|
||||||
|
|
||||||
|
/// Color code of the entered color string is returned back in this callback.
|
||||||
|
final ValueChanged<Color> onColorChanged;
|
||||||
|
|
||||||
|
/// The Color code editing field has focus.
|
||||||
|
final ValueChanged<bool> onEditFocused;
|
||||||
|
|
||||||
|
/// TextStyle of the color code display and edit field.
|
||||||
|
///
|
||||||
|
/// Defaults to Theme.of(context).textTheme.bodyMedium;
|
||||||
|
final TextStyle? textStyle;
|
||||||
|
|
||||||
|
/// The TextStyle of the prefix of the color code.
|
||||||
|
///
|
||||||
|
/// Defaults to [textStyle], if not defined.
|
||||||
|
final TextStyle? prefixStyle;
|
||||||
|
|
||||||
|
/// If true then the background of the color code entry field uses the current
|
||||||
|
/// selected color.
|
||||||
|
///
|
||||||
|
/// This makes the color code entry field a larger current color indicator
|
||||||
|
/// area that changes color as the color value is changed.
|
||||||
|
/// The text color of the filed will adjust to for best contrast as will
|
||||||
|
/// the opacity indicator text. Enabling this feature will override any
|
||||||
|
/// color specified in [textStyle] and [prefixStyle] but
|
||||||
|
/// their styles will otherwise be kept as specified.
|
||||||
|
///
|
||||||
|
/// Defaults to false.
|
||||||
|
final bool colorCodeHasColor;
|
||||||
|
|
||||||
|
/// Defines icons for the color picker title bar and its actions.
|
||||||
|
///
|
||||||
|
/// Defaults to ColorPickerToolIcons().
|
||||||
|
final ColorPickerActionButtons toolIcons;
|
||||||
|
|
||||||
|
/// Defines the color picker's copy and paste behavior.
|
||||||
|
///
|
||||||
|
/// Defaults to ColorPickerPasteBehavior().
|
||||||
|
final ColorPickerCopyPasteBehavior copyPasteBehavior;
|
||||||
|
|
||||||
|
/// Controls if tooltips are shown or not
|
||||||
|
///
|
||||||
|
/// Defaults to true.
|
||||||
|
final bool enableTooltips;
|
||||||
|
|
||||||
|
/// Should a change on the color value update the field?
|
||||||
|
///
|
||||||
|
/// If we are just editing text in the control it should not, we just send
|
||||||
|
/// the data out to update any widget using the [color].
|
||||||
|
/// However, when we get a new color due to external action is should update.
|
||||||
|
/// This is similar to the same property on the wheel.
|
||||||
|
///
|
||||||
|
/// Defaults to false.
|
||||||
|
final bool shouldUpdate;
|
||||||
|
|
||||||
|
@override
|
||||||
|
// ignore: library_private_types_in_public_api
|
||||||
|
_ColorCodeFieldState createState() => _ColorCodeFieldState();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Color code display and entry field.
|
||||||
|
class _ColorCodeFieldState extends State<ColorCodeField> {
|
||||||
|
late TextEditingController textController;
|
||||||
|
late FocusNode textFocusNode;
|
||||||
|
late String colorHexCode;
|
||||||
|
late Color color;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
textController = TextEditingController();
|
||||||
|
textFocusNode = FocusNode();
|
||||||
|
color = widget.color;
|
||||||
|
textController.text = color.hex;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
textController.dispose();
|
||||||
|
textFocusNode.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didUpdateWidget(covariant ColorCodeField oldWidget) {
|
||||||
|
super.didUpdateWidget(oldWidget);
|
||||||
|
if (oldWidget.color != widget.color && widget.shouldUpdate) {
|
||||||
|
color = widget.color;
|
||||||
|
textController.text = color.hex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
// The tooltip for copying the color code via the icon button
|
||||||
|
String? copyTooltip;
|
||||||
|
|
||||||
|
if (widget.enableTooltips) {
|
||||||
|
// Get current platform.
|
||||||
|
final TargetPlatform platform = Theme.of(context).platform;
|
||||||
|
// Get the Material localizations.
|
||||||
|
final MaterialLocalizations translate = MaterialLocalizations.of(context);
|
||||||
|
// If shortcut key enabled, make a shortcut platform aware info tooltip.
|
||||||
|
String copyKeyTooltip = '';
|
||||||
|
if (widget.copyPasteBehavior.ctrlC) {
|
||||||
|
copyKeyTooltip = platformControlKey(platform, 'C');
|
||||||
|
}
|
||||||
|
// Make the Copy tooltip.
|
||||||
|
copyTooltip =
|
||||||
|
(widget.copyPasteBehavior.copyTooltip ?? translate.copyButtonLabel) +
|
||||||
|
copyKeyTooltip;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define opinionated styles for the color code display and entry field.
|
||||||
|
final bool isLight = Theme.of(context).brightness == Brightness.light;
|
||||||
|
final Color fieldBackground = widget.colorCodeHasColor
|
||||||
|
? color
|
||||||
|
: isLight
|
||||||
|
? Colors.black.withAlpha(11)
|
||||||
|
: Colors.white.withAlpha(33);
|
||||||
|
|
||||||
|
final bool isLightBackground =
|
||||||
|
ThemeData.estimateBrightnessForColor(fieldBackground) ==
|
||||||
|
Brightness.light;
|
||||||
|
final Color textColor = isLight
|
||||||
|
? (isLightBackground || fieldBackground.opacity < 0.5)
|
||||||
|
? Colors.black
|
||||||
|
: Colors.white
|
||||||
|
: (!isLightBackground || fieldBackground.opacity < 0.5)
|
||||||
|
? Colors.white
|
||||||
|
: Colors.black;
|
||||||
|
|
||||||
|
final Color fieldBorder =
|
||||||
|
isLight ? Colors.black.withAlpha(33) : Colors.white.withAlpha(55);
|
||||||
|
|
||||||
|
// Set the default text style to bodyMedium if not given.
|
||||||
|
TextStyle effectiveStyle = widget.textStyle ??
|
||||||
|
Theme.of(context).textTheme.bodyMedium ??
|
||||||
|
const TextStyle(fontSize: 14);
|
||||||
|
|
||||||
|
TextStyle effectivePrefixStyle = widget.prefixStyle ?? effectiveStyle;
|
||||||
|
|
||||||
|
if (widget.colorCodeHasColor) {
|
||||||
|
effectiveStyle = effectiveStyle.copyWith(color: textColor);
|
||||||
|
effectivePrefixStyle = effectivePrefixStyle.copyWith(color: textColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute color code field size based on the used font size. Might not
|
||||||
|
// always be ideal, but with normal fonts and sizes they have been tested to
|
||||||
|
// work well enough visually and to always have room for "DDDDDD", which is
|
||||||
|
// usually the widest possible entry string.
|
||||||
|
final double fontSize = effectiveStyle.fontSize ?? 14.0;
|
||||||
|
final double iconSize = fontSize * 1.1;
|
||||||
|
final double borderRadius = fontSize * 1.2;
|
||||||
|
final double fieldWidth = fontSize * 10;
|
||||||
|
|
||||||
|
return SizedBox(
|
||||||
|
width: fieldWidth,
|
||||||
|
// The custom DryIntrinsicWidth layout widget is used due to issue:
|
||||||
|
// https://github.com/flutter/flutter/issues/71687
|
||||||
|
child: DryIntrinsicWidth(
|
||||||
|
child: Focus(
|
||||||
|
// Tell the parent when the text edit field has focus.
|
||||||
|
onFocusChange: widget.onEditFocused,
|
||||||
|
child: TextField(
|
||||||
|
enabled: true,
|
||||||
|
readOnly: widget.readOnly,
|
||||||
|
enableInteractiveSelection: !widget.readOnly,
|
||||||
|
controller: textController,
|
||||||
|
focusNode: textFocusNode,
|
||||||
|
maxLength: 6,
|
||||||
|
maxLengthEnforcement: MaxLengthEnforcement.enforced,
|
||||||
|
// Remove line that shows entered chars when maxLength is used.
|
||||||
|
buildCounter: (BuildContext context,
|
||||||
|
{required int currentLength,
|
||||||
|
int? maxLength,
|
||||||
|
required bool isFocused}) =>
|
||||||
|
null,
|
||||||
|
style: effectiveStyle,
|
||||||
|
// Only affects the type of keyboard shown on devices, does not
|
||||||
|
// make the input uppercase.
|
||||||
|
textCapitalization: TextCapitalization.characters,
|
||||||
|
// These input formatters limits the input to only valid chars for
|
||||||
|
// a hex color code, and we also convert them to uppercase.
|
||||||
|
inputFormatters: <TextInputFormatter>[
|
||||||
|
FilteringTextInputFormatter.allow(RegExp('[a-fA-F0-9]')),
|
||||||
|
_UpperCaseTextFormatter(),
|
||||||
|
],
|
||||||
|
decoration: InputDecoration(
|
||||||
|
suffixIcon: widget.copyPasteBehavior.editFieldCopyButton
|
||||||
|
? IconButton(
|
||||||
|
icon: Icon(widget.copyPasteBehavior.copyIcon),
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
tooltip: copyTooltip,
|
||||||
|
iconSize: iconSize,
|
||||||
|
splashRadius: borderRadius,
|
||||||
|
color: effectiveStyle.color,
|
||||||
|
constraints: const BoxConstraints(),
|
||||||
|
onPressed: _setClipboard,
|
||||||
|
)
|
||||||
|
: SizedBox(height: borderRadius * 2),
|
||||||
|
suffixIconConstraints: BoxConstraints(
|
||||||
|
minHeight: borderRadius * 2,
|
||||||
|
minWidth: borderRadius * 2,
|
||||||
|
),
|
||||||
|
isDense: true,
|
||||||
|
contentPadding: EdgeInsetsDirectional.only(start: fontSize),
|
||||||
|
prefixText: _editColorPrefix,
|
||||||
|
prefixStyle: effectivePrefixStyle,
|
||||||
|
filled: true,
|
||||||
|
fillColor: fieldBackground,
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderSide: BorderSide.none,
|
||||||
|
borderRadius: BorderRadius.circular(borderRadius),
|
||||||
|
),
|
||||||
|
focusedBorder: OutlineInputBorder(
|
||||||
|
borderSide: BorderSide(
|
||||||
|
color: fieldBorder,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(borderRadius),
|
||||||
|
),
|
||||||
|
enabledBorder: OutlineInputBorder(
|
||||||
|
borderSide: BorderSide(
|
||||||
|
color: fieldBorder,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(borderRadius),
|
||||||
|
),
|
||||||
|
disabledBorder: OutlineInputBorder(
|
||||||
|
borderSide: BorderSide(
|
||||||
|
color: fieldBorder,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(borderRadius),
|
||||||
|
),
|
||||||
|
errorBorder: OutlineInputBorder(
|
||||||
|
borderSide: BorderSide(
|
||||||
|
color: fieldBorder,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(borderRadius),
|
||||||
|
),
|
||||||
|
focusedErrorBorder: OutlineInputBorder(
|
||||||
|
borderSide: BorderSide(
|
||||||
|
color: fieldBorder,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(borderRadius),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
//
|
||||||
|
onChanged: (String textColor) {
|
||||||
|
setState(() {
|
||||||
|
color = textColor
|
||||||
|
.toColorShort(widget.copyPasteBehavior.parseShortHexCode)
|
||||||
|
.withOpacity(color.opacity);
|
||||||
|
});
|
||||||
|
widget.onColorChanged(color);
|
||||||
|
},
|
||||||
|
onEditingComplete: () {
|
||||||
|
setState(() {
|
||||||
|
color = textController.text
|
||||||
|
.toColorShort(widget.copyPasteBehavior.parseShortHexCode)
|
||||||
|
.withOpacity(color.opacity);
|
||||||
|
});
|
||||||
|
textController.text = color.hex;
|
||||||
|
widget.onColorChanged(color);
|
||||||
|
textFocusNode.unfocus();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set current selected color values as a String on the Clipboard in the
|
||||||
|
// currently configured format.
|
||||||
|
Future<void> _setClipboard() async {
|
||||||
|
String colorString = '00000000';
|
||||||
|
switch (widget.copyPasteBehavior.copyFormat) {
|
||||||
|
case ColorPickerCopyFormat.dartCode:
|
||||||
|
colorString = '0x${color.hexAlpha}';
|
||||||
|
break;
|
||||||
|
case ColorPickerCopyFormat.hexRRGGBB:
|
||||||
|
colorString = color.hex;
|
||||||
|
break;
|
||||||
|
case ColorPickerCopyFormat.hexAARRGGBB:
|
||||||
|
colorString = color.hexAlpha;
|
||||||
|
break;
|
||||||
|
case ColorPickerCopyFormat.numHexRRGGBB:
|
||||||
|
colorString = '#${color.hex}';
|
||||||
|
break;
|
||||||
|
case ColorPickerCopyFormat.numHexAARRGGBB:
|
||||||
|
colorString = '#${color.hexAlpha}';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
final ClipboardData data = ClipboardData(text: colorString);
|
||||||
|
await Clipboard.setData(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the current selected color prefix format for the input field.
|
||||||
|
// The prefix in the input field matches the set copy format.
|
||||||
|
String get _editColorPrefix {
|
||||||
|
final String alphaValue = color.hexAlpha.substring(0, 2);
|
||||||
|
switch (widget.copyPasteBehavior.copyFormat) {
|
||||||
|
case ColorPickerCopyFormat.dartCode:
|
||||||
|
return '0x$alphaValue';
|
||||||
|
case ColorPickerCopyFormat.hexRRGGBB:
|
||||||
|
return ' ';
|
||||||
|
case ColorPickerCopyFormat.hexAARRGGBB:
|
||||||
|
return ' $alphaValue';
|
||||||
|
case ColorPickerCopyFormat.numHexRRGGBB:
|
||||||
|
return ' #';
|
||||||
|
case ColorPickerCopyFormat.numHexAARRGGBB:
|
||||||
|
return ' #$alphaValue';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This TextField formatter converts all input to uppercase.
|
||||||
|
class _UpperCaseTextFormatter extends TextInputFormatter {
|
||||||
|
@override
|
||||||
|
TextEditingValue formatEditUpdate(
|
||||||
|
TextEditingValue oldValue, TextEditingValue newValue) {
|
||||||
|
return TextEditingValue(
|
||||||
|
text: newValue.text.toUpperCase(),
|
||||||
|
selection: newValue.selection,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,427 @@
|
|||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// Type of button used for OK or Cancel action button on a FlexColorPicker
|
||||||
|
/// dialog.
|
||||||
|
enum ColorPickerActionButtonType {
|
||||||
|
/// Use a [TextButton] button.
|
||||||
|
text,
|
||||||
|
|
||||||
|
/// Use [OutlinedButton] button.
|
||||||
|
outlined,
|
||||||
|
|
||||||
|
/// Use [ElevatedButton] button.
|
||||||
|
elevated,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Used to define the order of OK and Cancel buttons on the FlexColorPicker
|
||||||
|
/// dialog.
|
||||||
|
enum ColorPickerActionButtonOrder {
|
||||||
|
/// OK is the right button.
|
||||||
|
okIsRight,
|
||||||
|
|
||||||
|
/// OK is the left button.
|
||||||
|
okIsLeft,
|
||||||
|
|
||||||
|
/// OK is on the place where it belongs depending on used platform
|
||||||
|
///
|
||||||
|
/// Right:
|
||||||
|
///
|
||||||
|
/// Cancel - OK : macOS, iOS, Android, Linux, Fuchsia
|
||||||
|
///
|
||||||
|
/// Left:
|
||||||
|
///
|
||||||
|
/// OK - Cancel : Windows
|
||||||
|
adaptive,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Defines the FlexColorPicker OK and Cancel actions buttons and their style.
|
||||||
|
///
|
||||||
|
/// You can define if action buttons are on a top toolbar or only
|
||||||
|
/// in the dialog bottom. The toolbar buttons are plain icon only
|
||||||
|
/// buttons. For the Dialog buttons you can choose between
|
||||||
|
/// [TextButton], [OutlinedButton] and [ElevatedButton].
|
||||||
|
///
|
||||||
|
/// The used icons can be changed form default ones, as can used tooltips.
|
||||||
|
///
|
||||||
|
/// Used by FlexColorPicker to define action buttons and their style.
|
||||||
|
@immutable
|
||||||
|
class ColorPickerActionButtons with Diagnosticable {
|
||||||
|
/// Default const constructor.
|
||||||
|
const ColorPickerActionButtons({
|
||||||
|
this.okButton = false,
|
||||||
|
this.closeButton = false,
|
||||||
|
this.okIcon = Icons.check,
|
||||||
|
this.closeIcon = Icons.close,
|
||||||
|
this.closeIsLast = true,
|
||||||
|
this.okTooltip,
|
||||||
|
this.closeTooltip,
|
||||||
|
this.closeTooltipIsClose = true,
|
||||||
|
this.toolIconsThemeData,
|
||||||
|
this.visualDensity,
|
||||||
|
this.padding = EdgeInsets.zero,
|
||||||
|
this.alignment = Alignment.center,
|
||||||
|
this.splashRadius = 24,
|
||||||
|
this.constraints = const BoxConstraints(minHeight: 42, minWidth: 42),
|
||||||
|
this.dialogActionButtons = true,
|
||||||
|
this.dialogActionOrder = ColorPickerActionButtonOrder.okIsRight,
|
||||||
|
this.dialogActionIcons = false,
|
||||||
|
this.dialogCancelButtonLabel,
|
||||||
|
this.dialogCancelButtonType = ColorPickerActionButtonType.text,
|
||||||
|
this.dialogOkButtonLabel,
|
||||||
|
this.dialogOkButtonType = ColorPickerActionButtonType.text,
|
||||||
|
this.useRootNavigator = true,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Dialog has an OK icon button on top toolbar to select active color
|
||||||
|
/// and close the color picker dialog.
|
||||||
|
///
|
||||||
|
/// This will pop the current top route on navigation stack and return true.
|
||||||
|
/// Only enable this toolbar button when you are using the picker in a dialog.
|
||||||
|
///
|
||||||
|
/// Defaults to false.
|
||||||
|
final bool okButton;
|
||||||
|
|
||||||
|
/// Dialog has a Close icon button on top toolbar to cancel and close the
|
||||||
|
/// color picker dialog.
|
||||||
|
///
|
||||||
|
/// This will pop the current top route on navigation stack and return false.
|
||||||
|
/// Only enable this toolbar button when you are using the picker in a dialog.
|
||||||
|
///
|
||||||
|
/// Defaults to false.
|
||||||
|
final bool closeButton;
|
||||||
|
|
||||||
|
/// Icon used for the OK action icon in the color picker dialog.
|
||||||
|
///
|
||||||
|
/// Used both on the toolbar icon button and as a prefix icon on the bottom
|
||||||
|
/// action button, when icons have been enabled for the bottom action buttons.
|
||||||
|
///
|
||||||
|
/// Defaults to [Icons.check].
|
||||||
|
final IconData okIcon;
|
||||||
|
|
||||||
|
/// Icon used for the close action icon in the color picker dialog.
|
||||||
|
///
|
||||||
|
/// Used both on the toolbar icon button and as a prefix icon on the bottom
|
||||||
|
/// action button, when prefix icons have been enabled for the bottom
|
||||||
|
/// action buttons.
|
||||||
|
///
|
||||||
|
/// Defaults to [Icons.close].
|
||||||
|
final IconData closeIcon;
|
||||||
|
|
||||||
|
/// Close icon is last in the color picker title toolbar.
|
||||||
|
///
|
||||||
|
/// Set to false to swap the order of the OK and Close toolbar icons.
|
||||||
|
///
|
||||||
|
/// Defaults to true, which results in the close 'x' icon being in upper
|
||||||
|
/// end corner of the picker dialog.
|
||||||
|
///
|
||||||
|
/// Based on Material guide, the bottom action buttons in a dialog are always
|
||||||
|
/// in the Cancel-OK order. If the top toolbar buttons are used at the
|
||||||
|
/// same time, this value can be set to false to show the top toolbar buttons
|
||||||
|
/// in the same order as the Material bottom dialog action buttons.
|
||||||
|
///
|
||||||
|
/// The recommendation is to not use the top and bottom action buttons at the
|
||||||
|
/// same time, but rather select one of the two options. The API does
|
||||||
|
/// however allow using both or even a mix and match. It is possible to show
|
||||||
|
/// **Cancel** and **OK** actions at the bottom of the dialog, and also add
|
||||||
|
/// just an 'x' icon in the upper end corner of the dialog that also
|
||||||
|
/// cancel-closes the dialog as expected.
|
||||||
|
final bool closeIsLast;
|
||||||
|
|
||||||
|
/// Label used as tooltip for OK toolbar button.
|
||||||
|
///
|
||||||
|
/// Provide your own or use the default material localization label.
|
||||||
|
///
|
||||||
|
/// Defaults to MaterialLocalizations.of(context).okButtonLabel.
|
||||||
|
final String? okTooltip;
|
||||||
|
|
||||||
|
/// Label used as tooltip for close toolbar button.
|
||||||
|
///
|
||||||
|
/// Provide your own or use the default material localization label.
|
||||||
|
///
|
||||||
|
/// Defaults to MaterialLocalizations.of(context).cancelButtonLabel if
|
||||||
|
/// closeTooltipIsClose is true. If false it defaults to
|
||||||
|
/// MaterialLocalizations.of(context).cancelButtonLabel.
|
||||||
|
final String? closeTooltip;
|
||||||
|
|
||||||
|
/// Close toolbar icon button uses "close" material localization as
|
||||||
|
/// default label.
|
||||||
|
///
|
||||||
|
/// If set to false, it uses "cancel" localization. You can still also
|
||||||
|
/// provide your own custom tooltip. This toggle is just convenient
|
||||||
|
/// if you want to call the top toolbar buttons that closes/cancels the
|
||||||
|
/// dialog "Close" (default) or "Cancel" like on a typical dialog bottom.
|
||||||
|
/// The "Close" tooltip, is more in line with the 'x' icon,
|
||||||
|
/// that here also closes the dialog with the dismiss result.
|
||||||
|
///
|
||||||
|
/// Defaults to true.
|
||||||
|
final bool closeTooltipIsClose;
|
||||||
|
|
||||||
|
/// The theme for the toolbar icons.
|
||||||
|
///
|
||||||
|
/// The toolbar is compact, so icons are small by design.
|
||||||
|
///
|
||||||
|
/// Effective style will uses any none null property in the passed in
|
||||||
|
/// [IconThemeData]. If the passed in theme data is null, or any property in
|
||||||
|
/// it, is null, then the following fallback defaults are used:
|
||||||
|
///
|
||||||
|
/// color: remains null, so default [IconThemeData] color behavior is kept.
|
||||||
|
/// size: 22
|
||||||
|
/// opacity: 0.90
|
||||||
|
///
|
||||||
|
/// NOTE: This theme on purpose does not merge any ambient theme. To keep the
|
||||||
|
/// compact fallback values as its default style. To change them you have
|
||||||
|
/// to pass in the desired [IconThemeData].
|
||||||
|
final IconThemeData? toolIconsThemeData;
|
||||||
|
|
||||||
|
/// Defines how compact the toolbar icon button layout will be.
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [ThemeData.visualDensity], which specifies the [visualDensity] for all
|
||||||
|
/// widgets within a [Theme].
|
||||||
|
final VisualDensity? visualDensity;
|
||||||
|
|
||||||
|
/// The padding around the toolbar icon buttons. The entire padded icon will
|
||||||
|
/// react to input gestures.
|
||||||
|
///
|
||||||
|
/// Defaults to const EdgeInsets.all(0),
|
||||||
|
final EdgeInsetsGeometry padding;
|
||||||
|
|
||||||
|
/// Defines how the icon is positioned within the IconButton.
|
||||||
|
///
|
||||||
|
/// Defaults to [Alignment.center].
|
||||||
|
final AlignmentGeometry alignment;
|
||||||
|
|
||||||
|
/// The splash radius on the toolbar icon buttons.
|
||||||
|
///
|
||||||
|
/// Defaults to 24.
|
||||||
|
final double splashRadius;
|
||||||
|
|
||||||
|
/// Optional size constraints for the icon button.
|
||||||
|
///
|
||||||
|
/// Defaults to: const BoxConstraints(minHeight: 34, minWidth: 34),
|
||||||
|
final BoxConstraints constraints;
|
||||||
|
|
||||||
|
/// If set to false, the bottom dialog action buttons att the bottom
|
||||||
|
/// are removed.
|
||||||
|
///
|
||||||
|
/// If you remove the bottom dialog action buttons, make sure to enabled the
|
||||||
|
/// ones in the dialog toolbar.
|
||||||
|
///
|
||||||
|
/// Defaults to true.
|
||||||
|
final bool dialogActionButtons;
|
||||||
|
|
||||||
|
/// Defines the order of the OK and Cancel actions buttons at the bottom
|
||||||
|
/// of the dialog.
|
||||||
|
///
|
||||||
|
/// Options are:
|
||||||
|
///
|
||||||
|
/// * okIsRight: OK is the right button.
|
||||||
|
/// * okIsLeft: OK is the left button.
|
||||||
|
/// * adaptive: OK is on the place where it belongs on used platform
|
||||||
|
/// Right: Cancel - OK : macOS, iOS, Android, Linux, Fuchsia
|
||||||
|
/// Left : OK - Cancel : Windows
|
||||||
|
///
|
||||||
|
/// Defaults to "okIsRight". Prefer using "adaptive", but defaults to
|
||||||
|
/// "okIsRight" in order to not break past behavior.
|
||||||
|
final ColorPickerActionButtonOrder dialogActionOrder;
|
||||||
|
|
||||||
|
/// If set to true, the dialog bottom action buttons will be prefixed with
|
||||||
|
/// an icon.
|
||||||
|
///
|
||||||
|
/// The cancel buttons will get the [closeIcon] and the ok button will get
|
||||||
|
/// prefixed with the [okIcon].
|
||||||
|
///
|
||||||
|
/// Defaults to false.
|
||||||
|
final bool dialogActionIcons;
|
||||||
|
|
||||||
|
/// Color picker dialog cancel button label.
|
||||||
|
///
|
||||||
|
/// Label shown on the button for cancelling the color picking and closing
|
||||||
|
/// the dialog and returning false.
|
||||||
|
///
|
||||||
|
/// If null, defaults to MaterialLocalizations.of(context).cancelButtonLabel.
|
||||||
|
final String? dialogCancelButtonLabel;
|
||||||
|
|
||||||
|
/// Type of button used in the dialog for the Cancel button.
|
||||||
|
///
|
||||||
|
/// The button will be themed based on closest ambient theme.
|
||||||
|
///
|
||||||
|
/// Defaults to [ColorPickerActionButtonType.text] resulting in [TextButton].
|
||||||
|
final ColorPickerActionButtonType dialogCancelButtonType;
|
||||||
|
|
||||||
|
/// Color picker dialog OK button label.
|
||||||
|
///
|
||||||
|
/// Label shown on bottom action button for selecting the current color in
|
||||||
|
/// the color picker dialog and closing the dialog and returning true.
|
||||||
|
///
|
||||||
|
/// If null, defaults to MaterialLocalizations.of(context).okButtonLabel.
|
||||||
|
final String? dialogOkButtonLabel;
|
||||||
|
|
||||||
|
/// Type of button used in the dialog for the OK button.
|
||||||
|
///
|
||||||
|
/// The button will be themed based on closest ambient theme.
|
||||||
|
///
|
||||||
|
/// Defaults to [ColorPickerActionButtonType.text] resulting in [TextButton].
|
||||||
|
final ColorPickerActionButtonType dialogOkButtonType;
|
||||||
|
|
||||||
|
/// The `useRootNavigator` argument is used to determine whether to push the
|
||||||
|
/// ColorPicker dialog to the [Navigator] furthest from or nearest to the
|
||||||
|
/// given `context`.
|
||||||
|
///
|
||||||
|
/// By default, `useRootNavigator` is `true` and the dialog route created
|
||||||
|
/// by build of ColorPicker dialogs are on the root.
|
||||||
|
///
|
||||||
|
/// This setting was moved here in version 2.1.0 in order to make the
|
||||||
|
/// property accessible by Navigator pop functions both in the ColorPicker
|
||||||
|
/// widget itself, as well as built-in dialogs that uses the ColorPicker.
|
||||||
|
final bool useRootNavigator;
|
||||||
|
|
||||||
|
/// Copy the object with one or more provided properties changed.
|
||||||
|
ColorPickerActionButtons copyWith({
|
||||||
|
bool? okButton,
|
||||||
|
bool? closeButton,
|
||||||
|
IconData? okIcon,
|
||||||
|
IconData? closeIcon,
|
||||||
|
bool? closeIsLast,
|
||||||
|
String? okTooltip,
|
||||||
|
String? closeTooltip,
|
||||||
|
bool? closeTooltipIsClose,
|
||||||
|
IconThemeData? toolIconsThemeData,
|
||||||
|
VisualDensity? visualDensity,
|
||||||
|
EdgeInsetsGeometry? padding,
|
||||||
|
AlignmentGeometry? alignment,
|
||||||
|
double? splashRadius,
|
||||||
|
BoxConstraints? constraints,
|
||||||
|
bool? dialogActionButtons,
|
||||||
|
ColorPickerActionButtonOrder? dialogActionOrder,
|
||||||
|
bool? dialogActionIcons,
|
||||||
|
String? dialogCancelButtonLabel,
|
||||||
|
ColorPickerActionButtonType? dialogCancelButtonType,
|
||||||
|
String? dialogOkButtonLabel,
|
||||||
|
ColorPickerActionButtonType? dialogOkButtonType,
|
||||||
|
bool? useRootNavigator,
|
||||||
|
}) {
|
||||||
|
return ColorPickerActionButtons(
|
||||||
|
okButton: okButton ?? this.okButton,
|
||||||
|
closeButton: closeButton ?? this.closeButton,
|
||||||
|
okIcon: okIcon ?? this.okIcon,
|
||||||
|
closeIcon: closeIcon ?? this.closeIcon,
|
||||||
|
closeIsLast: closeIsLast ?? this.closeIsLast,
|
||||||
|
okTooltip: okTooltip ?? this.okTooltip,
|
||||||
|
closeTooltip: closeTooltip ?? this.closeTooltip,
|
||||||
|
closeTooltipIsClose: closeTooltipIsClose ?? this.closeTooltipIsClose,
|
||||||
|
toolIconsThemeData: toolIconsThemeData ?? this.toolIconsThemeData,
|
||||||
|
visualDensity: visualDensity ?? this.visualDensity,
|
||||||
|
padding: padding ?? this.padding,
|
||||||
|
alignment: alignment ?? this.alignment,
|
||||||
|
splashRadius: splashRadius ?? this.splashRadius,
|
||||||
|
constraints: constraints ?? this.constraints,
|
||||||
|
dialogActionButtons: dialogActionButtons ?? this.dialogActionButtons,
|
||||||
|
dialogActionOrder: dialogActionOrder ?? this.dialogActionOrder,
|
||||||
|
dialogActionIcons: dialogActionIcons ?? this.dialogActionIcons,
|
||||||
|
dialogCancelButtonLabel:
|
||||||
|
dialogCancelButtonLabel ?? this.dialogCancelButtonLabel,
|
||||||
|
dialogCancelButtonType:
|
||||||
|
dialogCancelButtonType ?? this.dialogCancelButtonType,
|
||||||
|
dialogOkButtonLabel: dialogOkButtonLabel ?? this.dialogOkButtonLabel,
|
||||||
|
dialogOkButtonType: dialogOkButtonType ?? this.dialogOkButtonType,
|
||||||
|
useRootNavigator: useRootNavigator ?? this.useRootNavigator,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
if (identical(this, other)) return true;
|
||||||
|
if (other.runtimeType != runtimeType) return false;
|
||||||
|
return other is ColorPickerActionButtons &&
|
||||||
|
runtimeType == other.runtimeType &&
|
||||||
|
okButton == other.okButton &&
|
||||||
|
closeButton == other.closeButton &&
|
||||||
|
okIcon == other.okIcon &&
|
||||||
|
closeIcon == other.closeIcon &&
|
||||||
|
closeIsLast == other.closeIsLast &&
|
||||||
|
okTooltip == other.okTooltip &&
|
||||||
|
closeTooltip == other.closeTooltip &&
|
||||||
|
closeTooltipIsClose == other.closeTooltipIsClose &&
|
||||||
|
toolIconsThemeData == other.toolIconsThemeData &&
|
||||||
|
visualDensity == other.visualDensity &&
|
||||||
|
padding == other.padding &&
|
||||||
|
alignment == other.alignment &&
|
||||||
|
splashRadius == other.splashRadius &&
|
||||||
|
constraints == other.constraints &&
|
||||||
|
dialogActionButtons == other.dialogActionButtons &&
|
||||||
|
dialogActionOrder == other.dialogActionOrder &&
|
||||||
|
dialogActionIcons == other.dialogActionIcons &&
|
||||||
|
dialogCancelButtonLabel == other.dialogCancelButtonLabel &&
|
||||||
|
dialogCancelButtonType == other.dialogCancelButtonType &&
|
||||||
|
dialogOkButtonLabel == other.dialogOkButtonLabel &&
|
||||||
|
dialogOkButtonType == other.dialogOkButtonType &&
|
||||||
|
useRootNavigator == other.useRootNavigator;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hashAll(<Object?>[
|
||||||
|
okButton,
|
||||||
|
closeButton,
|
||||||
|
okIcon,
|
||||||
|
closeIcon,
|
||||||
|
closeIsLast,
|
||||||
|
okTooltip,
|
||||||
|
closeTooltip,
|
||||||
|
closeTooltipIsClose,
|
||||||
|
toolIconsThemeData,
|
||||||
|
visualDensity,
|
||||||
|
padding,
|
||||||
|
alignment,
|
||||||
|
splashRadius,
|
||||||
|
constraints,
|
||||||
|
dialogActionButtons,
|
||||||
|
dialogActionOrder,
|
||||||
|
dialogActionIcons,
|
||||||
|
dialogCancelButtonLabel,
|
||||||
|
dialogCancelButtonType,
|
||||||
|
dialogOkButtonLabel,
|
||||||
|
dialogOkButtonType,
|
||||||
|
useRootNavigator,
|
||||||
|
]);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||||
|
super.debugFillProperties(properties);
|
||||||
|
properties.add(DiagnosticsProperty<bool>('okButton', okButton));
|
||||||
|
properties.add(DiagnosticsProperty<bool>('closeButton', closeButton));
|
||||||
|
properties.add(DiagnosticsProperty<IconData>('okIcon', okIcon));
|
||||||
|
properties.add(DiagnosticsProperty<IconData>('closeIcon', closeIcon));
|
||||||
|
properties.add(DiagnosticsProperty<bool>('closeIsLast', closeIsLast));
|
||||||
|
properties.add(StringProperty('okTooltip', okTooltip));
|
||||||
|
properties.add(StringProperty('closeTooltip', closeTooltip));
|
||||||
|
properties.add(
|
||||||
|
DiagnosticsProperty<bool>('closeTooltipIsClose', closeTooltipIsClose));
|
||||||
|
properties.add(DiagnosticsProperty<IconThemeData?>(
|
||||||
|
'toolIconsThemeData', toolIconsThemeData));
|
||||||
|
properties.add(
|
||||||
|
DiagnosticsProperty<VisualDensity?>('visualDensity', visualDensity));
|
||||||
|
properties
|
||||||
|
.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment));
|
||||||
|
properties.add(DoubleProperty('splashRadius', splashRadius));
|
||||||
|
properties
|
||||||
|
.add(DiagnosticsProperty<BoxConstraints?>('constraints', constraints));
|
||||||
|
properties.add(
|
||||||
|
DiagnosticsProperty<bool>('dialogActionButtons', dialogActionButtons));
|
||||||
|
properties.add(EnumProperty<ColorPickerActionButtonOrder>(
|
||||||
|
'dialogActionOrder', dialogActionOrder));
|
||||||
|
properties
|
||||||
|
.add(DiagnosticsProperty<bool>('dialogActionIcons', dialogActionIcons));
|
||||||
|
properties.add(
|
||||||
|
StringProperty('dialogCancelButtonLabel', dialogCancelButtonLabel));
|
||||||
|
properties.add(EnumProperty<ColorPickerActionButtonType>(
|
||||||
|
'dialogCancelButtonType', dialogCancelButtonType));
|
||||||
|
properties.add(StringProperty('dialogOkButtonLabel', dialogOkButtonLabel));
|
||||||
|
properties.add(EnumProperty<ColorPickerActionButtonType>(
|
||||||
|
'dialogOkButtonType', dialogOkButtonType));
|
||||||
|
properties
|
||||||
|
.add(DiagnosticsProperty<bool>('useRootNavigator', useRootNavigator));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,511 @@
|
|||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// Enum that controls the RGB string format of the copied color value.
|
||||||
|
///
|
||||||
|
/// When you copy a [Color] value from the color picker, this enum is
|
||||||
|
/// used to configure the desired default format of the received RGB string.
|
||||||
|
///
|
||||||
|
/// If you have opacity enabled for the picker, the alpha value will not
|
||||||
|
/// be included in the copied RGB string if you use a format that does
|
||||||
|
/// not include the alpha value.
|
||||||
|
///
|
||||||
|
/// When you paste a color string value into the color picker, it can
|
||||||
|
/// automatically parse a string from any of the available RGB strings formats
|
||||||
|
/// to a Dart and Flutter [Color] object, regardless of what the copy format is.
|
||||||
|
/// Additionally the pasted color value can be in the 3-char short RGB hex
|
||||||
|
/// format, it will also be correctly parsed to its [Color] value, provided
|
||||||
|
/// that [ColorPickerCopyPasteBehavior.parseShortHexCode] is true.
|
||||||
|
enum ColorPickerCopyFormat {
|
||||||
|
/// In Flutter/Dart Hex RGB format '0xAARRGGBB'.
|
||||||
|
dartCode,
|
||||||
|
|
||||||
|
/// Hex RGB format with no alpha 'RRGGBB'.
|
||||||
|
hexRRGGBB,
|
||||||
|
|
||||||
|
/// Hex RGB format with alpha 'AARRGGBB'.
|
||||||
|
hexAARRGGBB,
|
||||||
|
|
||||||
|
/// Web Hex RGB format with leading num # sign and no alpha '#RRGGBB'.
|
||||||
|
numHexRRGGBB,
|
||||||
|
|
||||||
|
/// Web Hex RGB format with leading num # sign and alpha '#AARRGGBB'.
|
||||||
|
numHexAARRGGBB,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Used by FlexColorPicker to define how copy-paste operations behave.
|
||||||
|
///
|
||||||
|
/// * Copy and paste action buttons in the top toolbar.
|
||||||
|
/// * Long press and/or right click copy and paste context menu.
|
||||||
|
/// * Ctrl-C and Ctrl-V keyboard shortcuts, also when not in edit field.
|
||||||
|
/// Keyboard shortcuts automatically uses Command instead of Ctrl on macOS.
|
||||||
|
/// * A copy color action button in the code entry and display field.
|
||||||
|
///
|
||||||
|
/// You can also:
|
||||||
|
///
|
||||||
|
/// * Define default result RGB string format of a copy command.
|
||||||
|
/// * Define icons for copy and paste action buttons.
|
||||||
|
/// * Define icon theme's for the copy and paste icons.
|
||||||
|
/// * Define paste color string parsing error feedback type and message if used.
|
||||||
|
/// * Modify the tooltips for copy and paste buttons.
|
||||||
|
///
|
||||||
|
/// Paste operation supports all RGB string formats defined by
|
||||||
|
/// [ColorPickerCopyFormat], but copy format is only in selected
|
||||||
|
/// [copyFormat].
|
||||||
|
@immutable
|
||||||
|
class ColorPickerCopyPasteBehavior with Diagnosticable {
|
||||||
|
/// Default constructor
|
||||||
|
const ColorPickerCopyPasteBehavior(
|
||||||
|
{this.ctrlC = true,
|
||||||
|
this.ctrlV = true,
|
||||||
|
this.autoFocus = true,
|
||||||
|
this.copyButton = false,
|
||||||
|
this.pasteButton = false,
|
||||||
|
this.copyIcon = Icons.copy,
|
||||||
|
this.pasteIcon = Icons.paste,
|
||||||
|
this.copyTooltip,
|
||||||
|
this.pasteTooltip,
|
||||||
|
this.copyFormat = ColorPickerCopyFormat.dartCode,
|
||||||
|
this.longPressMenu = false,
|
||||||
|
this.secondaryMenu = false,
|
||||||
|
this.secondaryOnDesktopLongOnDevice = false,
|
||||||
|
this.secondaryOnDesktopLongOnDeviceAndWeb = false,
|
||||||
|
this.editFieldCopyButton = true,
|
||||||
|
this.menuIconThemeData,
|
||||||
|
this.menuThemeData,
|
||||||
|
this.menuWidth = 80,
|
||||||
|
this.menuItemHeight = 30,
|
||||||
|
this.snackBarParseError = false,
|
||||||
|
this.snackBarMessage,
|
||||||
|
this.snackBarDuration = const Duration(milliseconds: 1800),
|
||||||
|
this.feedbackParseError = false,
|
||||||
|
this.parseShortHexCode = false,
|
||||||
|
this.editUsesParsedPaste = false});
|
||||||
|
|
||||||
|
/// A keyboard CMD/CTRL-C press will copy the clipboard into the picker.
|
||||||
|
///
|
||||||
|
/// When enabled, this keyboard copy color shortcut works when the
|
||||||
|
/// ColorPicker and one of its focusable widgets have focus. Those include
|
||||||
|
/// color indicator, color field, buttons, opacity slider and the picker
|
||||||
|
/// selector as well as the color wheel.
|
||||||
|
///
|
||||||
|
/// Defaults to true.
|
||||||
|
final bool ctrlC;
|
||||||
|
|
||||||
|
/// A keyboard CMD/CTRL-V press will paste the clipboard into the picker.
|
||||||
|
///
|
||||||
|
/// When enabled, this keyboard copy color shortcut works when the
|
||||||
|
/// ColorPicker and one of its focusable widgets have focus. Those include
|
||||||
|
/// color indicator, color field, buttons, opacity slider and the picker
|
||||||
|
/// selector as well as the color wheel.
|
||||||
|
///
|
||||||
|
/// Defaults to true.
|
||||||
|
final bool ctrlV;
|
||||||
|
|
||||||
|
/// When true, the picker tries to grab the focus when the picker is created.
|
||||||
|
///
|
||||||
|
/// By default the picker tries to set focus to its own widgets when it is
|
||||||
|
/// created. It does this when either [ctrlC] or [ctrlV] are enabled in
|
||||||
|
/// order for the keyboard listener to be able to react to copy-paste events
|
||||||
|
/// even if no control on the widget has been focused yet.
|
||||||
|
///
|
||||||
|
/// If you need another widget to retain focus. e.g. if the picker is used on
|
||||||
|
/// surface/scope shared with other widgets and not in its own dialog, then
|
||||||
|
/// setting [autoFocus] to false might help.
|
||||||
|
///
|
||||||
|
/// If both [ctrlC] and [ctrlV] are false, the picker yields the focus the
|
||||||
|
/// same way as setting [autoFocus] false, but then you have no keyboard
|
||||||
|
/// shortcut copy-paste functions at all. With [autoFocus] false, you
|
||||||
|
/// can still use keyboard copy-paste shortcuts and yield the focus from
|
||||||
|
/// the picker. When you do this, the copy-paste keyboard shortcuts will not
|
||||||
|
/// work until one of the picker's components have been focused by
|
||||||
|
/// interacting with them.
|
||||||
|
///
|
||||||
|
/// The picker still grabs focus when you click on its background, as one way
|
||||||
|
/// to set focus to keyboard listener to enable copy-paste keyboard shortcuts
|
||||||
|
/// or when you operate any of its controls, the control in question
|
||||||
|
/// always gains focus.
|
||||||
|
///
|
||||||
|
/// Default to true.
|
||||||
|
final bool autoFocus;
|
||||||
|
|
||||||
|
/// Show a copy action icon in the picker top tool bar.
|
||||||
|
///
|
||||||
|
/// Defaults to false.
|
||||||
|
final bool copyButton;
|
||||||
|
|
||||||
|
/// Show a paste action icon in the picker top tool bar.
|
||||||
|
///
|
||||||
|
/// Defaults to false.
|
||||||
|
final bool pasteButton;
|
||||||
|
|
||||||
|
/// Icon used for the copy action.
|
||||||
|
///
|
||||||
|
/// The same COPY icon is used in the top tool bar, on the context menu and
|
||||||
|
/// after the code field, when those features are enabled.
|
||||||
|
///
|
||||||
|
/// Defaults to [Icons.copy].
|
||||||
|
final IconData copyIcon;
|
||||||
|
|
||||||
|
/// Icon used for the paste action icon in the title bar.
|
||||||
|
///
|
||||||
|
/// The same PASTE icon is used in the top tool bar and on the context menu,
|
||||||
|
/// when those features are enabled.
|
||||||
|
///
|
||||||
|
/// Defaults to [Icons.paste].
|
||||||
|
final IconData pasteIcon;
|
||||||
|
|
||||||
|
/// Label used as tooltip for copy action.
|
||||||
|
///
|
||||||
|
/// Provide your own or use the default material localization label.
|
||||||
|
///
|
||||||
|
/// Defaults to MaterialLocalizations.of(context).copyButtonLabel.
|
||||||
|
/// If CTRL-C copying is also enabled, the string ' (CTRL-C)' is added
|
||||||
|
/// on Linux and Windows platforms and on macOS ' (CMD-C)' is added.
|
||||||
|
final String? copyTooltip;
|
||||||
|
|
||||||
|
/// Label used as tooltip for paste action.
|
||||||
|
///
|
||||||
|
/// Provide your own or use the default material localization label.
|
||||||
|
///
|
||||||
|
/// Defaults to: MaterialLocalizations.of(context).pasteButtonLabel.
|
||||||
|
/// If CTRL-V pasting is also enabled, the string ' (CTRL-V)' is added
|
||||||
|
/// on Linux and Windows platforms and on macOS ' (CMD-V)' is added.
|
||||||
|
final String? pasteTooltip;
|
||||||
|
|
||||||
|
/// Defines the format of the copied color code string.
|
||||||
|
///
|
||||||
|
/// Defaults to [ColorPickerCopyFormat.dartCode].
|
||||||
|
///
|
||||||
|
/// * [ColorPickerCopyFormat.dartCode] is Flutter Hex RGB format '0xAARRGGBB'.
|
||||||
|
/// * [ColorPickerCopyFormat.hexRRGGBB] is Hex RGB format with no
|
||||||
|
/// alpha 'RRGGBB'.
|
||||||
|
/// * [ColorPickerCopyFormat.hexAARRGGBB] is Hex RGB format with
|
||||||
|
/// alpha 'AARRGGBB'.
|
||||||
|
/// * [ColorPickerCopyFormat.numHexRRGGBB] is Web Hex RGB format with a
|
||||||
|
/// leading num # sign and no alpha '#RRGGBB'.
|
||||||
|
/// * [ColorPickerCopyFormat.numHexAARRGGBB] is Web Hex RGB format with a
|
||||||
|
/// * leading num # sign and alpha '#AARRGGBB'.
|
||||||
|
final ColorPickerCopyFormat copyFormat;
|
||||||
|
|
||||||
|
/// Use long press in the picker to open a color copy and paste menu.
|
||||||
|
///
|
||||||
|
/// Defaults to false.
|
||||||
|
final bool longPressMenu;
|
||||||
|
|
||||||
|
/// Use secondary button click in the picker to open a color copy and paste
|
||||||
|
/// menu.
|
||||||
|
///
|
||||||
|
/// Defaults to false.
|
||||||
|
final bool secondaryMenu;
|
||||||
|
|
||||||
|
/// Use secondary button click on desktop and their web version and long
|
||||||
|
/// press on iOs/Android devices in the picker, to open a color copy and
|
||||||
|
/// paste context menu.
|
||||||
|
///
|
||||||
|
/// Defaults to false.
|
||||||
|
final bool secondaryOnDesktopLongOnDevice;
|
||||||
|
|
||||||
|
/// Use secondary button click on desktop and long press on iOs/Android
|
||||||
|
/// devices and all web builds in the picker, to open a color copy and
|
||||||
|
/// paste context menu.
|
||||||
|
///
|
||||||
|
/// Defaults to false.
|
||||||
|
final bool secondaryOnDesktopLongOnDeviceAndWeb;
|
||||||
|
|
||||||
|
/// Show a copy button suffix in the color code edit and display field.
|
||||||
|
///
|
||||||
|
/// Defaults to true.
|
||||||
|
final bool editFieldCopyButton;
|
||||||
|
|
||||||
|
/// The theme for the menu icons.
|
||||||
|
///
|
||||||
|
/// The menu is compact, so icons are small by design.
|
||||||
|
///
|
||||||
|
/// Uses any none null property in passed in [IconThemeData]. If it is
|
||||||
|
/// is null, or any property in it is null, then it uses the
|
||||||
|
/// property values from surrounding `Theme.of(context).iconTheme` if they
|
||||||
|
/// are defined. For any values that remain null value, the following
|
||||||
|
/// fallback defaults are used:
|
||||||
|
/// ```
|
||||||
|
/// color: remains null, so default [IconThemeData] color behavior is kept.
|
||||||
|
/// size: 16
|
||||||
|
/// opacity: 0.90
|
||||||
|
/// ```
|
||||||
|
final IconThemeData? menuIconThemeData;
|
||||||
|
|
||||||
|
/// The theme of the popup menu.
|
||||||
|
///
|
||||||
|
/// Uses any none null property in provided [PopupMenuThemeData], if it is
|
||||||
|
/// null or any property in it is null, then it uses property values from
|
||||||
|
/// `Theme.of(context).popupMenuTheme` if they are not null, for any null
|
||||||
|
/// value the following fallback defaults are used:
|
||||||
|
/// ```
|
||||||
|
/// color: theme.cardColor.withOpacity(0.9)
|
||||||
|
/// shape: RoundedRectangleBorder(
|
||||||
|
/// borderRadius: BorderRadius.circular(8),
|
||||||
|
/// side: BorderSide(
|
||||||
|
/// color: theme.dividerColor))
|
||||||
|
/// elevation: 3
|
||||||
|
/// textStyle: theme.textTheme.bodyMedium!
|
||||||
|
/// enableFeedback: true
|
||||||
|
/// ```
|
||||||
|
final PopupMenuThemeData? menuThemeData;
|
||||||
|
|
||||||
|
/// The width of the menu.
|
||||||
|
///
|
||||||
|
/// Defaults to 80 dp.
|
||||||
|
final double menuWidth;
|
||||||
|
|
||||||
|
/// The height of each menu item.
|
||||||
|
///
|
||||||
|
/// Defaults to 30 dp.
|
||||||
|
final double menuItemHeight;
|
||||||
|
|
||||||
|
/// Show a snack bar paste parse error message when pasting something that
|
||||||
|
/// could not be parsed to a color value.
|
||||||
|
///
|
||||||
|
/// A paste parse error occurs when something is pasted into the color picker
|
||||||
|
/// that cannot parsed to a color value.
|
||||||
|
///
|
||||||
|
/// Defaults to false.
|
||||||
|
final bool snackBarParseError;
|
||||||
|
|
||||||
|
/// The message shown in the paste parse error snack bar.
|
||||||
|
///
|
||||||
|
/// The String is shown in the snack bar when there
|
||||||
|
/// is a paste parse error and [snackBarParseError] is true.
|
||||||
|
///
|
||||||
|
/// If null, it defaults to the combination of the two Material localization
|
||||||
|
/// labels `pasteButtonLabel`: `invalidDateFormatLabel` in a [Text] widget.
|
||||||
|
/// In English it says "Paste: Invalid format.".
|
||||||
|
///
|
||||||
|
/// The snackBar uses the closest theme with SnackBarThemeData for its
|
||||||
|
/// theming.
|
||||||
|
final String? snackBarMessage;
|
||||||
|
|
||||||
|
/// The duration the paste parse error snack bar message is shown.
|
||||||
|
///
|
||||||
|
/// Defaults to const Duration(milliseconds: 1800).
|
||||||
|
final Duration snackBarDuration;
|
||||||
|
|
||||||
|
/// If true then vibrate, play audible click or an alert sound, when a
|
||||||
|
/// paste parse error occurs.
|
||||||
|
///
|
||||||
|
/// A paste parse error occurs when something is pasted into the color picker
|
||||||
|
/// that cannot parsed to a color value.
|
||||||
|
///
|
||||||
|
/// This feature is experimental, its support is limited on most platforms
|
||||||
|
/// in Flutter. If Flutter one day supports the Material Sound Guide, this
|
||||||
|
/// feature can be improved with better sound effects. Currently it cannot be
|
||||||
|
/// improved without importing none SDK plugins/packages to make sounds.
|
||||||
|
/// This package strives to work without any plugins or packages, so it will
|
||||||
|
/// not add any additional none Flutter SDK imports.
|
||||||
|
///
|
||||||
|
/// Defaults to false.
|
||||||
|
final bool feedbackParseError;
|
||||||
|
|
||||||
|
/// When true the hex color code paste action and field entry parser,
|
||||||
|
/// interpret short three character web hex color codes like in CSS.
|
||||||
|
///
|
||||||
|
/// Web allows for short HEX RGB color codes like 123, ABC, F0C and 5D1
|
||||||
|
/// being used as RGB hex color values. These will be interpreted as
|
||||||
|
/// 112233, AABBCC, FF00CC and 55DD11 when [parseShortHexCode] is true.
|
||||||
|
/// This parsing applies to both pasted color values and entries in the color
|
||||||
|
/// code field when [parseShortHexCode] is true.
|
||||||
|
///
|
||||||
|
/// Defaults to false.
|
||||||
|
final bool parseShortHexCode;
|
||||||
|
|
||||||
|
/// If true, the color code entry field uses parsed paste action for
|
||||||
|
/// keyboard shortcuts CTRL-V and CMD-V,
|
||||||
|
///
|
||||||
|
/// A standard text field, will just paste whatever text is in the copy/paste
|
||||||
|
/// buffer into the field. This is the `false` default behavior here too,
|
||||||
|
/// with the exception that the field only accepts valid hex value input
|
||||||
|
/// chars (0-9, A-F), so it always filters and pastes only the acceptable
|
||||||
|
/// input chars from the paste buffer.
|
||||||
|
///
|
||||||
|
/// If this property is `true`, the edit field will use the same color
|
||||||
|
/// paste value parsing used by the other paste actions used when the input
|
||||||
|
/// field is not in focus.
|
||||||
|
/// This results in a paste action in the field that always fully replaces
|
||||||
|
/// the content with the parsed color value of the pasted data, not just
|
||||||
|
/// pasting in the string in the paste buffer over selected text.
|
||||||
|
///
|
||||||
|
/// Currently this setting only impacts CTRL-V and CMD-V keyboard shortcut
|
||||||
|
/// pasting on desktops. The paste on Android and iOS are not intercepted
|
||||||
|
/// when this setting is true.
|
||||||
|
///
|
||||||
|
/// Defaults to false.
|
||||||
|
///
|
||||||
|
/// The false setting is equivalent to past versions (1.x) default behavior
|
||||||
|
/// when pasting strings into the code entry field. Setting the value to true
|
||||||
|
/// may be preferred for a more consistent paste experience.
|
||||||
|
final bool editUsesParsedPaste;
|
||||||
|
|
||||||
|
/// Copy the object with one or more provided properties changed.
|
||||||
|
ColorPickerCopyPasteBehavior copyWith({
|
||||||
|
bool? ctrlC,
|
||||||
|
bool? ctrlV,
|
||||||
|
bool? autoFocus,
|
||||||
|
bool? copyButton,
|
||||||
|
bool? pasteButton,
|
||||||
|
IconData? copyIcon,
|
||||||
|
IconData? pasteIcon,
|
||||||
|
String? copyTooltip,
|
||||||
|
String? pasteTooltip,
|
||||||
|
ColorPickerCopyFormat? copyFormat,
|
||||||
|
bool? longPressMenu,
|
||||||
|
bool? secondaryMenu,
|
||||||
|
bool? secondaryOnDesktopLongOnDevice,
|
||||||
|
bool? secondaryOnDesktopLongOnDeviceAndWeb,
|
||||||
|
bool? editFieldCopyButton,
|
||||||
|
IconThemeData? menuIconThemeData,
|
||||||
|
PopupMenuThemeData? menuThemeData,
|
||||||
|
double? menuWidth,
|
||||||
|
double? menuItemHeight,
|
||||||
|
bool? snackBarParseError,
|
||||||
|
String? snackBarMessage,
|
||||||
|
Duration? snackBarDuration,
|
||||||
|
bool? feedbackParseError,
|
||||||
|
bool? parseShortHexCode,
|
||||||
|
bool? editUsesParsedPaste,
|
||||||
|
}) {
|
||||||
|
return ColorPickerCopyPasteBehavior(
|
||||||
|
ctrlC: ctrlC ?? this.ctrlC,
|
||||||
|
ctrlV: ctrlV ?? this.ctrlV,
|
||||||
|
autoFocus: autoFocus ?? this.autoFocus,
|
||||||
|
copyButton: copyButton ?? this.copyButton,
|
||||||
|
pasteButton: pasteButton ?? this.pasteButton,
|
||||||
|
copyIcon: copyIcon ?? this.copyIcon,
|
||||||
|
pasteIcon: pasteIcon ?? this.pasteIcon,
|
||||||
|
copyTooltip: copyTooltip ?? this.copyTooltip,
|
||||||
|
pasteTooltip: pasteTooltip ?? this.pasteTooltip,
|
||||||
|
copyFormat: copyFormat ?? this.copyFormat,
|
||||||
|
longPressMenu: longPressMenu ?? this.longPressMenu,
|
||||||
|
secondaryMenu: secondaryMenu ?? this.secondaryMenu,
|
||||||
|
secondaryOnDesktopLongOnDevice:
|
||||||
|
secondaryOnDesktopLongOnDevice ?? this.secondaryOnDesktopLongOnDevice,
|
||||||
|
secondaryOnDesktopLongOnDeviceAndWeb:
|
||||||
|
secondaryOnDesktopLongOnDeviceAndWeb ??
|
||||||
|
this.secondaryOnDesktopLongOnDeviceAndWeb,
|
||||||
|
editFieldCopyButton: editFieldCopyButton ?? this.editFieldCopyButton,
|
||||||
|
menuIconThemeData: menuIconThemeData ?? this.menuIconThemeData,
|
||||||
|
menuThemeData: menuThemeData ?? this.menuThemeData,
|
||||||
|
menuWidth: menuWidth ?? this.menuWidth,
|
||||||
|
menuItemHeight: menuItemHeight ?? this.menuItemHeight,
|
||||||
|
snackBarParseError: snackBarParseError ?? this.snackBarParseError,
|
||||||
|
snackBarMessage: snackBarMessage ?? this.snackBarMessage,
|
||||||
|
snackBarDuration: snackBarDuration ?? this.snackBarDuration,
|
||||||
|
feedbackParseError: feedbackParseError ?? this.feedbackParseError,
|
||||||
|
parseShortHexCode: parseShortHexCode ?? this.parseShortHexCode,
|
||||||
|
editUsesParsedPaste: editUsesParsedPaste ?? this.editUsesParsedPaste,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
if (identical(this, other)) return true;
|
||||||
|
if (other.runtimeType != runtimeType) return false;
|
||||||
|
return other is ColorPickerCopyPasteBehavior &&
|
||||||
|
ctrlC == other.ctrlC &&
|
||||||
|
ctrlV == other.ctrlV &&
|
||||||
|
autoFocus == other.autoFocus &&
|
||||||
|
copyButton == other.copyButton &&
|
||||||
|
pasteButton == other.pasteButton &&
|
||||||
|
copyIcon == other.copyIcon &&
|
||||||
|
pasteIcon == other.pasteIcon &&
|
||||||
|
copyTooltip == other.copyTooltip &&
|
||||||
|
pasteTooltip == other.pasteTooltip &&
|
||||||
|
copyFormat == other.copyFormat &&
|
||||||
|
longPressMenu == other.longPressMenu &&
|
||||||
|
secondaryMenu == other.secondaryMenu &&
|
||||||
|
secondaryOnDesktopLongOnDevice ==
|
||||||
|
other.secondaryOnDesktopLongOnDevice &&
|
||||||
|
secondaryOnDesktopLongOnDeviceAndWeb ==
|
||||||
|
other.secondaryOnDesktopLongOnDeviceAndWeb &&
|
||||||
|
editFieldCopyButton == other.editFieldCopyButton &&
|
||||||
|
menuIconThemeData == other.menuIconThemeData &&
|
||||||
|
menuThemeData == other.menuThemeData &&
|
||||||
|
menuWidth == other.menuWidth &&
|
||||||
|
menuItemHeight == other.menuItemHeight &&
|
||||||
|
snackBarParseError == other.snackBarParseError &&
|
||||||
|
snackBarMessage == other.snackBarMessage &&
|
||||||
|
snackBarDuration == other.snackBarDuration &&
|
||||||
|
feedbackParseError == other.feedbackParseError &&
|
||||||
|
parseShortHexCode == other.parseShortHexCode &&
|
||||||
|
editUsesParsedPaste == other.editUsesParsedPaste;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hashAll(<Object?>[
|
||||||
|
ctrlC,
|
||||||
|
ctrlV,
|
||||||
|
autoFocus,
|
||||||
|
copyButton,
|
||||||
|
pasteButton,
|
||||||
|
copyIcon,
|
||||||
|
pasteIcon,
|
||||||
|
copyTooltip,
|
||||||
|
pasteTooltip,
|
||||||
|
copyFormat,
|
||||||
|
longPressMenu,
|
||||||
|
secondaryMenu,
|
||||||
|
secondaryOnDesktopLongOnDevice,
|
||||||
|
secondaryOnDesktopLongOnDeviceAndWeb,
|
||||||
|
editFieldCopyButton,
|
||||||
|
menuIconThemeData,
|
||||||
|
menuThemeData,
|
||||||
|
menuWidth,
|
||||||
|
menuItemHeight,
|
||||||
|
snackBarParseError,
|
||||||
|
snackBarMessage,
|
||||||
|
snackBarDuration,
|
||||||
|
feedbackParseError,
|
||||||
|
parseShortHexCode,
|
||||||
|
editUsesParsedPaste,
|
||||||
|
]);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||||
|
super.debugFillProperties(properties);
|
||||||
|
properties.add(DiagnosticsProperty<bool>('ctrlC', ctrlC));
|
||||||
|
properties.add(DiagnosticsProperty<bool>('ctrlV', ctrlV));
|
||||||
|
properties.add(DiagnosticsProperty<bool>('autoFocus', autoFocus));
|
||||||
|
properties.add(DiagnosticsProperty<bool>('copyButton', copyButton));
|
||||||
|
properties.add(DiagnosticsProperty<bool>('pasteButton', pasteButton));
|
||||||
|
properties.add(DiagnosticsProperty<IconData>('copyIcon', copyIcon));
|
||||||
|
properties.add(DiagnosticsProperty<IconData>('pasteIcon', pasteIcon));
|
||||||
|
properties.add(StringProperty('copyTooltip', copyTooltip));
|
||||||
|
properties.add(StringProperty('pasteTooltip', pasteTooltip));
|
||||||
|
properties
|
||||||
|
.add(EnumProperty<ColorPickerCopyFormat>('copyFormat', copyFormat));
|
||||||
|
properties.add(DiagnosticsProperty<bool>('longPressMenu', longPressMenu));
|
||||||
|
properties.add(DiagnosticsProperty<bool>('secondaryMenu', secondaryMenu));
|
||||||
|
properties.add(DiagnosticsProperty<bool>(
|
||||||
|
'secondaryOnDesktopLongOnDevice', secondaryOnDesktopLongOnDevice));
|
||||||
|
properties.add(DiagnosticsProperty<bool>(
|
||||||
|
'secondaryOnDesktopLongOnDeviceAndWeb',
|
||||||
|
secondaryOnDesktopLongOnDeviceAndWeb));
|
||||||
|
properties.add(
|
||||||
|
DiagnosticsProperty<bool>('editFieldCopyButton', editFieldCopyButton));
|
||||||
|
properties.add(DiagnosticsProperty<IconThemeData?>(
|
||||||
|
'menuIconThemeData', menuIconThemeData));
|
||||||
|
properties.add(DiagnosticsProperty<PopupMenuThemeData?>(
|
||||||
|
'menuThemeData', menuThemeData));
|
||||||
|
properties.add(DoubleProperty('menuWidth', menuWidth));
|
||||||
|
properties.add(DoubleProperty('menuItemHeight', menuItemHeight));
|
||||||
|
properties.add(
|
||||||
|
DiagnosticsProperty<bool>('snackBarParseError', snackBarParseError));
|
||||||
|
properties.add(StringProperty('snackBarMessage', snackBarMessage));
|
||||||
|
properties.add(
|
||||||
|
DiagnosticsProperty<Duration>('snackBarDuration', snackBarDuration));
|
||||||
|
properties.add(
|
||||||
|
DiagnosticsProperty<bool>('feedbackParseError', feedbackParseError));
|
||||||
|
properties
|
||||||
|
.add(DiagnosticsProperty<bool>('parseShortHexCode', parseShortHexCode));
|
||||||
|
properties.add(
|
||||||
|
DiagnosticsProperty<bool>('editUsesParsedPaste', editUsesParsedPaste));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,214 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// Extensions on non nullable [Color] to return it's color value as strings.
|
||||||
|
///
|
||||||
|
/// The color extension also include getting a color's RGB hex code as a string
|
||||||
|
/// in two different formats. Extension [hexAlpha] returns a HEX code string of
|
||||||
|
/// a Color value including alpha channel.
|
||||||
|
/// The [hex] extension returns the hex color value as RGB string without the
|
||||||
|
/// alpha channel value.
|
||||||
|
extension FlexPickerNoNullColorExtensions on Color {
|
||||||
|
/// Return color's uppercase RGB hex string, including alpha channel.
|
||||||
|
String get hexAlpha {
|
||||||
|
return value.toRadixString(16).toUpperCase().padLeft(8, '0');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return color's uppercase RGB hex string, excluding alpha channel.
|
||||||
|
String get hex {
|
||||||
|
return value.toRadixString(16).toUpperCase().padLeft(8, '0').substring(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extensions on [String].
|
||||||
|
///
|
||||||
|
/// Included extensions are, [toColor] to convert a String to a Color.
|
||||||
|
/// To [capitalize] the first letter in a String and [dotTail] to get
|
||||||
|
/// remaining string after first dot "." in a String.
|
||||||
|
extension FlexPickerNoNullStringExtensions on String {
|
||||||
|
//
|
||||||
|
/// Convert a HEX value encoded (A)RGB string to a Dart Color.
|
||||||
|
///
|
||||||
|
/// * The string may include the '#' char, but does not have to.
|
||||||
|
/// * String may also include '0x' Dart Hex indicator, but does not have to.
|
||||||
|
/// * Any '#' '0x' patterns are trimmed out and String is assumed to be Hex.
|
||||||
|
/// * The String may start with alpha channel hex value, but does not have to,
|
||||||
|
/// if alpha value is missing "FF" is used for alpha.
|
||||||
|
/// * String may be longer than 8 chars, after trimming out # and 0x, it will
|
||||||
|
/// be RIGHT truncated to max 8 chars before parsing.
|
||||||
|
/// * If [enableShortRGB] is true a CSS style 3 char RGB value is interpreted
|
||||||
|
/// as RRGGBB, if false it used it as a partial color value.
|
||||||
|
///
|
||||||
|
/// IF the resulting string cannot be parsed to a Color, is empty or null
|
||||||
|
/// THEN fully opaque black color is returned ELSE the Color is returned.
|
||||||
|
///
|
||||||
|
/// To give caller a chance to handle parsing errors, use the same
|
||||||
|
/// extension on nullable Color [toColorMaybeNull]. It returns null when
|
||||||
|
/// there is a string that cannot be parsed to color value.
|
||||||
|
/// You can then decide what to do with the error instead of just receiving
|
||||||
|
/// fully opaque black color.
|
||||||
|
Color toColorShort(bool enableShortRGB) {
|
||||||
|
// If String was zero length, then we return transparent, cannot parse.
|
||||||
|
if (this == '') return const Color(0xFF000000);
|
||||||
|
// If String length is > 200 we as a safety precaution will not try to
|
||||||
|
// parse it to a color, for shorter lengths the last 8 chars will be used.
|
||||||
|
if (this.length > 200) return const Color(0xFF000000);
|
||||||
|
// Remove all num signs, we allow them, but disregard them all.
|
||||||
|
String hexColor = replaceAll('#', '');
|
||||||
|
if (hexColor == '') return const Color(0xFF000000);
|
||||||
|
// Remove all spaces, we allow them, but disregard them all.
|
||||||
|
hexColor = hexColor.replaceAll(' ', '');
|
||||||
|
if (hexColor == '') return const Color(0xFF000000);
|
||||||
|
// Remove all '0x' Hex code marks, we allow them, but disregard them all.
|
||||||
|
hexColor = hexColor.replaceAll('0x', '');
|
||||||
|
if (hexColor == '') return const Color(0xFF000000);
|
||||||
|
// If the input is exactly 3 chars long, we may have a short Web hex code,
|
||||||
|
// let's make the potential 'RGB' code to a 'RRGGBB' code.
|
||||||
|
if (hexColor.length == 3 && enableShortRGB) {
|
||||||
|
hexColor = hexColor[0] +
|
||||||
|
hexColor[0] +
|
||||||
|
hexColor[1] +
|
||||||
|
hexColor[1] +
|
||||||
|
hexColor[2] +
|
||||||
|
hexColor[2];
|
||||||
|
}
|
||||||
|
// Pad anything shorter than 7 with left 0 -> fill non spec channels with 0.
|
||||||
|
hexColor = hexColor.padLeft(6, '0');
|
||||||
|
// Pad anything shorter than 9 with left F -> fill with opaque alpha.
|
||||||
|
hexColor = hexColor.padLeft(8, 'F');
|
||||||
|
|
||||||
|
// We only try to parse the last 8 chars in the remaining string, rest can
|
||||||
|
// still be whatever.
|
||||||
|
final int length = hexColor.length;
|
||||||
|
return Color(int.tryParse('0x${hexColor.substring(length - 8, length)}') ??
|
||||||
|
0xFF000000);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns [toColorShort] with `enableShortRGB` set to true.
|
||||||
|
///
|
||||||
|
/// Available for backwards compatibility with previous API.
|
||||||
|
Color get toColor => toColorShort(true);
|
||||||
|
|
||||||
|
/// Capitalize the first letter in a string.
|
||||||
|
String get capitalize {
|
||||||
|
return (length > 1) ? this[0].toUpperCase() + substring(1) : toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the string remaining in a string after the last "." in a String,
|
||||||
|
/// if there is no "." the string itself is returned.
|
||||||
|
///
|
||||||
|
/// This function can be used to e.g. return the enum tail value from an
|
||||||
|
/// enum's standard toString method.
|
||||||
|
String get dotTail {
|
||||||
|
return split('.').last;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extensions on [String].
|
||||||
|
///
|
||||||
|
/// Included extensions are, [toColorMaybeNull] to convert a String to a Color.
|
||||||
|
/// To [capitalizeMaybeNull] the first letter in a String and
|
||||||
|
/// [dotTailMaybeNull] to get remaining string after first dot "." in a String.
|
||||||
|
extension FlexPickerNullableStringExtensions on String? {
|
||||||
|
//
|
||||||
|
/// Convert a HEX value encoded (A)RGB string to a Dart Color.
|
||||||
|
///
|
||||||
|
/// * The string may include the '#' char, but does not have to.
|
||||||
|
/// * String may also include '0x' Dart Hex indicator, but does not have to.
|
||||||
|
/// * Any '#' '0x' patterns are trimmed out and String is assumed to be Hex.
|
||||||
|
/// * The String may start with alpha channel hex value, but does not have to,
|
||||||
|
/// if alpha value is missing "FF" is used for alpha.
|
||||||
|
/// * String may be longer than 8 chars, after trimming out # and 0x, it will
|
||||||
|
/// be RIGHT truncated to max 8 chars before parsing.
|
||||||
|
/// * If [enableShortRGB] is true a CSS style 3 char RGB value is interpreted
|
||||||
|
/// as RRGGBB, if false it used it as a partial color value.
|
||||||
|
///
|
||||||
|
/// IF the resulting string cannot be parsed to a Color, is empty or null
|
||||||
|
/// THEN NULL is returned ELSE the Color is returned.
|
||||||
|
Color? toColorShortMaybeNull(bool enableShortRGB) {
|
||||||
|
// If String was null or zero length, then we return null, cannot parse.
|
||||||
|
if (this == null || this == '') return null;
|
||||||
|
// If String length is > 200 we as a safety precaution will not try to
|
||||||
|
// parse it to a color, for shorter lengths the last 8 chars will be used.
|
||||||
|
if ((this?.length ?? 200) > 200) return null;
|
||||||
|
// Remove all num signs, we allow them, but disregard them all.
|
||||||
|
String hexColor = this?.replaceAll('#', '') ?? '';
|
||||||
|
if (hexColor == '') return null;
|
||||||
|
// Remove all spaces, we allow them, but disregard them all.
|
||||||
|
hexColor = hexColor.replaceAll(' ', '');
|
||||||
|
if (hexColor == '') return null;
|
||||||
|
// Remove all '0x' Hex code marks, we allow them, but disregard them all.
|
||||||
|
hexColor = hexColor.replaceAll('0x', '');
|
||||||
|
if (hexColor == '') return null;
|
||||||
|
// If the input is exactly 3 chars long, we may have a short Web hex code,
|
||||||
|
// let's make the potential 'RGB' code to a 'RRGGBB' code.
|
||||||
|
if (hexColor.length == 3 && enableShortRGB) {
|
||||||
|
hexColor = hexColor[0] +
|
||||||
|
hexColor[0] +
|
||||||
|
hexColor[1] +
|
||||||
|
hexColor[1] +
|
||||||
|
hexColor[2] +
|
||||||
|
hexColor[2];
|
||||||
|
}
|
||||||
|
// Pad anything shorter than 7 with left 0 -> fill non spec channels with 0.
|
||||||
|
hexColor = hexColor.padLeft(6, '0');
|
||||||
|
// Pad anything shorter than 9 with left F -> fill with opaque alpha.
|
||||||
|
hexColor = hexColor.padLeft(8, 'F');
|
||||||
|
|
||||||
|
// We only try to parse the last 8 chars in the remaining string, rest can
|
||||||
|
// still be whatever.
|
||||||
|
final int length = hexColor.length;
|
||||||
|
final int? intColor =
|
||||||
|
int.tryParse('0x${hexColor.substring(length - 8, length)}');
|
||||||
|
return intColor != null ? Color(intColor) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns [toColorShortMaybeNull] with `enableShortRGB` set to true.
|
||||||
|
///
|
||||||
|
/// Available for backwards compatibility with previous API.
|
||||||
|
Color? get toColorMaybeNull => toColorShortMaybeNull(true);
|
||||||
|
|
||||||
|
/// Capitalize the first letter in a string. If string is null, we get null
|
||||||
|
/// back.
|
||||||
|
String? get capitalizeMaybeNull {
|
||||||
|
if (this == null) {
|
||||||
|
return this;
|
||||||
|
} else {
|
||||||
|
return ((this?.length ?? 0) > 1)
|
||||||
|
? (this?[0].toUpperCase() ?? '') + (this?.substring(1) ?? '')
|
||||||
|
: this?.toUpperCase() ?? '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the string remaining in a string after the last "." in a String,
|
||||||
|
/// if there is no "." the string itself is returned. If string is null
|
||||||
|
/// we get null back.
|
||||||
|
///
|
||||||
|
/// This function can be used to e.g. return the enum tail value from an
|
||||||
|
/// enum's standard toString method.
|
||||||
|
String? get dotTailMaybeNull {
|
||||||
|
if (this == null) {
|
||||||
|
return this;
|
||||||
|
} else {
|
||||||
|
return this?.split('.').last;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the control key label for the current platform.
|
||||||
|
///
|
||||||
|
/// Windows, Linux: CTRL
|
||||||
|
/// Mac: CMD (Tried using the ⌘ symbol, did not show up on Web though.)
|
||||||
|
/// Others: Empty string
|
||||||
|
String platformControlKey(TargetPlatform platform, String key) {
|
||||||
|
switch (platform) {
|
||||||
|
case TargetPlatform.android:
|
||||||
|
case TargetPlatform.iOS:
|
||||||
|
case TargetPlatform.fuchsia:
|
||||||
|
return '';
|
||||||
|
case TargetPlatform.linux:
|
||||||
|
case TargetPlatform.windows:
|
||||||
|
return ' (CTRL-$key)';
|
||||||
|
case TargetPlatform.macOS:
|
||||||
|
return ' (CMD-$key)';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/rendering.dart';
|
||||||
|
|
||||||
|
// These two classes are a workaround to issue:
|
||||||
|
// https://github.com/flutter/flutter/issues/71687
|
||||||
|
// This workaround for the issue was made by:
|
||||||
|
// @slightfoot and @matthew-carroll (GitHub accounts)
|
||||||
|
|
||||||
|
/// Same as `IntrinsicWidth` except that when this widget is instructed
|
||||||
|
/// to `computeDryLayout()`, it doesn't invoke that on its child, instead
|
||||||
|
/// it computes the child's intrinsic width.
|
||||||
|
///
|
||||||
|
/// This widget is useful in situations where the `child` does not
|
||||||
|
/// support dry layout, e.g., `TextField` as of 01/02/2021.
|
||||||
|
///
|
||||||
|
/// Not library exposed, private to the package.
|
||||||
|
@immutable
|
||||||
|
class DryIntrinsicWidth extends SingleChildRenderObjectWidget {
|
||||||
|
/// Default const constructor.
|
||||||
|
const DryIntrinsicWidth({
|
||||||
|
super.key,
|
||||||
|
required Widget super.child,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
_RenderDryIntrinsicWidth createRenderObject(BuildContext context) =>
|
||||||
|
_RenderDryIntrinsicWidth();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RenderDryIntrinsicWidth extends RenderIntrinsicWidth {
|
||||||
|
@override
|
||||||
|
Size computeDryLayout(BoxConstraints constraints) {
|
||||||
|
if (child != null) {
|
||||||
|
final double? width =
|
||||||
|
child?.computeMinIntrinsicWidth(constraints.maxHeight);
|
||||||
|
final double? height = child?.computeMinIntrinsicHeight(width ?? 0);
|
||||||
|
return Size(width ?? 0, height ?? 0);
|
||||||
|
} else {
|
||||||
|
return Size.zero;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Same as `IntrinsicHeight` except that when this widget is instructed
|
||||||
|
/// to `computeDryLayout()`, it doesn't invoke that on its child, instead
|
||||||
|
/// it computes the child's intrinsic height.
|
||||||
|
///
|
||||||
|
/// This widget is useful in situations where the `child` does not
|
||||||
|
/// support dry layout, e.g., `TextField` as of 01/02/2021.
|
||||||
|
///
|
||||||
|
/// Not library exposed, private to the library.
|
||||||
|
@immutable
|
||||||
|
class DryIntrinsicHeight extends SingleChildRenderObjectWidget {
|
||||||
|
/// Default const constructor.
|
||||||
|
const DryIntrinsicHeight({
|
||||||
|
super.key,
|
||||||
|
required Widget super.child,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
_RenderDryIntrinsicHeight createRenderObject(BuildContext context) =>
|
||||||
|
_RenderDryIntrinsicHeight();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RenderDryIntrinsicHeight extends RenderIntrinsicHeight {
|
||||||
|
@override
|
||||||
|
Size computeDryLayout(BoxConstraints constraints) {
|
||||||
|
if (child != null) {
|
||||||
|
final double? height =
|
||||||
|
child?.computeMinIntrinsicHeight(constraints.maxWidth);
|
||||||
|
final double? width = child?.computeMinIntrinsicWidth(height ?? 0);
|
||||||
|
return Size(width ?? 0, height ?? 0);
|
||||||
|
} else {
|
||||||
|
return Size.zero;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-17
@@ -145,22 +145,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.1.4"
|
version: "6.1.4"
|
||||||
flex_color_picker:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: flex_color_picker
|
|
||||||
sha256: "607c9fdb26be84d4a5a0931ab42a7eda725372e4f5ebaa2526ab6b22ead752f9"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "3.1.0"
|
|
||||||
flex_seed_scheme:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: flex_seed_scheme
|
|
||||||
sha256: "7058288ef97d348657ac95cea25d65a9aac181ca08387ede891fd7230ad7600f"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.2.3"
|
|
||||||
flutter:
|
flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description: flutter
|
description: flutter
|
||||||
@@ -575,4 +559,4 @@ packages:
|
|||||||
version: "6.2.2"
|
version: "6.2.2"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=2.19.2 <3.0.0"
|
dart: ">=2.19.2 <3.0.0"
|
||||||
flutter: ">=3.7.0"
|
flutter: ">=3.7.0-0"
|
||||||
|
|||||||
@@ -59,8 +59,6 @@ dependencies:
|
|||||||
|
|
||||||
flutter_keyboard_visibility: 5.4.0
|
flutter_keyboard_visibility: 5.4.0
|
||||||
|
|
||||||
flex_color_picker: 3.1.0
|
|
||||||
|
|
||||||
flutter_image_compress: 1.1.3
|
flutter_image_compress: 1.1.3
|
||||||
|
|
||||||
http_parser: 4.0.2
|
http_parser: 4.0.2
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ abstract class BaseRichTextController extends GetxController {
|
|||||||
) async {
|
) async {
|
||||||
await ColorPickerDialogBuilder(
|
await ColorPickerDialogBuilder(
|
||||||
context,
|
context,
|
||||||
currentColor,
|
ValueNotifier<Color>(currentColor),
|
||||||
title: AppLocalizations.of(context).chooseAColor,
|
title: AppLocalizations.of(context).chooseAColor,
|
||||||
textActionSetColor: AppLocalizations.of(context).setColor,
|
textActionSetColor: AppLocalizations.of(context).setColor,
|
||||||
textActionResetDefault: AppLocalizations.of(context).resetToDefault,
|
textActionResetDefault: AppLocalizations.of(context).resetToDefault,
|
||||||
|
|||||||
Reference in New Issue
Block a user