Feat/support latest stable flutter (#1874)
* fix: temporarily remove flutter_colorpicker from pub.dev * chore: clone flutter_colorpicker from Github, add a TODO with it * fix: update color_dialog.dart to use color picker package from the lib/src/packages/flutter_colorpicker * refactor(example): remove the old android example and recreate it to get it working with the latest stable version without any warrnings * fix: format flutter_colorpicker to fix CI failure, update the android example project to use latest version of Kotlin, fix AndroidManifest string resources * fix: update the linux example to fix CI failure * ci: update build.yml as an attemp to fix building the Linux application * ci: add a todo in build.yml, remove flutter doctor check * ci: fix a typo * ci: update the name of each steppull/1875/head v9.3.12
parent
9599f4b82b
commit
dd23f7aace
22 changed files with 3499 additions and 138 deletions
@ -1,4 +0,0 @@ |
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<resources> |
||||
<string name="app_name">Flutter Quill Demo</string> |
||||
</resources> |
@ -1 +0,0 @@ |
||||
extensions: |
@ -0,0 +1,9 @@ |
||||
library flutter_colorpicker; |
||||
|
||||
// TODO: temporarily clone https://pub.dev/packages/flutter_colorpicker as it's hasn't been published on pub.dev for a while |
||||
|
||||
export 'src/block_picker.dart'; |
||||
export 'src/colorpicker.dart'; |
||||
export 'src/material_picker.dart'; |
||||
export 'src/palette.dart'; |
||||
export 'src/utils.dart'; |
@ -0,0 +1,211 @@ |
||||
// ignore_for_file: type=lint |
||||
|
||||
/// Blocky Color Picker |
||||
|
||||
library block_colorpicker; |
||||
|
||||
import 'package:flutter/material.dart'; |
||||
import 'utils.dart'; |
||||
|
||||
/// Child widget for layout builder. |
||||
typedef PickerItem = Widget Function(Color color); |
||||
|
||||
/// Customize the layout. |
||||
typedef PickerLayoutBuilder = Widget Function( |
||||
BuildContext context, List<Color> colors, PickerItem child); |
||||
|
||||
/// Customize the item shape. |
||||
typedef PickerItemBuilder = Widget Function( |
||||
Color color, bool isCurrentColor, void Function() changeColor); |
||||
|
||||
// Provide a list of colors for block color picker. |
||||
const List<Color> _defaultColors = [ |
||||
Colors.red, |
||||
Colors.pink, |
||||
Colors.purple, |
||||
Colors.deepPurple, |
||||
Colors.indigo, |
||||
Colors.blue, |
||||
Colors.lightBlue, |
||||
Colors.cyan, |
||||
Colors.teal, |
||||
Colors.green, |
||||
Colors.lightGreen, |
||||
Colors.lime, |
||||
Colors.yellow, |
||||
Colors.amber, |
||||
Colors.orange, |
||||
Colors.deepOrange, |
||||
Colors.brown, |
||||
Colors.grey, |
||||
Colors.blueGrey, |
||||
Colors.black, |
||||
]; |
||||
|
||||
// Provide a layout for [BlockPicker]. |
||||
Widget _defaultLayoutBuilder( |
||||
BuildContext context, List<Color> colors, PickerItem child) { |
||||
Orientation orientation = MediaQuery.of(context).orientation; |
||||
|
||||
return SizedBox( |
||||
width: 300, |
||||
height: orientation == Orientation.portrait ? 360 : 200, |
||||
child: GridView.count( |
||||
crossAxisCount: orientation == Orientation.portrait ? 4 : 6, |
||||
crossAxisSpacing: 5, |
||||
mainAxisSpacing: 5, |
||||
children: [for (Color color in colors) child(color)], |
||||
), |
||||
); |
||||
} |
||||
|
||||
// Provide a shape for [BlockPicker]. |
||||
Widget _defaultItemBuilder( |
||||
Color color, bool isCurrentColor, void Function() changeColor) { |
||||
return Container( |
||||
margin: const EdgeInsets.all(7), |
||||
decoration: BoxDecoration( |
||||
shape: BoxShape.circle, |
||||
color: color, |
||||
boxShadow: [ |
||||
BoxShadow( |
||||
color: color.withOpacity(0.8), |
||||
offset: const Offset(1, 2), |
||||
blurRadius: 5) |
||||
], |
||||
), |
||||
child: Material( |
||||
color: Colors.transparent, |
||||
child: InkWell( |
||||
onTap: changeColor, |
||||
borderRadius: BorderRadius.circular(50), |
||||
child: AnimatedOpacity( |
||||
duration: const Duration(milliseconds: 210), |
||||
opacity: isCurrentColor ? 1 : 0, |
||||
child: Icon(Icons.done, |
||||
color: useWhiteForeground(color) ? Colors.white : Colors.black), |
||||
), |
||||
), |
||||
), |
||||
); |
||||
} |
||||
|
||||
// The blocky color picker you can alter the layout and shape. |
||||
class BlockPicker extends StatefulWidget { |
||||
const BlockPicker({ |
||||
Key? key, |
||||
required this.pickerColor, |
||||
required this.onColorChanged, |
||||
this.availableColors = _defaultColors, |
||||
this.useInShowDialog = true, |
||||
this.layoutBuilder = _defaultLayoutBuilder, |
||||
this.itemBuilder = _defaultItemBuilder, |
||||
}) : super(key: key); |
||||
|
||||
final Color? pickerColor; |
||||
final ValueChanged<Color> onColorChanged; |
||||
final List<Color> availableColors; |
||||
final bool useInShowDialog; |
||||
final PickerLayoutBuilder layoutBuilder; |
||||
final PickerItemBuilder itemBuilder; |
||||
|
||||
@override |
||||
State<StatefulWidget> createState() => _BlockPickerState(); |
||||
} |
||||
|
||||
class _BlockPickerState extends State<BlockPicker> { |
||||
Color? _currentColor; |
||||
|
||||
@override |
||||
void initState() { |
||||
_currentColor = widget.pickerColor; |
||||
super.initState(); |
||||
} |
||||
|
||||
void changeColor(Color color) { |
||||
setState(() => _currentColor = color); |
||||
widget.onColorChanged(color); |
||||
} |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return widget.layoutBuilder( |
||||
context, |
||||
widget.availableColors, |
||||
(Color color) => widget.itemBuilder( |
||||
color, |
||||
(_currentColor != null && |
||||
(widget.useInShowDialog ? true : widget.pickerColor != null)) |
||||
? (_currentColor?.value == color.value) && |
||||
(widget.useInShowDialog |
||||
? true |
||||
: widget.pickerColor?.value == color.value) |
||||
: false, |
||||
() => changeColor(color), |
||||
), |
||||
); |
||||
} |
||||
} |
||||
|
||||
// The blocky color picker you can alter the layout and shape with multiple choice. |
||||
class MultipleChoiceBlockPicker extends StatefulWidget { |
||||
const MultipleChoiceBlockPicker({ |
||||
Key? key, |
||||
required this.pickerColors, |
||||
required this.onColorsChanged, |
||||
this.availableColors = _defaultColors, |
||||
this.useInShowDialog = true, |
||||
this.layoutBuilder = _defaultLayoutBuilder, |
||||
this.itemBuilder = _defaultItemBuilder, |
||||
}) : super(key: key); |
||||
|
||||
final List<Color>? pickerColors; |
||||
final ValueChanged<List<Color>> onColorsChanged; |
||||
final List<Color> availableColors; |
||||
final bool useInShowDialog; |
||||
final PickerLayoutBuilder layoutBuilder; |
||||
final PickerItemBuilder itemBuilder; |
||||
|
||||
@override |
||||
State<StatefulWidget> createState() => _MultipleChoiceBlockPickerState(); |
||||
} |
||||
|
||||
class _MultipleChoiceBlockPickerState extends State<MultipleChoiceBlockPicker> { |
||||
List<Color>? _currentColors; |
||||
|
||||
@override |
||||
void initState() { |
||||
_currentColors = widget.pickerColors; |
||||
super.initState(); |
||||
} |
||||
|
||||
void toggleColor(Color color) { |
||||
setState(() { |
||||
if (_currentColors != null) { |
||||
_currentColors!.contains(color) |
||||
? _currentColors!.remove(color) |
||||
: _currentColors!.add(color); |
||||
} |
||||
}); |
||||
widget.onColorsChanged(_currentColors ?? []); |
||||
} |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return widget.layoutBuilder( |
||||
context, |
||||
widget.availableColors, |
||||
(Color color) => widget.itemBuilder( |
||||
color, |
||||
(_currentColors != null && |
||||
(widget.useInShowDialog ? true : widget.pickerColors != null)) |
||||
? _currentColors!.contains(color) && |
||||
(widget.useInShowDialog |
||||
? true |
||||
: widget.pickerColors!.contains(color)) |
||||
: false, |
||||
() => toggleColor(color), |
||||
), |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,891 @@ |
||||
// ignore_for_file: type=lint |
||||
|
||||
/// HSV(HSB)/HSL Color Picker example |
||||
/// |
||||
/// You can create your own layout by importing `picker.dart`. |
||||
|
||||
library hsv_picker; |
||||
|
||||
import 'package:flutter/material.dart'; |
||||
import 'palette.dart'; |
||||
import 'utils.dart'; |
||||
|
||||
/// The default layout of Color Picker. |
||||
class ColorPicker extends StatefulWidget { |
||||
const ColorPicker({ |
||||
Key? key, |
||||
required this.pickerColor, |
||||
required this.onColorChanged, |
||||
this.pickerHsvColor, |
||||
this.onHsvColorChanged, |
||||
this.paletteType = PaletteType.hsvWithHue, |
||||
this.enableAlpha = true, |
||||
@Deprecated('Use empty list in [labelTypes] to disable label.') |
||||
this.showLabel = true, |
||||
this.labelTypes = const [ |
||||
ColorLabelType.rgb, |
||||
ColorLabelType.hsv, |
||||
ColorLabelType.hsl |
||||
], |
||||
@Deprecated( |
||||
'Use Theme.of(context).textTheme.bodyText1 & 2 to alter text style.') |
||||
this.labelTextStyle, |
||||
this.displayThumbColor = false, |
||||
this.portraitOnly = false, |
||||
this.colorPickerWidth = 300.0, |
||||
this.pickerAreaHeightPercent = 1.0, |
||||
this.pickerAreaBorderRadius = const BorderRadius.all(Radius.zero), |
||||
this.hexInputBar = false, |
||||
this.hexInputController, |
||||
this.colorHistory, |
||||
this.onHistoryChanged, |
||||
}) : super(key: key); |
||||
|
||||
final Color pickerColor; |
||||
final ValueChanged<Color> onColorChanged; |
||||
final HSVColor? pickerHsvColor; |
||||
final ValueChanged<HSVColor>? onHsvColorChanged; |
||||
final PaletteType paletteType; |
||||
final bool enableAlpha; |
||||
final bool showLabel; |
||||
final List<ColorLabelType> labelTypes; |
||||
final TextStyle? labelTextStyle; |
||||
final bool displayThumbColor; |
||||
final bool portraitOnly; |
||||
final double colorPickerWidth; |
||||
final double pickerAreaHeightPercent; |
||||
final BorderRadius pickerAreaBorderRadius; |
||||
final bool hexInputBar; |
||||
|
||||
/// Allows setting the color using text input, via [TextEditingController]. |
||||
/// |
||||
/// Listens to [String] input and trying to convert it to the valid [Color]. |
||||
/// Contains basic validator, that requires final input to be provided |
||||
/// in one of those formats: |
||||
/// |
||||
/// * RGB |
||||
/// * #RGB |
||||
/// * RRGGBB |
||||
/// * #RRGGBB |
||||
/// * AARRGGBB |
||||
/// * #AARRGGBB |
||||
/// |
||||
/// Where: A stands for Alpha, R for Red, G for Green, and B for blue color. |
||||
/// It will only accept 3/6/8 long HEXs with an optional hash (`#`) at the beginning. |
||||
/// Allowed characters are Latin A-F case insensitive and numbers 0-9. |
||||
/// It does respect the [enableAlpha] flag, so if alpha is disabled, all inputs |
||||
/// with transparency are also converted to non-transparent color values. |
||||
/// ```dart |
||||
/// MaterialButton( |
||||
/// elevation: 3.0, |
||||
/// onPressed: () { |
||||
/// // The initial value can be provided directly to the controller. |
||||
/// final textController = |
||||
/// TextEditingController(text: '#2F19DB'); |
||||
/// showDialog( |
||||
/// context: context, |
||||
/// builder: (BuildContext context) { |
||||
/// return AlertDialog( |
||||
/// scrollable: true, |
||||
/// titlePadding: const EdgeInsets.all(0.0), |
||||
/// contentPadding: const EdgeInsets.all(0.0), |
||||
/// content: Column( |
||||
/// children: [ |
||||
/// ColorPicker( |
||||
/// pickerColor: currentColor, |
||||
/// onColorChanged: changeColor, |
||||
/// colorPickerWidth: 300.0, |
||||
/// pickerAreaHeightPercent: 0.7, |
||||
/// enableAlpha: |
||||
/// true, // hexInputController will respect it too. |
||||
/// displayThumbColor: true, |
||||
/// showLabel: true, |
||||
/// paletteType: PaletteType.hsv, |
||||
/// pickerAreaBorderRadius: const BorderRadius.only( |
||||
/// topLeft: const Radius.circular(2.0), |
||||
/// topRight: const Radius.circular(2.0), |
||||
/// ), |
||||
/// hexInputController: textController, // <- here |
||||
/// portraitOnly: true, |
||||
/// ), |
||||
/// Padding( |
||||
/// padding: const EdgeInsets.all(16), |
||||
/// /* It can be any text field, for example: |
||||
/// * TextField |
||||
/// * TextFormField |
||||
/// * CupertinoTextField |
||||
/// * EditableText |
||||
/// * any text field from 3-rd party package |
||||
/// * your own text field |
||||
/// so basically anything that supports/uses |
||||
/// a TextEditingController for an editable text. |
||||
/// */ |
||||
/// child: CupertinoTextField( |
||||
/// controller: textController, |
||||
/// // Everything below is purely optional. |
||||
/// prefix: Padding( |
||||
/// padding: const EdgeInsets.only(left: 8), |
||||
/// child: const Icon(Icons.tag), |
||||
/// ), |
||||
/// suffix: IconButton( |
||||
/// icon: |
||||
/// const Icon(Icons.content_paste_rounded), |
||||
/// onPressed: () async => |
||||
/// copyToClipboard(textController.text), |
||||
/// ), |
||||
/// autofocus: true, |
||||
/// maxLength: 9, |
||||
/// inputFormatters: [ |
||||
/// // Any custom input formatter can be passed |
||||
/// // here or use any Form validator you want. |
||||
/// UpperCaseTextFormatter(), |
||||
/// FilteringTextInputFormatter.allow( |
||||
/// RegExp(kValidHexPattern)), |
||||
/// ], |
||||
/// ), |
||||
/// ) |
||||
/// ], |
||||
/// ), |
||||
/// ); |
||||
/// }, |
||||
/// ); |
||||
/// }, |
||||
/// child: const Text('Change me via text input'), |
||||
/// color: currentColor, |
||||
/// textColor: useWhiteForeground(currentColor) |
||||
/// ? const Color(0xffffffff) |
||||
/// : const Color(0xff000000), |
||||
/// ), |
||||
/// ``` |
||||
/// |
||||
/// Do not forget to `dispose()` your [TextEditingController] if you creating |
||||
/// it inside any kind of [StatefulWidget]'s [State]. |
||||
/// Reference: https://en.wikipedia.org/wiki/Web_colors#Hex_triplet |
||||
final TextEditingController? hexInputController; |
||||
final List<Color>? colorHistory; |
||||
final ValueChanged<List<Color>>? onHistoryChanged; |
||||
|
||||
@override |
||||
_ColorPickerState createState() => _ColorPickerState(); |
||||
} |
||||
|
||||
class _ColorPickerState extends State<ColorPicker> { |
||||
HSVColor currentHsvColor = const HSVColor.fromAHSV(0.0, 0.0, 0.0, 0.0); |
||||
List<Color> colorHistory = []; |
||||
|
||||
@override |
||||
void initState() { |
||||
currentHsvColor = (widget.pickerHsvColor != null) |
||||
? widget.pickerHsvColor as HSVColor |
||||
: HSVColor.fromColor(widget.pickerColor); |
||||
// If there's no initial text in `hexInputController`, |
||||
if (widget.hexInputController?.text.isEmpty == true) { |
||||
// set it to the current's color HEX value. |
||||
widget.hexInputController?.text = colorToHex( |
||||
currentHsvColor.toColor(), |
||||
enableAlpha: widget.enableAlpha, |
||||
); |
||||
} |
||||
// Listen to the text input, If there is an `hexInputController` provided. |
||||
widget.hexInputController?.addListener(colorPickerTextInputListener); |
||||
if (widget.colorHistory != null && widget.onHistoryChanged != null) { |
||||
colorHistory = widget.colorHistory ?? []; |
||||
} |
||||
super.initState(); |
||||
} |
||||
|
||||
@override |
||||
void didUpdateWidget(ColorPicker oldWidget) { |
||||
super.didUpdateWidget(oldWidget); |
||||
currentHsvColor = (widget.pickerHsvColor != null) |
||||
? widget.pickerHsvColor as HSVColor |
||||
: HSVColor.fromColor(widget.pickerColor); |
||||
} |
||||
|
||||
void colorPickerTextInputListener() { |
||||
// It can't be null really, since it's only listening if the controller |
||||
// is provided, but it may help to calm the Dart analyzer in the future. |
||||
if (widget.hexInputController == null) return; |
||||
// If a user is inserting/typing any text — try to get the color value from it, |
||||
// and interpret its transparency, dependent on the widget's settings. |
||||
final Color? color = colorFromHex(widget.hexInputController!.text, |
||||
enableAlpha: widget.enableAlpha); |
||||
// If it's the valid color: |
||||
if (color != null) { |
||||
// set it as the current color and |
||||
setState(() => currentHsvColor = HSVColor.fromColor(color)); |
||||
// notify with a callback. |
||||
widget.onColorChanged(color); |
||||
if (widget.onHsvColorChanged != null) |
||||
widget.onHsvColorChanged!(currentHsvColor); |
||||
} |
||||
} |
||||
|
||||
@override |
||||
void dispose() { |
||||
widget.hexInputController?.removeListener(colorPickerTextInputListener); |
||||
super.dispose(); |
||||
} |
||||
|
||||
Widget colorPickerSlider(TrackType trackType) { |
||||
return ColorPickerSlider( |
||||
trackType, |
||||
currentHsvColor, |
||||
(HSVColor color) { |
||||
// Update text in `hexInputController` if provided. |
||||
widget.hexInputController?.text = |
||||
colorToHex(color.toColor(), enableAlpha: widget.enableAlpha); |
||||
setState(() => currentHsvColor = color); |
||||
widget.onColorChanged(currentHsvColor.toColor()); |
||||
if (widget.onHsvColorChanged != null) |
||||
widget.onHsvColorChanged!(currentHsvColor); |
||||
}, |
||||
displayThumbColor: widget.displayThumbColor, |
||||
); |
||||
} |
||||
|
||||
void onColorChanging(HSVColor color) { |
||||
// Update text in `hexInputController` if provided. |
||||
widget.hexInputController?.text = |
||||
colorToHex(color.toColor(), enableAlpha: widget.enableAlpha); |
||||
setState(() => currentHsvColor = color); |
||||
widget.onColorChanged(currentHsvColor.toColor()); |
||||
if (widget.onHsvColorChanged != null) |
||||
widget.onHsvColorChanged!(currentHsvColor); |
||||
} |
||||
|
||||
Widget colorPicker() { |
||||
return ClipRRect( |
||||
borderRadius: widget.pickerAreaBorderRadius, |
||||
child: Padding( |
||||
padding: |
||||
EdgeInsets.all(widget.paletteType == PaletteType.hueWheel ? 10 : 0), |
||||
child: ColorPickerArea( |
||||
currentHsvColor, onColorChanging, widget.paletteType), |
||||
), |
||||
); |
||||
} |
||||
|
||||
Widget sliderByPaletteType() { |
||||
switch (widget.paletteType) { |
||||
case PaletteType.hsv: |
||||
case PaletteType.hsvWithHue: |
||||
case PaletteType.hsl: |
||||
case PaletteType.hslWithHue: |
||||
return colorPickerSlider(TrackType.hue); |
||||
case PaletteType.hsvWithValue: |
||||
case PaletteType.hueWheel: |
||||
return colorPickerSlider(TrackType.value); |
||||
case PaletteType.hsvWithSaturation: |
||||
return colorPickerSlider(TrackType.saturation); |
||||
case PaletteType.hslWithLightness: |
||||
return colorPickerSlider(TrackType.lightness); |
||||
case PaletteType.hslWithSaturation: |
||||
return colorPickerSlider(TrackType.saturationForHSL); |
||||
case PaletteType.rgbWithBlue: |
||||
return colorPickerSlider(TrackType.blue); |
||||
case PaletteType.rgbWithGreen: |
||||
return colorPickerSlider(TrackType.green); |
||||
case PaletteType.rgbWithRed: |
||||
return colorPickerSlider(TrackType.red); |
||||
default: |
||||
return const SizedBox(); |
||||
} |
||||
} |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
if (MediaQuery.of(context).orientation == Orientation.portrait || |
||||
widget.portraitOnly) { |
||||
return Column( |
||||
children: <Widget>[ |
||||
SizedBox( |
||||
width: widget.colorPickerWidth, |
||||
height: widget.colorPickerWidth * widget.pickerAreaHeightPercent, |
||||
child: colorPicker(), |
||||
), |
||||
Padding( |
||||
padding: const EdgeInsets.fromLTRB(15.0, 5.0, 10.0, 5.0), |
||||
child: Row( |
||||
mainAxisAlignment: MainAxisAlignment.center, |
||||
children: <Widget>[ |
||||
GestureDetector( |
||||
onTap: () => setState(() { |
||||
if (widget.onHistoryChanged != null && |
||||
!colorHistory.contains(currentHsvColor.toColor())) { |
||||
colorHistory.add(currentHsvColor.toColor()); |
||||
widget.onHistoryChanged!(colorHistory); |
||||
} |
||||
}), |
||||
child: ColorIndicator(currentHsvColor), |
||||
), |
||||
Expanded( |
||||
child: Column( |
||||
children: <Widget>[ |
||||
SizedBox( |
||||
height: 40.0, |
||||
width: widget.colorPickerWidth - 75.0, |
||||
child: sliderByPaletteType()), |
||||
if (widget.enableAlpha) |
||||
SizedBox( |
||||
height: 40.0, |
||||
width: widget.colorPickerWidth - 75.0, |
||||
child: colorPickerSlider(TrackType.alpha), |
||||
), |
||||
], |
||||
), |
||||
), |
||||
], |
||||
), |
||||
), |
||||
if (colorHistory.isNotEmpty) |
||||
SizedBox( |
||||
width: widget.colorPickerWidth, |
||||
height: 50, |
||||
child: |
||||
ListView(scrollDirection: Axis.horizontal, children: <Widget>[ |
||||
for (Color color in colorHistory) |
||||
Padding( |
||||
key: Key(color.hashCode.toString()), |
||||
padding: const EdgeInsets.fromLTRB(15, 0, 0, 10), |
||||
child: Center( |
||||
child: GestureDetector( |
||||
onTap: () => onColorChanging(HSVColor.fromColor(color)), |
||||
child: ColorIndicator(HSVColor.fromColor(color), |
||||
width: 30, height: 30), |
||||
), |
||||
), |
||||
), |
||||
const SizedBox(width: 15), |
||||
]), |
||||
), |
||||
if (widget.showLabel && widget.labelTypes.isNotEmpty) |
||||
FittedBox( |
||||
child: ColorPickerLabel( |
||||
currentHsvColor, |
||||
enableAlpha: widget.enableAlpha, |
||||
textStyle: widget.labelTextStyle, |
||||
colorLabelTypes: widget.labelTypes, |
||||
), |
||||
), |
||||
if (widget.hexInputBar) |
||||
ColorPickerInput( |
||||
currentHsvColor.toColor(), |
||||
(Color color) { |
||||
setState(() => currentHsvColor = HSVColor.fromColor(color)); |
||||
widget.onColorChanged(currentHsvColor.toColor()); |
||||
if (widget.onHsvColorChanged != null) |
||||
widget.onHsvColorChanged!(currentHsvColor); |
||||
}, |
||||
enableAlpha: widget.enableAlpha, |
||||
embeddedText: false, |
||||
), |
||||
const SizedBox(height: 20.0), |
||||
], |
||||
); |
||||
} else { |
||||
return Row( |
||||
children: <Widget>[ |
||||
SizedBox( |
||||
width: widget.colorPickerWidth, |
||||
height: widget.colorPickerWidth * widget.pickerAreaHeightPercent, |
||||
child: colorPicker()), |
||||
Column( |
||||
children: <Widget>[ |
||||
Row( |
||||
children: <Widget>[ |
||||
const SizedBox(width: 20.0), |
||||
GestureDetector( |
||||
onTap: () => setState(() { |
||||
if (widget.onHistoryChanged != null && |
||||
!colorHistory.contains(currentHsvColor.toColor())) { |
||||
colorHistory.add(currentHsvColor.toColor()); |
||||
widget.onHistoryChanged!(colorHistory); |
||||
} |
||||
}), |
||||
child: ColorIndicator(currentHsvColor), |
||||
), |
||||
Column( |
||||
children: <Widget>[ |
||||
SizedBox( |
||||
height: 40.0, |
||||
width: 260.0, |
||||
child: sliderByPaletteType()), |
||||
if (widget.enableAlpha) |
||||
SizedBox( |
||||
height: 40.0, |
||||
width: 260.0, |
||||
child: colorPickerSlider(TrackType.alpha)), |
||||
], |
||||
), |
||||
const SizedBox(width: 10.0), |
||||
], |
||||
), |
||||
if (colorHistory.isNotEmpty) |
||||
SizedBox( |
||||
width: widget.colorPickerWidth, |
||||
height: 50, |
||||
child: ListView( |
||||
scrollDirection: Axis.horizontal, |
||||
children: <Widget>[ |
||||
for (Color color in colorHistory) |
||||
Padding( |
||||
key: Key(color.hashCode.toString()), |
||||
padding: const EdgeInsets.fromLTRB(15, 18, 0, 0), |
||||
child: Center( |
||||
child: GestureDetector( |
||||
onTap: () => |
||||
onColorChanging(HSVColor.fromColor(color)), |
||||
onLongPress: () { |
||||
if (colorHistory.remove(color)) { |
||||
widget.onHistoryChanged!(colorHistory); |
||||
setState(() {}); |
||||
} |
||||
}, |
||||
child: ColorIndicator(HSVColor.fromColor(color), |
||||
width: 30, height: 30), |
||||
), |
||||
), |
||||
), |
||||
const SizedBox(width: 15), |
||||
]), |
||||
), |
||||
const SizedBox(height: 20.0), |
||||
if (widget.showLabel && widget.labelTypes.isNotEmpty) |
||||
FittedBox( |
||||
child: ColorPickerLabel( |
||||
currentHsvColor, |
||||
enableAlpha: widget.enableAlpha, |
||||
textStyle: widget.labelTextStyle, |
||||
colorLabelTypes: widget.labelTypes, |
||||
), |
||||
), |
||||
if (widget.hexInputBar) |
||||
ColorPickerInput( |
||||
currentHsvColor.toColor(), |
||||
(Color color) { |
||||
setState(() => currentHsvColor = HSVColor.fromColor(color)); |
||||
widget.onColorChanged(currentHsvColor.toColor()); |
||||
if (widget.onHsvColorChanged != null) |
||||
widget.onHsvColorChanged!(currentHsvColor); |
||||
}, |
||||
enableAlpha: widget.enableAlpha, |
||||
embeddedText: false, |
||||
), |
||||
const SizedBox(height: 5), |
||||
], |
||||
), |
||||
], |
||||
); |
||||
} |
||||
} |
||||
} |
||||
|
||||
/// The Color Picker with sliders only. Support HSV, HSL and RGB color model. |
||||
class SlidePicker extends StatefulWidget { |
||||
const SlidePicker({ |
||||
Key? key, |
||||
required this.pickerColor, |
||||
required this.onColorChanged, |
||||
this.colorModel = ColorModel.rgb, |
||||
this.enableAlpha = true, |
||||
this.sliderSize = const Size(260, 40), |
||||
this.showSliderText = true, |
||||
@Deprecated( |
||||
'Use Theme.of(context).textTheme.bodyText1 & 2 to alter text style.') |
||||
this.sliderTextStyle, |
||||
this.showParams = true, |
||||
@Deprecated('Use empty list in [labelTypes] to disable label.') |
||||
this.showLabel = true, |
||||
this.labelTypes = const [], |
||||
@Deprecated( |
||||
'Use Theme.of(context).textTheme.bodyText1 & 2 to alter text style.') |
||||
this.labelTextStyle, |
||||
this.showIndicator = true, |
||||
this.indicatorSize = const Size(280, 50), |
||||
this.indicatorAlignmentBegin = const Alignment(-1.0, -3.0), |
||||
this.indicatorAlignmentEnd = const Alignment(1.0, 3.0), |
||||
this.displayThumbColor = true, |
||||
this.indicatorBorderRadius = const BorderRadius.all(Radius.zero), |
||||
}) : super(key: key); |
||||
|
||||
final Color pickerColor; |
||||
final ValueChanged<Color> onColorChanged; |
||||
final ColorModel colorModel; |
||||
final bool enableAlpha; |
||||
final Size sliderSize; |
||||
final bool showSliderText; |
||||
final TextStyle? sliderTextStyle; |
||||
final bool showLabel; |
||||
final bool showParams; |
||||
final List<ColorLabelType> labelTypes; |
||||
final TextStyle? labelTextStyle; |
||||
final bool showIndicator; |
||||
final Size indicatorSize; |
||||
final AlignmentGeometry indicatorAlignmentBegin; |
||||
final AlignmentGeometry indicatorAlignmentEnd; |
||||
final bool displayThumbColor; |
||||
final BorderRadius indicatorBorderRadius; |
||||
|
||||
@override |
||||
State<StatefulWidget> createState() => _SlidePickerState(); |
||||
} |
||||
|
||||
class _SlidePickerState extends State<SlidePicker> { |
||||
HSVColor currentHsvColor = const HSVColor.fromAHSV(0.0, 0.0, 0.0, 0.0); |
||||
|
||||
@override |
||||
void initState() { |
||||
super.initState(); |
||||
currentHsvColor = HSVColor.fromColor(widget.pickerColor); |
||||
} |
||||
|
||||
@override |
||||
void didUpdateWidget(SlidePicker oldWidget) { |
||||
super.didUpdateWidget(oldWidget); |
||||
currentHsvColor = HSVColor.fromColor(widget.pickerColor); |
||||
} |
||||
|
||||
Widget colorPickerSlider(TrackType trackType) { |
||||
return ColorPickerSlider( |
||||
trackType, |
||||
currentHsvColor, |
||||
(HSVColor color) { |
||||
setState(() => currentHsvColor = color); |
||||
widget.onColorChanged(currentHsvColor.toColor()); |
||||
}, |
||||
displayThumbColor: widget.displayThumbColor, |
||||
fullThumbColor: true, |
||||
); |
||||
} |
||||
|
||||
Widget indicator() { |
||||
return ClipRRect( |
||||
borderRadius: widget.indicatorBorderRadius, |
||||
clipBehavior: Clip.antiAliasWithSaveLayer, |
||||
child: GestureDetector( |
||||
onTap: () { |
||||
setState( |
||||
() => currentHsvColor = HSVColor.fromColor(widget.pickerColor)); |
||||
widget.onColorChanged(currentHsvColor.toColor()); |
||||
}, |
||||
child: Container( |
||||
width: widget.indicatorSize.width, |
||||
height: widget.indicatorSize.height, |
||||
margin: const EdgeInsets.only(bottom: 15.0), |
||||
foregroundDecoration: BoxDecoration( |
||||
gradient: LinearGradient( |
||||
colors: [ |
||||
widget.pickerColor, |
||||
widget.pickerColor, |
||||
currentHsvColor.toColor(), |
||||
currentHsvColor.toColor(), |
||||
], |
||||
begin: widget.indicatorAlignmentBegin, |
||||
end: widget.indicatorAlignmentEnd, |
||||
stops: const [0.0, 0.5, 0.5, 1.0], |
||||
), |
||||
), |
||||
child: const CustomPaint(painter: CheckerPainter()), |
||||
), |
||||
), |
||||
); |
||||
} |
||||
|
||||
String getColorParams(int pos) { |
||||
assert(pos >= 0 && pos < 4); |
||||
if (widget.colorModel == ColorModel.rgb) { |
||||
final Color color = currentHsvColor.toColor(); |
||||
return [ |
||||
color.red.toString(), |
||||
color.green.toString(), |
||||
color.blue.toString(), |
||||
'${(color.opacity * 100).round()}', |
||||
][pos]; |
||||
} else if (widget.colorModel == ColorModel.hsv) { |
||||
return [ |
||||
currentHsvColor.hue.round().toString(), |
||||
(currentHsvColor.saturation * 100).round().toString(), |
||||
(currentHsvColor.value * 100).round().toString(), |
||||
(currentHsvColor.alpha * 100).round().toString(), |
||||
][pos]; |
||||
} else if (widget.colorModel == ColorModel.hsl) { |
||||
HSLColor hslColor = hsvToHsl(currentHsvColor); |
||||
return [ |
||||
hslColor.hue.round().toString(), |
||||
(hslColor.saturation * 100).round().toString(), |
||||
(hslColor.lightness * 100).round().toString(), |
||||
(currentHsvColor.alpha * 100).round().toString(), |
||||
][pos]; |
||||
} else { |
||||
return '??'; |
||||
} |
||||
} |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
double fontSize = 14; |
||||
if (widget.labelTextStyle != null && |
||||
widget.labelTextStyle?.fontSize != null) { |
||||
fontSize = widget.labelTextStyle?.fontSize ?? 14; |
||||
} |
||||
final List<TrackType> trackTypes = [ |
||||
if (widget.colorModel == ColorModel.hsv) ...[ |
||||
TrackType.hue, |
||||
TrackType.saturation, |
||||
TrackType.value |
||||
], |
||||
if (widget.colorModel == ColorModel.hsl) ...[ |
||||
TrackType.hue, |
||||
TrackType.saturationForHSL, |
||||
TrackType.lightness |
||||
], |
||||
if (widget.colorModel == ColorModel.rgb) ...[ |
||||
TrackType.red, |
||||
TrackType.green, |
||||
TrackType.blue |
||||
], |
||||
if (widget.enableAlpha) ...[TrackType.alpha], |
||||
]; |
||||
List<SizedBox> sliders = [ |
||||
for (TrackType trackType in trackTypes) |
||||
SizedBox( |
||||
width: widget.sliderSize.width, |
||||
height: widget.sliderSize.height, |
||||
child: Row( |
||||
children: <Widget>[ |
||||
if (widget.showSliderText) |
||||
Padding( |
||||
padding: const EdgeInsets.symmetric(horizontal: 10.0), |
||||
child: Text( |
||||
trackType.toString().split('.').last[0].toUpperCase(), |
||||
style: widget.sliderTextStyle ?? |
||||
Theme.of(context).textTheme.bodyLarge, |
||||
), |
||||
), |
||||
Expanded(child: colorPickerSlider(trackType)), |
||||
if (widget.showParams) |
||||
ConstrainedBox( |
||||
constraints: BoxConstraints(minWidth: fontSize * 2 + 5), |
||||
child: Text( |
||||
getColorParams(trackTypes.indexOf(trackType)), |
||||
style: widget.sliderTextStyle ?? |
||||
Theme.of(context).textTheme.bodyMedium, |
||||
textAlign: TextAlign.right, |
||||
), |
||||
), |
||||
], |
||||
), |
||||
), |
||||
]; |
||||
|
||||
return Column( |
||||
mainAxisAlignment: MainAxisAlignment.center, |
||||
crossAxisAlignment: CrossAxisAlignment.center, |
||||
children: <Widget>[ |
||||
if (widget.showIndicator) indicator(), |
||||
if (!widget.showIndicator) const SizedBox(height: 20), |
||||
...sliders, |
||||
const SizedBox(height: 20.0), |
||||
if (widget.showLabel && widget.labelTypes.isNotEmpty) |
||||
Padding( |
||||
padding: const EdgeInsets.only(bottom: 20.0), |
||||
child: ColorPickerLabel( |
||||
currentHsvColor, |
||||
enableAlpha: widget.enableAlpha, |
||||
textStyle: widget.labelTextStyle, |
||||
colorLabelTypes: widget.labelTypes, |
||||
), |
||||
), |
||||
], |
||||
); |
||||
} |
||||
} |
||||
|
||||
/// The Color Picker with HUE Ring & HSV model. |
||||
class HueRingPicker extends StatefulWidget { |
||||
const HueRingPicker({ |
||||
Key? key, |
||||
required this.pickerColor, |
||||
required this.onColorChanged, |
||||
this.portraitOnly = false, |
||||
this.colorPickerHeight = 250.0, |
||||
this.hueRingStrokeWidth = 20.0, |
||||
this.enableAlpha = false, |
||||
this.displayThumbColor = true, |
||||
this.pickerAreaBorderRadius = const BorderRadius.all(Radius.zero), |
||||
}) : super(key: key); |
||||
|
||||
final Color pickerColor; |
||||
final ValueChanged<Color> onColorChanged; |
||||
final bool portraitOnly; |
||||
final double colorPickerHeight; |
||||
final double hueRingStrokeWidth; |
||||
final bool enableAlpha; |
||||
final bool displayThumbColor; |
||||
final BorderRadius pickerAreaBorderRadius; |
||||
|
||||
@override |
||||
_HueRingPickerState createState() => _HueRingPickerState(); |
||||
} |
||||
|
||||
class _HueRingPickerState extends State<HueRingPicker> { |
||||
HSVColor currentHsvColor = const HSVColor.fromAHSV(0.0, 0.0, 0.0, 0.0); |
||||
|
||||
@override |
||||
void initState() { |
||||
currentHsvColor = HSVColor.fromColor(widget.pickerColor); |
||||
super.initState(); |
||||
} |
||||
|
||||
@override |
||||
void didUpdateWidget(HueRingPicker oldWidget) { |
||||
super.didUpdateWidget(oldWidget); |
||||
currentHsvColor = HSVColor.fromColor(widget.pickerColor); |
||||
} |
||||
|
||||
void onColorChanging(HSVColor color) { |
||||
setState(() => currentHsvColor = color); |
||||
widget.onColorChanged(currentHsvColor.toColor()); |
||||
} |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
if (MediaQuery.of(context).orientation == Orientation.portrait || |
||||
widget.portraitOnly) { |
||||
return Column( |
||||
children: <Widget>[ |
||||
ClipRRect( |
||||
borderRadius: widget.pickerAreaBorderRadius, |
||||
child: Padding( |
||||
padding: const EdgeInsets.all(15), |
||||
child: Stack( |
||||
alignment: AlignmentDirectional.center, |
||||
children: <Widget>[ |
||||
SizedBox( |
||||
width: widget.colorPickerHeight, |
||||
height: widget.colorPickerHeight, |
||||
child: ColorPickerHueRing( |
||||
currentHsvColor, |
||||
onColorChanging, |
||||
displayThumbColor: widget.displayThumbColor, |
||||
strokeWidth: widget.hueRingStrokeWidth, |
||||
), |
||||
), |
||||
SizedBox( |
||||
width: widget.colorPickerHeight / 1.6, |
||||
height: widget.colorPickerHeight / 1.6, |
||||
child: ColorPickerArea( |
||||
currentHsvColor, onColorChanging, PaletteType.hsv), |
||||
) |
||||
]), |
||||
), |
||||
), |
||||
if (widget.enableAlpha) |
||||
SizedBox( |
||||
height: 40.0, |
||||
width: widget.colorPickerHeight, |
||||
child: ColorPickerSlider( |
||||
TrackType.alpha, |
||||
currentHsvColor, |
||||
onColorChanging, |
||||
displayThumbColor: widget.displayThumbColor, |
||||
), |
||||
), |
||||
Padding( |
||||
padding: const EdgeInsets.fromLTRB(15.0, 5.0, 10.0, 5.0), |
||||
child: Row( |
||||
mainAxisAlignment: MainAxisAlignment.center, |
||||
children: <Widget>[ |
||||
const SizedBox(width: 10), |
||||
ColorIndicator(currentHsvColor), |
||||
Expanded( |
||||
child: Padding( |
||||
padding: const EdgeInsets.fromLTRB(0, 5, 0, 20), |
||||
child: ColorPickerInput( |
||||
currentHsvColor.toColor(), |
||||
(Color color) { |
||||
setState( |
||||
() => currentHsvColor = HSVColor.fromColor(color)); |
||||
widget.onColorChanged(currentHsvColor.toColor()); |
||||
}, |
||||
enableAlpha: widget.enableAlpha, |
||||
embeddedText: true, |
||||
), |
||||
), |
||||
), |
||||
], |
||||
), |
||||
), |
||||
], |
||||
); |
||||
} else { |
||||
return Row( |
||||
children: <Widget>[ |
||||
Expanded( |
||||
child: SizedBox( |
||||
width: 300.0, |
||||
height: widget.colorPickerHeight, |
||||
child: ClipRRect( |
||||
borderRadius: widget.pickerAreaBorderRadius, |
||||
child: ColorPickerArea( |
||||
currentHsvColor, onColorChanging, PaletteType.hsv), |
||||
), |
||||
), |
||||
), |
||||
ClipRRect( |
||||
borderRadius: widget.pickerAreaBorderRadius, |
||||
child: Padding( |
||||
padding: const EdgeInsets.all(15), |
||||
child: Stack( |
||||
alignment: AlignmentDirectional.topCenter, |
||||
children: <Widget>[ |
||||
SizedBox( |
||||
width: widget.colorPickerHeight - |
||||
widget.hueRingStrokeWidth * 2, |
||||
height: widget.colorPickerHeight - |
||||
widget.hueRingStrokeWidth * 2, |
||||
child: ColorPickerHueRing( |
||||
currentHsvColor, onColorChanging, |
||||
strokeWidth: widget.hueRingStrokeWidth), |
||||
), |
||||
Column( |
||||
children: [ |
||||
SizedBox(height: widget.colorPickerHeight / 8.5), |
||||
ColorIndicator(currentHsvColor), |
||||
const SizedBox(height: 10), |
||||
ColorPickerInput( |
||||
currentHsvColor.toColor(), |
||||
(Color color) { |
||||
setState(() => |
||||
currentHsvColor = HSVColor.fromColor(color)); |
||||
widget.onColorChanged(currentHsvColor.toColor()); |
||||
}, |
||||
enableAlpha: widget.enableAlpha, |
||||
embeddedText: true, |
||||
disable: true, |
||||
), |
||||
if (widget.enableAlpha) const SizedBox(height: 5), |
||||
if (widget.enableAlpha) |
||||
SizedBox( |
||||
height: 40.0, |
||||
width: (widget.colorPickerHeight - |
||||
widget.hueRingStrokeWidth * 2) / |
||||
2, |
||||
child: ColorPickerSlider( |
||||
TrackType.alpha, |
||||
currentHsvColor, |
||||
onColorChanging, |
||||
displayThumbColor: true, |
||||
), |
||||
), |
||||
], |
||||
), |
||||
]), |
||||
), |
||||
), |
||||
], |
||||
); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,172 @@ |
||||
// ignore_for_file: type=lint |
||||
|
||||
import 'dart:ui'; |
||||
|
||||
/// X11 Colors |
||||
/// |
||||
/// https://en.wikipedia.org/wiki/X11_color_names |
||||
|
||||
const Map<String, Color> x11Colors = { |
||||
'aliceblue': Color(0xfff0f8ff), |
||||
'antiquewhite': Color(0xfffaebd7), |
||||
'aqua': Color(0xff00ffff), |
||||
'aquamarine': Color(0xff7fffd4), |
||||
'azure': Color(0xfff0ffff), |
||||
'beige': Color(0xfff5f5dc), |
||||
'bisque': Color(0xffffe4c4), |
||||
'black': Color(0xff000000), |
||||
'blanchedalmond': Color(0xffffebcd), |
||||
'blue': Color(0xff0000ff), |
||||
'blueviolet': Color(0xff8a2be2), |
||||
'brown': Color(0xffa52a2a), |
||||
'burlywood': Color(0xffdeb887), |
||||
'cadetblue': Color(0xff5f9ea0), |
||||
'chartreuse': Color(0xff7fff00), |
||||
'chocolate': Color(0xffd2691e), |
||||
'coral': Color(0xffff7f50), |
||||
'cornflower': Color(0xff6495ed), |
||||
'cornflowerblue': Color(0xff6495ed), |
||||
'cornsilk': Color(0xfffff8dc), |
||||
'crimson': Color(0xffdc143c), |
||||
'cyan': Color(0xff00ffff), |
||||
'darkblue': Color(0xff00008b), |
||||
'darkcyan': Color(0xff008b8b), |
||||
'darkgoldenrod': Color(0xffb8860b), |
||||
'darkgray': Color(0xffa9a9a9), |
||||
'darkgreen': Color(0xff006400), |
||||
'darkgrey': Color(0xffa9a9a9), |
||||
'darkkhaki': Color(0xffbdb76b), |
||||
'darkmagenta': Color(0xff8b008b), |
||||
'darkolivegreen': Color(0xff556b2f), |
||||
'darkorange': Color(0xffff8c00), |
||||
'darkorchid': Color(0xff9932cc), |
||||
'darkred': Color(0xff8b0000), |
||||
'darksalmon': Color(0xffe9967a), |
||||
'darkseagreen': Color(0xff8fbc8f), |
||||
'darkslateblue': Color(0xff483d8b), |
||||
'darkslategray': Color(0xff2f4f4f), |
||||
'darkslategrey': Color(0xff2f4f4f), |
||||
'darkturquoise': Color(0xff00ced1), |
||||
'darkviolet': Color(0xff9400d3), |
||||
'deeppink': Color(0xffff1493), |
||||
'deepskyblue': Color(0xff00bfff), |
||||
'dimgray': Color(0xff696969), |
||||
'dimgrey': Color(0xff696969), |
||||
'dodgerblue': Color(0xff1e90ff), |
||||
'firebrick': Color(0xffb22222), |
||||
'floralwhite': Color(0xfffffaf0), |
||||
'forestgreen': Color(0xff228b22), |
||||
'fuchsia': Color(0xffff00ff), |
||||
'gainsboro': Color(0xffdcdcdc), |
||||
'ghostwhite': Color(0xfff8f8ff), |
||||
'gold': Color(0xffffd700), |
||||
'goldenrod': Color(0xffdaa520), |
||||
'gray': Color(0xff808080), |
||||
'green': Color(0xff008000), |
||||
'greenyellow': Color(0xffadff2f), |
||||
'grey': Color(0xff808080), |
||||
'honeydew': Color(0xfff0fff0), |
||||
'hotpink': Color(0xffff69b4), |
||||
'indianred': Color(0xffcd5c5c), |
||||
'indigo': Color(0xff4b0082), |
||||
'ivory': Color(0xfffffff0), |
||||
'khaki': Color(0xfff0e68c), |
||||
'laserlemon': Color(0xffffff54), |
||||
'lavender': Color(0xffe6e6fa), |
||||
'lavenderblush': Color(0xfffff0f5), |
||||
'lawngreen': Color(0xff7cfc00), |
||||
'lemonchiffon': Color(0xfffffacd), |
||||
'lightblue': Color(0xffadd8e6), |
||||
'lightcoral': Color(0xfff08080), |
||||
'lightcyan': Color(0xffe0ffff), |
||||
'lightgoldenrod': Color(0xfffafad2), |
||||
'lightgoldenrodyellow': Color(0xfffafad2), |
||||
'lightgray': Color(0xffd3d3d3), |
||||
'lightgreen': Color(0xff90ee90), |
||||
'lightgrey': Color(0xffd3d3d3), |
||||
'lightpink': Color(0xffffb6c1), |
||||
'lightsalmon': Color(0xffffa07a), |
||||
'lightseagreen': Color(0xff20b2aa), |
||||
'lightskyblue': Color(0xff87cefa), |
||||
'lightslategray': Color(0xff778899), |
||||
'lightslategrey': Color(0xff778899), |
||||
'lightsteelblue': Color(0xffb0c4de), |
||||
'lightyellow': Color(0xffffffe0), |
||||
'lime': Color(0xff00ff00), |
||||
'limegreen': Color(0xff32cd32), |
||||
'linen': Color(0xfffaf0e6), |
||||
'magenta': Color(0xffff00ff), |
||||
'maroon': Color(0xff800000), |
||||
'maroon2': Color(0xff7f0000), |
||||
'maroon3': Color(0xffb03060), |
||||
'mediumaquamarine': Color(0xff66cdaa), |
||||
'mediumblue': Color(0xff0000cd), |
||||
'mediumorchid': Color(0xffba55d3), |
||||
'mediumpurple': Color(0xff9370db), |
||||
'mediumseagreen': Color(0xff3cb371), |
||||
'mediumslateblue': Color(0xff7b68ee), |
||||
'mediumspringgreen': Color(0xff00fa9a), |
||||
'mediumturquoise': Color(0xff48d1cc), |
||||
'mediumvioletred': Color(0xffc71585), |
||||
'midnightblue': Color(0xff191970), |
||||
'mintcream': Color(0xfff5fffa), |
||||
'mistyrose': Color(0xffffe4e1), |
||||
'moccasin': Color(0xffffe4b5), |
||||
'navajowhite': Color(0xffffdead), |
||||
'navy': Color(0xff000080), |
||||
'oldlace': Color(0xfffdf5e6), |
||||
'olive': Color(0xff808000), |
||||
'olivedrab': Color(0xff6b8e23), |
||||
'orange': Color(0xffffa500), |
||||
'orangered': Color(0xffff4500), |
||||
'orchid': Color(0xffda70d6), |
||||
'palegoldenrod': Color(0xffeee8aa), |
||||
'palegreen': Color(0xff98fb98), |
||||
'paleturquoise': Color(0xffafeeee), |
||||
'palevioletred': Color(0xffdb7093), |
||||
'papayawhip': Color(0xffffefd5), |
||||
'peachpuff': Color(0xffffdab9), |
||||
'peru': Color(0xffcd853f), |
||||
'pink': Color(0xffffc0cb), |
||||
'plum': Color(0xffdda0dd), |
||||
'powderblue': Color(0xffb0e0e6), |
||||
'purple': Color(0xff800080), |
||||
'purple2': Color(0xff7f007f), |
||||
'purple3': Color(0xffa020f0), |
||||
'rebeccapurple': Color(0xff663399), |
||||
'red': Color(0xffff0000), |
||||
'rosybrown': Color(0xffbc8f8f), |
||||
'royalblue': Color(0xff4169e1), |
||||
'saddlebrown': Color(0xff8b4513), |
||||
'salmon': Color(0xfffa8072), |
||||
'sandybrown': Color(0xfff4a460), |
||||
'seagreen': Color(0xff2e8b57), |
||||
'seashell': Color(0xfffff5ee), |
||||
'sienna': Color(0xffa0522d), |
||||
'silver': Color(0xffc0c0c0), |
||||
'skyblue': Color(0xff87ceeb), |
||||
'slateblue': Color(0xff6a5acd), |
||||
'slategray': Color(0xff708090), |
||||
'slategrey': Color(0xff708090), |
||||
'snow': Color(0xfffffafa), |
||||
'springgreen': Color(0xff00ff7f), |
||||
'steelblue': Color(0xff4682b4), |
||||
'tan': Color(0xffd2b48c), |
||||
'teal': Color(0xff008080), |
||||
'thistle': Color(0xffd8bfd8), |
||||
'tomato': Color(0xffff6347), |
||||
'turquoise': Color(0xff40e0d0), |
||||
'violet': Color(0xffee82ee), |
||||
'wheat': Color(0xfff5deb3), |
||||
'white': Color(0xffffffff), |
||||
'whitesmoke': Color(0xfff5f5f5), |
||||
'yellow': Color(0xffffff00), |
||||
'yellowgreen': Color(0xff9acd32), |
||||
}; |
||||
|
||||
Color? colorFromName(String val) => |
||||
x11Colors[val.trim().replaceAll(' ', '').toLowerCase()]; |
||||
|
||||
extension ColorExtension on String { |
||||
Color? toColor() => colorFromName(this); |
||||
} |
@ -0,0 +1,384 @@ |
||||
// ignore_for_file: type=lint |
||||
|
||||
/// Material Color Picker |
||||
|
||||
library material_colorpicker; |
||||
|
||||
import 'package:flutter/gestures.dart'; |
||||
import 'package:flutter/material.dart'; |
||||
import 'utils.dart'; |
||||
|
||||
// The Color Picker which contains Material Design Color Palette. |
||||
class MaterialPicker extends StatefulWidget { |
||||
const MaterialPicker({ |
||||
Key? key, |
||||
required this.pickerColor, |
||||
required this.onColorChanged, |
||||
this.onPrimaryChanged, |
||||
this.enableLabel = false, |
||||
this.portraitOnly = false, |
||||
}) : super(key: key); |
||||
|
||||
final Color pickerColor; |
||||
final ValueChanged<Color> onColorChanged; |
||||
final ValueChanged<Color>? onPrimaryChanged; |
||||
final bool enableLabel; |
||||
final bool portraitOnly; |
||||
|
||||
@override |
||||
State<StatefulWidget> createState() => _MaterialPickerState(); |
||||
} |
||||
|
||||
class _MaterialPickerState extends State<MaterialPicker> { |
||||
final List<List<Color>> _colorTypes = [ |
||||
[Colors.red, Colors.redAccent], |
||||
[Colors.pink, Colors.pinkAccent], |
||||
[Colors.purple, Colors.purpleAccent], |
||||
[Colors.deepPurple, Colors.deepPurpleAccent], |
||||
[Colors.indigo, Colors.indigoAccent], |
||||
[Colors.blue, Colors.blueAccent], |
||||
[Colors.lightBlue, Colors.lightBlueAccent], |
||||
[Colors.cyan, Colors.cyanAccent], |
||||
[Colors.teal, Colors.tealAccent], |
||||
[Colors.green, Colors.greenAccent], |
||||
[Colors.lightGreen, Colors.lightGreenAccent], |
||||
[Colors.lime, Colors.limeAccent], |
||||
[Colors.yellow, Colors.yellowAccent], |
||||
[Colors.amber, Colors.amberAccent], |
||||
[Colors.orange, Colors.orangeAccent], |
||||
[Colors.deepOrange, Colors.deepOrangeAccent], |
||||
[Colors.brown], |
||||
[Colors.grey], |
||||
[Colors.blueGrey], |
||||
[Colors.black], |
||||
]; |
||||
|
||||
List<Color> _currentColorType = [Colors.red, Colors.redAccent]; |
||||
Color _currentShading = Colors.transparent; |
||||
|
||||
List<Map<Color, String>> _shadingTypes(List<Color> colors) { |
||||
List<Map<Color, String>> result = []; |
||||
|
||||
for (Color colorType in colors) { |
||||
if (colorType == Colors.grey) { |
||||
result.addAll([ |
||||
50, |
||||
100, |
||||
200, |
||||
300, |
||||
350, |
||||
400, |
||||
500, |
||||
600, |
||||
700, |
||||
800, |
||||
850, |
||||
900 |
||||
].map((int shade) => {Colors.grey[shade]!: shade.toString()}).toList()); |
||||
} else if (colorType == Colors.black || colorType == Colors.white) { |
||||
result.addAll([ |
||||
{Colors.black: ''}, |
||||
{Colors.white: ''} |
||||
]); |
||||
} else if (colorType is MaterialAccentColor) { |
||||
result.addAll([100, 200, 400, 700] |
||||
.map((int shade) => {colorType[shade]!: 'A$shade'}) |
||||
.toList()); |
||||
} else if (colorType is MaterialColor) { |
||||
result.addAll([50, 100, 200, 300, 400, 500, 600, 700, 800, 900] |
||||
.map((int shade) => {colorType[shade]!: shade.toString()}) |
||||
.toList()); |
||||
} else { |
||||
result.add({const Color(0x00000000): ''}); |
||||
} |
||||
} |
||||
|
||||
return result; |
||||
} |
||||
|
||||
@override |
||||
void initState() { |
||||
for (List<Color> _colors in _colorTypes) { |
||||
_shadingTypes(_colors).forEach((Map<Color, String> color) { |
||||
if (widget.pickerColor.value == color.keys.first.value) { |
||||
return setState(() { |
||||
_currentColorType = _colors; |
||||
_currentShading = color.keys.first; |
||||
}); |
||||
} |
||||
}); |
||||
} |
||||
super.initState(); |
||||
} |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
bool _isPortrait = |
||||
MediaQuery.of(context).orientation == Orientation.portrait || |
||||
widget.portraitOnly; |
||||
|
||||
Widget _colorList() { |
||||
return Container( |
||||
clipBehavior: Clip.hardEdge, |
||||
decoration: const BoxDecoration(), |
||||
child: Container( |
||||
margin: _isPortrait |
||||
? const EdgeInsets.only(right: 10) |
||||
: const EdgeInsets.only(bottom: 10), |
||||
width: _isPortrait ? 60 : null, |
||||
height: _isPortrait ? null : 60, |
||||
decoration: BoxDecoration( |
||||
color: Theme.of(context).cardColor, |
||||
boxShadow: [ |
||||
BoxShadow( |
||||
color: (Theme.of(context).brightness == Brightness.light) |
||||
? (Theme.of(context).brightness == Brightness.light) |
||||
? Colors.grey[300]! |
||||
: Colors.black38 |
||||
: Colors.black38, |
||||
blurRadius: 10) |
||||
], |
||||
border: _isPortrait |
||||
? Border( |
||||
right: BorderSide( |
||||
color: |
||||
(Theme.of(context).brightness == Brightness.light) |
||||
? Colors.grey[300]! |
||||
: Colors.black38, |
||||
width: 1)) |
||||
: Border( |
||||
top: BorderSide( |
||||
color: |
||||
(Theme.of(context).brightness == Brightness.light) |
||||
? Colors.grey[300]! |
||||
: Colors.black38, |
||||
width: 1)), |
||||
), |
||||
child: ScrollConfiguration( |
||||
behavior: ScrollConfiguration.of(context) |
||||
.copyWith(dragDevices: PointerDeviceKind.values.toSet()), |
||||
child: ListView( |
||||
scrollDirection: _isPortrait ? Axis.vertical : Axis.horizontal, |
||||
children: [ |
||||
_isPortrait |
||||
? const Padding(padding: EdgeInsets.only(top: 7)) |
||||
: const Padding(padding: EdgeInsets.only(left: 7)), |
||||
..._colorTypes.map((List<Color> _colors) { |
||||
Color _colorType = _colors[0]; |
||||
return GestureDetector( |
||||
onTap: () { |
||||
if (widget.onPrimaryChanged != null) |
||||
widget.onPrimaryChanged!(_colorType); |
||||
setState(() => _currentColorType = _colors); |
||||
}, |
||||
child: Container( |
||||
color: const Color(0x00000000), |
||||
padding: _isPortrait |
||||
? const EdgeInsets.fromLTRB(0, 7, 0, 7) |
||||
: const EdgeInsets.fromLTRB(7, 0, 7, 0), |
||||
child: Align( |
||||
child: AnimatedContainer( |
||||
duration: const Duration(milliseconds: 300), |
||||
width: 25, |
||||
height: 25, |
||||
decoration: BoxDecoration( |
||||
color: _colorType, |
||||
shape: BoxShape.circle, |
||||
boxShadow: _currentColorType == _colors |
||||
? [ |
||||
_colorType == Theme.of(context).cardColor |
||||
? BoxShadow( |
||||
color: |
||||
(Theme.of(context).brightness == |
||||
Brightness.light) |
||||
? Colors.grey[300]! |
||||
: Colors.black38, |
||||
blurRadius: 10, |
||||
) |
||||
: BoxShadow( |
||||
color: _colorType, |
||||
blurRadius: 10, |
||||
), |
||||
] |
||||
: null, |
||||
border: _colorType == Theme.of(context).cardColor |
||||
? Border.all( |
||||
color: (Theme.of(context).brightness == |
||||
Brightness.light) |
||||
? Colors.grey[300]! |
||||
: Colors.black38, |
||||
width: 1) |
||||
: null, |
||||
), |
||||
), |
||||
), |
||||
), |
||||
); |
||||
}), |
||||
_isPortrait |
||||
? const Padding(padding: EdgeInsets.only(top: 5)) |
||||
: const Padding(padding: EdgeInsets.only(left: 5)), |
||||
], |
||||
), |
||||
), |
||||
), |
||||
); |
||||
} |
||||
|
||||
Widget _shadingList() { |
||||
return ScrollConfiguration( |
||||
behavior: ScrollConfiguration.of(context) |
||||
.copyWith(dragDevices: PointerDeviceKind.values.toSet()), |
||||
child: ListView( |
||||
scrollDirection: _isPortrait ? Axis.vertical : Axis.horizontal, |
||||
children: [ |
||||
_isPortrait |
||||
? const Padding(padding: EdgeInsets.only(top: 15)) |
||||
: const Padding(padding: EdgeInsets.only(left: 15)), |
||||
..._shadingTypes(_currentColorType).map((Map<Color, String> color) { |
||||
final Color _color = color.keys.first; |
||||
return GestureDetector( |
||||
onTap: () { |
||||
setState(() => _currentShading = _color); |
||||
widget.onColorChanged(_color); |
||||
}, |
||||
child: Container( |
||||
color: const Color(0x00000000), |
||||
margin: _isPortrait |
||||
? const EdgeInsets.only(right: 10) |
||||
: const EdgeInsets.only(bottom: 10), |
||||
padding: _isPortrait |
||||
? const EdgeInsets.fromLTRB(0, 7, 0, 7) |
||||
: const EdgeInsets.fromLTRB(7, 0, 7, 0), |
||||
child: Align( |
||||
child: AnimatedContainer( |
||||
curve: Curves.fastOutSlowIn, |
||||
duration: const Duration(milliseconds: 500), |
||||
width: _isPortrait |
||||
? (_currentShading == _color ? 250 : 230) |
||||
: (_currentShading == _color ? 50 : 30), |
||||
height: _isPortrait ? 50 : 220, |
||||
decoration: BoxDecoration( |
||||
color: _color, |
||||
boxShadow: _currentShading == _color |
||||
? [ |
||||
(_color == Colors.white) || |
||||
(_color == Colors.black) |
||||
? BoxShadow( |
||||
color: (Theme.of(context).brightness == |
||||
Brightness.light) |
||||
? Colors.grey[300]! |
||||
: Colors.black38, |
||||
blurRadius: 10, |
||||
) |
||||
: BoxShadow( |
||||
color: _color, |
||||
blurRadius: 10, |
||||
), |
||||
] |
||||
: null, |
||||
border: |
||||
(_color == Colors.white) || (_color == Colors.black) |
||||
? Border.all( |
||||
color: (Theme.of(context).brightness == |
||||
Brightness.light) |
||||
? Colors.grey[300]! |
||||
: Colors.black38, |
||||
width: 1) |
||||
: null, |
||||
), |
||||
child: widget.enableLabel |
||||
? _isPortrait |
||||
? Row( |
||||
children: [ |
||||
Text( |
||||
' ${color.values.first}', |
||||
style: TextStyle( |
||||
color: useWhiteForeground(_color) |
||||
? Colors.white |
||||
: Colors.black), |
||||
), |
||||
Expanded( |
||||
child: Align( |
||||
alignment: Alignment.centerRight, |
||||
child: Text( |
||||
'#${(_color.toString().replaceFirst('Color(0xff', '').replaceFirst(')', '')).toUpperCase()} ', |
||||
style: TextStyle( |
||||
color: useWhiteForeground(_color) |
||||
? Colors.white |
||||
: Colors.black, |
||||
fontWeight: FontWeight.bold, |
||||
), |
||||
), |
||||
), |
||||
), |
||||
], |
||||
) |
||||
: AnimatedOpacity( |
||||
duration: const Duration(milliseconds: 300), |
||||
opacity: _currentShading == _color ? 1 : 0, |
||||
child: Container( |
||||
padding: const EdgeInsets.only(top: 16), |
||||
alignment: Alignment.topCenter, |
||||
child: Text( |
||||
color.values.first, |
||||
style: TextStyle( |
||||
color: useWhiteForeground(_color) |
||||
? Colors.white |
||||
: Colors.black, |
||||
fontWeight: FontWeight.bold, |
||||
fontSize: 14, |
||||
), |
||||
softWrap: false, |
||||
), |
||||
), |
||||
) |
||||
: const SizedBox(), |
||||
), |
||||
), |
||||
), |
||||
); |
||||
}), |
||||
_isPortrait |
||||
? const Padding(padding: EdgeInsets.only(top: 15)) |
||||
: const Padding(padding: EdgeInsets.only(left: 15)), |
||||
], |
||||
), |
||||
); |
||||
} |
||||
|
||||
if (_isPortrait) { |
||||
return SizedBox( |
||||
width: 350, |
||||
height: 500, |
||||
child: Row( |
||||
children: <Widget>[ |
||||
_colorList(), |
||||
Expanded( |
||||
child: Padding( |
||||
padding: const EdgeInsets.symmetric(horizontal: 12), |
||||
child: _shadingList(), |
||||
), |
||||
), |
||||
], |
||||
), |
||||
); |
||||
} else { |
||||
return SizedBox( |
||||
width: 500, |
||||
height: 300, |
||||
child: Column( |
||||
children: <Widget>[ |
||||
_colorList(), |
||||
Expanded( |
||||
child: Padding( |
||||
padding: const EdgeInsets.symmetric(vertical: 12), |
||||
child: _shadingList(), |
||||
), |
||||
), |
||||
], |
||||
), |
||||
); |
||||
} |
||||
} |
||||
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,224 @@ |
||||
// ignore_for_file: type=lint |
||||
|
||||
/// Common function lib |
||||
|
||||
import 'dart:math'; |
||||
import 'package:flutter/painting.dart'; |
||||
import 'colors.dart'; |
||||
|
||||
/// Check if is good condition to use white foreground color by passing |
||||
/// the background color, and optional bias. |
||||
/// |
||||
/// Reference: |
||||
/// |
||||
/// Old: https://www.w3.org/TR/WCAG20-TECHS/G18.html |
||||
/// |
||||
/// New: https://github.com/mchome/flutter_statusbarcolor/issues/40 |
||||
bool useWhiteForeground(Color backgroundColor, {double bias = 0.0}) { |
||||
// Old: |
||||
// return 1.05 / (color.computeLuminance() + 0.05) > 4.5; |
||||
|
||||
// New: |
||||
int v = sqrt(pow(backgroundColor.red, 2) * 0.299 + |
||||
pow(backgroundColor.green, 2) * 0.587 + |
||||
pow(backgroundColor.blue, 2) * 0.114) |
||||
.round(); |
||||
return v < 130 + bias ? true : false; |
||||
} |
||||
|
||||
/// Convert HSV to HSL |
||||
/// |
||||
/// Reference: https://en.wikipedia.org/wiki/HSL_and_HSV#HSV_to_HSL |
||||
HSLColor hsvToHsl(HSVColor color) { |
||||
double s = 0.0; |
||||
double l = 0.0; |
||||
l = (2 - color.saturation) * color.value / 2; |
||||
if (l != 0) { |
||||
if (l == 1) { |
||||
s = 0.0; |
||||
} else if (l < 0.5) { |
||||
s = color.saturation * color.value / (l * 2); |
||||
} else { |
||||
s = color.saturation * color.value / (2 - l * 2); |
||||
} |
||||
} |
||||
return HSLColor.fromAHSL( |
||||
color.alpha, |
||||
color.hue, |
||||
s.clamp(0.0, 1.0), |
||||
l.clamp(0.0, 1.0), |
||||
); |
||||
} |
||||
|
||||
/// Convert HSL to HSV |
||||
/// |
||||
/// Reference: https://en.wikipedia.org/wiki/HSL_and_HSV#HSL_to_HSV |
||||
HSVColor hslToHsv(HSLColor color) { |
||||
double s = 0.0; |
||||
double v = 0.0; |
||||
|
||||
v = color.lightness + |
||||
color.saturation * |
||||
(color.lightness < 0.5 ? color.lightness : 1 - color.lightness); |
||||
if (v != 0) s = 2 - 2 * color.lightness / v; |
||||
|
||||
return HSVColor.fromAHSV( |
||||
color.alpha, |
||||
color.hue, |
||||
s.clamp(0.0, 1.0), |
||||
v.clamp(0.0, 1.0), |
||||
); |
||||
} |
||||
|
||||
/// [RegExp] pattern for validation HEX color [String] inputs, allows only: |
||||
/// |
||||
/// * exactly 1 to 8 digits in HEX format, |
||||
/// * only Latin A-F characters, case insensitive, |
||||
/// * and integer numbers 0,1,2,3,4,5,6,7,8,9, |
||||
/// * with optional hash (`#`) symbol at the beginning (not calculated in length). |
||||
/// |
||||
/// ```dart |
||||
/// final RegExp hexInputValidator = RegExp(kValidHexPattern); |
||||
/// if (hexInputValidator.hasMatch(hex)) print('$hex might be a valid HEX color'); |
||||
/// ``` |
||||
/// Reference: https://en.wikipedia.org/wiki/Web_colors#Hex_triplet |
||||
const String kValidHexPattern = r'^#?[0-9a-fA-F]{1,8}'; |
||||
|
||||
/// [RegExp] pattern for validation complete HEX color [String], allows only: |
||||
/// |
||||
/// * exactly 6 or 8 digits in HEX format, |
||||
/// * only Latin A-F characters, case insensitive, |
||||
/// * and integer numbers 0,1,2,3,4,5,6,7,8,9, |
||||
/// * with optional hash (`#`) symbol at the beginning (not calculated in length). |
||||
/// |
||||
/// ```dart |
||||
/// final RegExp hexCompleteValidator = RegExp(kCompleteValidHexPattern); |
||||
/// if (hexCompleteValidator.hasMatch(hex)) print('$hex is valid HEX color'); |
||||
/// ``` |
||||
/// Reference: https://en.wikipedia.org/wiki/Web_colors#Hex_triplet |
||||
const String kCompleteValidHexPattern = |
||||
r'^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$'; |
||||
|
||||
/// Try to convert text input or any [String] to valid [Color]. |
||||
/// The [String] must be provided in one of those formats: |
||||
/// |
||||
/// * RGB |
||||
/// * #RGB |
||||
/// * RRGGBB |
||||
/// * #RRGGBB |
||||
/// * AARRGGBB |
||||
/// * #AARRGGBB |
||||
/// |
||||
/// Where: A stands for Alpha, R for Red, G for Green, and B for blue color. |
||||
/// It will only accept 3/6/8 long HEXs with an optional hash (`#`) at the beginning. |
||||
/// Allowed characters are Latin A-F case insensitive and numbers 0-9. |
||||
/// Optional [enableAlpha] can be provided (it's `true` by default). If it's set |
||||
/// to `false` transparency information (alpha channel) will be removed. |
||||
/// ```dart |
||||
/// /// // Valid 3 digit HEXs: |
||||
/// colorFromHex('abc') == Color(0xffaabbcc) |
||||
/// colorFromHex('ABc') == Color(0xffaabbcc) |
||||
/// colorFromHex('ABC') == Color(0xffaabbcc) |
||||
/// colorFromHex('#Abc') == Color(0xffaabbcc) |
||||
/// colorFromHex('#abc') == Color(0xffaabbcc) |
||||
/// colorFromHex('#ABC') == Color(0xffaabbcc) |
||||
/// // Valid 6 digit HEXs: |
||||
/// colorFromHex('aabbcc') == Color(0xffaabbcc) |
||||
/// colorFromHex('AABbcc') == Color(0xffaabbcc) |
||||
/// colorFromHex('AABBCC') == Color(0xffaabbcc) |
||||
/// colorFromHex('#AABbcc') == Color(0xffaabbcc) |
||||
/// colorFromHex('#aabbcc') == Color(0xffaabbcc) |
||||
/// colorFromHex('#AABBCC') == Color(0xffaabbcc) |
||||
/// // Valid 8 digit HEXs: |
||||
/// colorFromHex('ffaabbcc') == Color(0xffaabbcc) |
||||
/// colorFromHex('ffAABbcc') == Color(0xffaabbcc) |
||||
/// colorFromHex('ffAABBCC') == Color(0xffaabbcc) |
||||
/// colorFromHex('ffaabbcc', enableAlpha: true) == Color(0xffaabbcc) |
||||
/// colorFromHex('FFAAbbcc', enableAlpha: true) == Color(0xffaabbcc) |
||||
/// colorFromHex('ffAABBCC', enableAlpha: true) == Color(0xffaabbcc) |
||||
/// colorFromHex('FFaabbcc', enableAlpha: true) == Color(0xffaabbcc) |
||||
/// colorFromHex('#ffaabbcc') == Color(0xffaabbcc) |
||||
/// colorFromHex('#ffAABbcc') == Color(0xffaabbcc) |
||||
/// colorFromHex('#FFAABBCC') == Color(0xffaabbcc) |
||||
/// colorFromHex('#ffaabbcc', enableAlpha: true) == Color(0xffaabbcc) |
||||
/// colorFromHex('#FFAAbbcc', enableAlpha: true) == Color(0xffaabbcc) |
||||
/// colorFromHex('#ffAABBCC', enableAlpha: true) == Color(0xffaabbcc) |
||||
/// colorFromHex('#FFaabbcc', enableAlpha: true) == Color(0xffaabbcc) |
||||
/// // Invalid HEXs: |
||||
/// colorFromHex('bc') == null // length 2 |
||||
/// colorFromHex('aabbc') == null // length 5 |
||||
/// colorFromHex('#ffaabbccd') == null // length 9 (+#) |
||||
/// colorFromHex('aabbcx') == null // x character |
||||
/// colorFromHex('#aabbвв') == null // в non-latin character |
||||
/// colorFromHex('') == null // empty |
||||
/// ``` |
||||
/// Reference: https://en.wikipedia.org/wiki/Web_colors#Hex_triplet |
||||
Color? colorFromHex(String inputString, {bool enableAlpha = true}) { |
||||
// Registers validator for exactly 6 or 8 digits long HEX (with optional #). |
||||
final RegExp hexValidator = RegExp(kCompleteValidHexPattern); |
||||
// Validating input, if it does not match — it's not proper HEX. |
||||
if (!hexValidator.hasMatch(inputString)) return null; |
||||
// Remove optional hash if exists and convert HEX to UPPER CASE. |
||||
String hexToParse = inputString.replaceFirst('#', '').toUpperCase(); |
||||
// It may allow HEXs with transparency information even if alpha is disabled, |
||||
if (!enableAlpha && hexToParse.length == 8) { |
||||
// but it will replace this info with 100% non-transparent value (FF). |
||||
hexToParse = 'FF${hexToParse.substring(2)}'; |
||||
} |
||||
// HEX may be provided in 3-digits format, let's just duplicate each letter. |
||||
if (hexToParse.length == 3) { |
||||
hexToParse = hexToParse.split('').expand((i) => [i * 2]).join(); |
||||
} |
||||
// We will need 8 digits to parse the color, let's add missing digits. |
||||
if (hexToParse.length == 6) hexToParse = 'FF$hexToParse'; |
||||
// HEX must be valid now, but as a precaution, it will just "try" to parse it. |
||||
final intColorValue = int.tryParse(hexToParse, radix: 16); |
||||
// If for some reason HEX is not valid — abort the operation, return nothing. |
||||
if (intColorValue == null) return null; |
||||
// Register output color for the last step. |
||||
final color = Color(intColorValue); |
||||
// Decide to return color with transparency information or not. |
||||
return enableAlpha ? color : color.withAlpha(255); |
||||
} |
||||
|
||||
/// Converts `dart:ui` [Color] to the 6/8 digits HEX [String]. |
||||
/// |
||||
/// Prefixes a hash (`#`) sign if [includeHashSign] is set to `true`. |
||||
/// The result will be provided as UPPER CASE, it can be changed via [toUpperCase] |
||||
/// flag set to `false` (default is `true`). Hex can be returned without alpha |
||||
/// channel information (transparency), with the [enableAlpha] flag set to `false`. |
||||
String colorToHex( |
||||
Color color, { |
||||
bool includeHashSign = false, |
||||
bool enableAlpha = true, |
||||
bool toUpperCase = true, |
||||
}) { |
||||
final String hex = (includeHashSign ? '#' : '') + |
||||
(enableAlpha ? _padRadix(color.alpha) : '') + |
||||
_padRadix(color.red) + |
||||
_padRadix(color.green) + |
||||
_padRadix(color.blue); |
||||
return toUpperCase ? hex.toUpperCase() : hex; |
||||
} |
||||
|
||||
// Shorthand for padLeft of RadixString, DRY. |
||||
String _padRadix(int value) => value.toRadixString(16).padLeft(2, '0'); |
||||
|
||||
// Extension for String |
||||
extension ColorExtension1 on String { |
||||
Color? toColor() { |
||||
Color? color = colorFromName(this); |
||||
if (color != null) return color; |
||||
return colorFromHex(this); |
||||
} |
||||
} |
||||
|
||||
// Extension from Color |
||||
extension ColorExtension2 on Color { |
||||
String toHexString( |
||||
{bool includeHashSign = false, |
||||
bool enableAlpha = true, |
||||
bool toUpperCase = true}) => |
||||
colorToHex(this, |
||||
includeHashSign: false, enableAlpha: true, toUpperCase: true); |
||||
} |
Loading…
Reference in new issue