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