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)
|
||||
.padLeft(6, '0')
|
||||
.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? {
|
||||
@@ -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/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:pointer_interceptor/pointer_interceptor.dart';
|
||||
|
||||
typedef SelectColorActionCallback = Function(Color? colorSelected);
|
||||
|
||||
class ColorPickerDialogBuilder {
|
||||
|
||||
final SelectColorActionCallback? setColorActionCallback;
|
||||
final VoidCallback? cancelActionCallback;
|
||||
final VoidCallback? resetToDefaultActionCallback;
|
||||
final BuildContext _context;
|
||||
final Color defaultColor;
|
||||
final Color _currentColor;
|
||||
final ValueNotifier<Color> _currentColor;
|
||||
final String? title;
|
||||
final String? textActionSetColor;
|
||||
final String? textActionCancel;
|
||||
final String? textActionResetDefault;
|
||||
final Function(Color)? onSelected;
|
||||
|
||||
Color? _colorSelected;
|
||||
bool _shouldUpdate = false;
|
||||
Color _colorCode = Colors.black;
|
||||
|
||||
ColorPickerDialogBuilder(
|
||||
this._context,
|
||||
this._currentColor,
|
||||
{
|
||||
this.onSelected,
|
||||
this.title,
|
||||
this.textActionSetColor,
|
||||
this.textActionCancel,
|
||||
@@ -35,22 +39,23 @@ class ColorPickerDialogBuilder {
|
||||
this.cancelActionCallback,
|
||||
this.resetToDefaultActionCallback
|
||||
}
|
||||
) : _colorSelected = _currentColor;
|
||||
);
|
||||
|
||||
Future show() async {
|
||||
await showDialog(context: _context, builder: (BuildContext context) {
|
||||
return PointerInterceptor(
|
||||
child: AlertDialog(
|
||||
title: Text(title ?? '',
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 20,
|
||||
color: Colors.black)),
|
||||
titleTextStyle: const TextStyle(
|
||||
title: Text(
|
||||
title ?? '',
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
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),
|
||||
contentPadding: const EdgeInsets.symmetric(vertical: 0, horizontal: 16),
|
||||
actionsPadding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
|
||||
@@ -59,27 +64,55 @@ class ColorPickerDialogBuilder {
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)),
|
||||
scrollable: true,
|
||||
elevation: 10,
|
||||
content: ColorPicker(
|
||||
color: _currentColor,
|
||||
onColorChanged: (color) => _colorSelected = color,
|
||||
width: 40,
|
||||
height: 40,
|
||||
spacing: 0,
|
||||
runSpacing: 0,
|
||||
borderRadius: 0,
|
||||
wheelDiameter: 165,
|
||||
enableOpacity: false,
|
||||
showColorCode: true,
|
||||
colorCodeHasColor: true,
|
||||
pickersEnabled: const <ColorPickerType, bool>{
|
||||
ColorPickerType.wheel: true,
|
||||
content: ValueListenableBuilder(
|
||||
valueListenable: _currentColor,
|
||||
builder: (context, _, __) {
|
||||
return Column(
|
||||
children: [
|
||||
Container(
|
||||
width: 600,
|
||||
decoration: const BoxDecoration(
|
||||
borderRadius: BorderRadius.all(Radius.circular(4)),
|
||||
color: Colors.white
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(4)),
|
||||
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>[
|
||||
buildButtonWrapText(
|
||||
@@ -108,13 +141,42 @@ class ColorPickerDialogBuilder {
|
||||
radius: 5,
|
||||
height: 30,
|
||||
textStyle: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500),
|
||||
onTap: () => setColorActionCallback?.call(_colorSelected))
|
||||
color: Colors.white,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500),
|
||||
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"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
@@ -575,4 +559,4 @@ packages:
|
||||
version: "6.2.2"
|
||||
sdks:
|
||||
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
|
||||
|
||||
flex_color_picker: 3.1.0
|
||||
|
||||
flutter_image_compress: 1.1.3
|
||||
|
||||
http_parser: 4.0.2
|
||||
|
||||
@@ -22,7 +22,7 @@ abstract class BaseRichTextController extends GetxController {
|
||||
) async {
|
||||
await ColorPickerDialogBuilder(
|
||||
context,
|
||||
currentColor,
|
||||
ValueNotifier<Color>(currentColor),
|
||||
title: AppLocalizations.of(context).chooseAColor,
|
||||
textActionSetColor: AppLocalizations.of(context).setColor,
|
||||
textActionResetDefault: AppLocalizations.of(context).resetToDefault,
|
||||
|
||||
Reference in New Issue
Block a user