diff --git a/CHANGELOG.md b/CHANGELOG.md index 99005e1..b81b782 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,13 +2,47 @@ All notable changes to the **FlexColorPicker** package are documented in this file. +## 3.4.0 + +**Mar 3, 2024** + +Requires min Flutter 3.16.0 and Dart 3.0.0. + +**NEW** + +- Added enum values `filled` and `filledTonal` to `ColorPickerActionButtonType` and added support for these button styles as OK/Cancel buttons in the ColorPicker dialog. +- Added `dialogActionOnlyOkButton` to `ColorPickerActionButtons`. Defaults to false. If set to true and `dialogActionButtons` is true, only the OK button will be shown. +- Added support for a second custom color palette to the picker. In addition to `ColorPickerType.custom` there is now also a `ColorPickerType.customSecondary` picker selector. It gets its values from `ColorPicker.customSecondaryColorSwatchesAndNames`. +- Added support for transparent colors for both custom color palette pickers. They can now have opacity in the picker in their custom color values. This also works if the opacity and slider in `ColorPicker.enableOpacity` is not enabled. Nothing new is needed to use this feature. It works automatically when custom color palettes are used that have partially transparent colors in them. +- The color utilities `ColorTools.createPrimarySwatch` and `ColorTools.createAccentSwatch` now create color swatches with alpha channel value kept at its input values for all created swatch indexes. Previously they set alpha to `#FF`, even if the value might have been something else. Creating palettes with very low alpha in the source color will not produce pretty palettes, but it is now possible to create them. +- The Color picker received two new layout properties. Previously all vertical spacings between the column elements in the picker were controlled by the `ColorPicker` property `columnSpacing`. For two key elements, you can now override this spacing. + - Use `toolbarSpacing` to adjust the vertical spacing below the top toolbar header and its action buttons. The purpose is to enable using zero space or close to it, so the top toolbar and action buttons can be closer to the picker selection control than the rest of the spacing in the picker uses. + - Use `shadesSpacing` to adjust the vertical spacing after the Material-2 swatch palette. By setting it to zero or one, you can create a design where the Material-2 swatch-based palette is closer to or connected to the Material-3 tonal palette. As long as the tonal palette does not use a heading, of course. + - Both `toolbarSpacing` and `shadesSpacing` default to `columnSpacing` if they are not defined. + - More of these vertical spacing fine-tuning properties can be added if there is a need for them. + +**CHANGE** + +- Dialog **OK** and **Cancel** action buttons now use the `.icon` Material button variants, when icon usage is enabled. Previously they baked in the leading icon into the button child Widget. This version follows the Material design spec exactly. The visual change is minor, but it does look better now when icons are used. +- Recent colors now also capture the opacity of a selected color as a different color, it does this also when you change opacity. Selecting a color with opacity in the recent colors list will set the picker's opacity to the opacity the color in the recent colors list has. + +**FIX** + +Package +- Replaced APIs deprecated in Flutter 3.19.0. Replaced internally used deprecated APIs `RawKeyboardListener`, `RawKeyEvent`, `RawKeyDownEvent`, `RawKeyEventDataMacOs`, `RawKeyEventDataIos` with `Shortcut` APIs. +- When using custom transitions the `InheritedTheme.capture ` should use `actionButtons.useRootNavigator` value and not default it to true. Fixed. + +Web demo +- Reset to defaults did not reset settings for `wheelSquarePadding` and `wheelSquareBorderRadius`. Fixed. + + ## 3.3.1 **January 21, 2024** - FIX: Fixed issue [#71 _activeColorSwatchList init in Wheel with tonal palette case](https://github.com/rydmike/flex_color_picker/issues/71). - CHORE: Bump FlexSeedScheme to version 1.4.0. -- TEST: Add tests, improve test coverage (65% -> 74%) +- TEST: Improved test coverage (65% -> 74%) ## 3.3.0 @@ -24,7 +58,7 @@ All notable changes to the **FlexColorPicker** package are documented in this fi - When clicking on the color wheels square color box part inside the Hue circle, the click moved the selection on wheel when clicking close to the square edge. This is now fixed. The fix also introduces exact wheel tap/drag to start an operation of Hue wheel. Previously, the Hue wheel would start operating when taping or dragging on the square containing the Hue wheel, but outside the squared color area inside it. Now to start dragging or make a tap action, it must start on the Hue wheel. Dragging around outside it or inside it, once a drag operation has started, works as before. - Fixed issue [#66 White color selects multiple colors](https://github.com/rydmike/flex_color_picker/issues/66). - - Part of original design with the picker was to only have a given color value appear in one color palette. When adding custom color palettes or using tonal palettes, the same color values may appear in multiple palettes. Selecting such a color value would highlight all the palettes the color appears in. Tonal palettes always contain white and black colors, so it is particularly problematic when using them. This fix prevents showing the main color as selected in multiple palettes and avoids switching Material swatch palette when operating on a tonal palettes. As a part of this FIX, main Material swatch shade color index 500, or for Material accent swatch shade, color index 200, is only shown as selected when its color is actually selected in a Material swatch or Material tonal color tone. + - Part of original design with the picker was to only have a given color value appear in one color palette. When adding custom color palettes or using tonal palettes, the same color values may appear in multiple palettes. Selecting such a color value would highlight all the palettes the color appears in. Tonal palettes always contain white and black colors, so it is particularly problematic when using them. This fix prevents showing the main color as selected in multiple palettes and avoids switching Material swatch palette when operating on a tonal palettes. As a part of this FIX, main Material swatch shade color index 500 or Material accent swatch shade color index 200, is only shown as selected when its color is actually selected in a Material swatch or Material tonal color tone. ## 3.2.2 @@ -57,7 +91,7 @@ All notable changes to the **FlexColorPicker** package are documented in this fi **CHANGE** -* Requires minimum Flutter 3.7.0 and Dart 2.19.0 that is used by Flutter 3.17. Version 3.7.0 of Flutter broke the nullable `Overlay.of` API. The new API is `Overlay.maybeOf`. This forced a new release of **FlexColorPicker** that requires minimum Flutter 3.7 that breaks compatibility with older versions of Flutter. +* Requires minimum Flutter 3.7.0 and Dart 2.19.0 that Flutter 3.7.0 uses. Version 3.7.0 of Flutter broke the nullable `Overlay.of` API. The new API is `Overlay.maybeOf`. This forced a new release of **FlexColorPicker** that requires minimum Flutter 3.7 that breaks compatibility with older versions of Flutter. **FIX** @@ -165,18 +199,17 @@ All notable changes to the **FlexColorPicker** package are documented in this fi * Updated material_color_utilities to ^0.2.0. This version constraint does not work with Flutter 3.0.x stable or beta 3.3.x, and their earlier versions. - This dev release is required to use Flutter SDK **master** 3.1.0-0.0.pre.2111 or later, + This dev release is required to use Flutter SDK **master** `3.1.0-0.0.pre.2111` or later, that uses material_color_utilities 0.2.0. * For other (older) versions of Flutter SDK, you can use package version 2.5.0 that has a material_color_utilities version constraint of ^0.1.3. -* This release also updates Dart SDK constraint to '>=2.17.0 <3.0.0' and has Flutter listed as - '>=3.1.0-0.0.pre.2111'. +* This release also updates Dart SDK constraint to `'>=2.17.0 <3.0.0'` and has Flutter listed as `'>=3.1.0-0.0.pre.2111'`. **DOCS** -* Harmonized the changelog style and its past history. The new style and how it looks will be tested +* Harmonized the changelog style and its history. The new style and how it looks will be tested with a dev release to ensure it works well on pub. ## 2.5.0 @@ -203,9 +236,9 @@ All notable changes to the **FlexColorPicker** package are documented in this fi * The order of the action buttons Cancel - OK on the bottom of the built-in dialog can be changed to OK - Cancel. come in three flavors controlled by enum `ColorPickerActionButtonOrder` having values: - - `okIsRight` this is the default in order to no break past behavior. + - `okIsRight` this is the default, to no break past behavior. - `okIsLeft` - - `adaptive` order depends on platform. Windows uses okIsLeft others + - `adaptive` order depends on the used platform. Windows uses `okIsLeft` others `okIsRight`. The feature is enabled via the `ColorPickerActionButtons` configuration @@ -226,7 +259,7 @@ All notable changes to the **FlexColorPicker** package are documented in this fi When you do this, the copy-paste keyboard shortcuts will not work until one of the picker's components is focused by interacting with any of them. - The picker still grabs focus when you click on its background, as one way + The picker still grabs focus when you click on its background. This is used as a way to set focus to keyboard listener to enable copy-paste keyboard shortcuts or when you operate any of its controls, the control in question always gains focus. @@ -284,15 +317,15 @@ All notable changes to the **FlexColorPicker** package are documented in this fi that has an optional `subHeading` widget, when tonal palette is enabled you can show an optional `tonalSubheading` widget above it. - - When you click/select a color in the color picker and tonal palette is + - When you click/select a color in the color picker, and tonal palette is enabled, a 13 shade Material 3 tonal-palette for the selected color will be - generated, always starting with black, tone 0 for the used seed color and - ending in white, tone 100. + generated. It always starts with black, tone 0 for the used seed color and + ends in white, tone 100. - The official Material 3 Dart library is used to create the tonal palette from any selected color. The color you select functions a seed color to generate the tonal palette and might not be included itself and selected in - the palette. You can, of course, click on any color in the generated palette to + the palette. You can click on any color in the generated palette to select and pick a color. - Selecting a color in the tonal palette, only selects the color in the palette. @@ -311,7 +344,7 @@ All notable changes to the **FlexColorPicker** package are documented in this fi It uses ColorScheme properties in its theme that were not available earlier and removed in 2.10.0 deprecated color properties from its theme. The color picker package itself still has the same version requirement as before - of Dart SDK: '>=2.14.0 < 3.0.0'. + of Dart SDK: `'>=2.14.0 < 3.0.0'`. ## 2.2.0 @@ -346,12 +379,12 @@ All notable changes to the **FlexColorPicker** package are documented in this fi * **Fix:** The `useRootNavigator` argument is now respected on all Navigator `pop` functions used in the `ColorPicker` widget itself and by - built-in dialogs used by the `ColorPicker`. In order to support this, + built-in dialogs used by the `ColorPicker`. To support this, the current `useRootNavigator` property in the `ColorPicker.showPickerDialog()` and in the function `showColorPickerDialog` had to be deprecated. The property has moved to become a configuration option in `ColorPickerActionButtons` - class in order to make it accessible to the Navigator pop functions both in + class to make it accessible to the Navigator pop functions both in the `ColorPicker` widget itself, as well as to built-in dialogs. The default behavior has not changed, the setting still defaults to using @@ -384,11 +417,10 @@ All notable changes to the **FlexColorPicker** package are documented in this fi **April 10, 2021** -* **New feature:** Enabled updating the color picker externally. Just set the `color` property of the widget to a - new value to update it. You can even "remote control" the color picker by updating the `color`, if so needed. +* **New feature:** Enabled updating the color picker externally. Set the `color` property of the widget to a new value to update it. You can even "remote control" the color picker by updating the `color`, if so needed. This is mostly a potential use-case for desktop and web, when the picker is not used in a dialog. - You can, of course, use this on a phone or tablet too, but often there is not enough space to keep the picker + You can use this on a phone or tablet too, but often there is not enough space to keep the picker visible on the main surface this way on mobile devices. However, on desktops it is certainly a valid use case that should be supported. It was previously not supported by design, but as we are going to support web/desktop use-cases, it should certainly be supported. This update adds support for it. The picker only @@ -443,44 +475,16 @@ In addition to breaking changes as a result of the null-safety implementation, t **April 8, 2021** * **Fix:** Setting `borderColor` did not change the border color on the wheel when `wheelHasBorder` was true. -* **New features:** The `showPickerDialog` method now exposes most (= not directly controlled) properties - of the underlying `AlertDialog` used to make the dialog, this includes e.g., the `backgroundColor`, `elevation`, - `clipBehavior` and `shape` as new exposed properties that may be useful. -* **New feature:** Added a new alternative color picker dialog function `showColorPickerDialog` that returns a - `Future` which when the dialog is closed, returns the selected color from the dialog or original start - color value, if no selection was made. - This picker might be simpler to use in some scenarios, but it does not allow - for the feature where colors and theme's can update in the background behind the dialog, as colors are selected - in it, before it is even closed. However, if you just need to open a dialog, select a color and move on, this - version offers a simpler API for that. Under the hood it is just a wrapper for the previous more - capable version with the onChange callbacks. It shares all other properties and features with the `ColorPicker` - combined with its `showPickerDialog` method, except all the **onChanged** callbacks that are excluded. - Since the properties `elevation` and `title` in the `showPickerDialog` method, would collide with the same - named properties in `ColorPicker`. The dialog's elevation and title in the `showColorPickerDialog` are - instead called `dialogElevation` and `dialogTitle` in it. +* **New features:** The `showPickerDialog` method now exposes most (= not directly controlled) properties of the underlying `AlertDialog` used to make the dialog, this includes e.g., the `backgroundColor`, `elevation`, `clipBehavior` and `shape` as new exposed properties that may be useful. +* **New feature:** Added a new alternative color picker dialog function `showColorPickerDialog` that returns a `Future` which when the dialog is closed, returns the selected color from the dialog or original start color value, if no selection was made. This picker might be simpler to use in some scenarios. However, it does not allow for the feature where colors and theme's can update in the background behind the dialog, as colors are selected in it, before it is even closed. However, if you just need to open a dialog, select a color and move on, this version offers a simpler API for that. Under the hood it is just a wrapper for the previous more capable version with the onChange callbacks. It shares all other properties and features with the `ColorPicker`, combined with its `showPickerDialog` method, except all the **onChanged** callbacks that are excluded. Since the properties `elevation` and `title` in the `showPickerDialog` method, would collide with the same named properties in `ColorPicker`. The dialog's elevation and title in the `showColorPickerDialog` are instead called `dialogElevation` and `dialogTitle` in it. + * **Improvement:** Performance was improved via more optimized rebuilds. -* **Documentation:** The first version of updated documentation with API guide documentation is now included. It still - requires proofreading before stable release, but getting close to being ready for release now. -* **Default example:** The default example got a new picker that shows how to the new `showColorPickerDialog` function. -* **Web example:** The Web example, with the built-in API tooltips guides, got a major rewrite. It was originally - not intended to be as large as it grew to be, but since it grew so much it needed a rewrite. - It now uses Riverpod to make its simple state management needs easy to handle and much cleaner than before. - It also includes persisting the settings directly as settings are changed in the app. Persistence is - implemented with Hive, and should work on all Flutter platforms as well, but it has only been tested on Android, - Web and Windows. - As an experiment, only RiverPod StateProviders were used. While the setup is a bit tedious, it enables the desired - fine-grained control over rebuilds of all the used setting control widgets. Each setting is also stored as an - individual key-value pair in the used Hive box. - A ProviderObserver that observes changes in the StateProviders we want to persist is used to save any state change - to the used Hive box, regardless of where the state is changed in the demo app. This setup was an experiment to - see if it might work and provide some simplification benefits. At least in this case it did, and it is also a pretty - interesting and simple solution. - The default start values are also defined via the Riverpod StateProvider's default values, that also - use their const Hive string key as their provider name. Each StateProvider gets its start setting value from - the Hive box with the same key. If the key does not exist yet in Hive, it falls back to a default value from a - const Map using the same string const as its key, for the default fallback value. Reset back to default values is - also done by setting all providers' state back to their default values as defined by the same const - fallback value map. +* **Documentation:** The first version of updated documentation with API guide documentation is now included. It still requires proofreading before stable release, but getting close to being ready for release now. +* **Default example:** The default example got a new picker that shows how the new `showColorPickerDialog` functions. +* **Web example:** The Web example, with the built-in API tooltips guides, got a major rewrite. It was originally not intended to be as large as it grew to be, but since it grew so much it needed a rewrite. + * It now uses Riverpod to make its simple state management needs easy to handle and much cleaner than before. It also includes persisting the settings directly as settings are changed in the app. Persistence is implemented with Hive. It should work on all Flutter platforms as well, but it has only been tested on Android, Web and Windows. + * As an experiment, only RiverPod StateProviders were used. While the setup is a bit tedious, it enables the desired fine-grained control over rebuilds of all the used setting control widgets. Each setting is also stored as an individual key-value pair in the used Hive box. A ProviderObserver that observes changes in the StateProviders we want to persist is used to save any state change to the used Hive box, regardless of where the state is changed in the demo app. + * This setup was an experiment to see if it might work and provide some simplification benefits. At least in this case, it did. It is also a pretty interesting and simple solution. The default start values are also defined via the Riverpod StateProvider's default values, that also use their const Hive string key as their provider name. Each StateProvider gets its start setting value from the Hive box with the same key. If the key does not exist yet in Hive, it falls back to a default value from a const Map using the same string const as its key, for the default fallback value. Reset back to default values is also done by setting all providers' state back to their default values as defined by the same const fallback value map. ## 2.0.0-nullsafety.4 @@ -506,7 +510,7 @@ In addition to breaking changes as a result of the null-safety implementation, t only accepts valid hex input and converts all inputs to uppercase. * **New property:** If `colorCodeHasColor` is true, then the background of the color code entry field uses the current selected color. -* **New property** If `colorCodeReadOnly` the color code entry field is always read only. Normally, color code can +* **New property** If `colorCodeReadOnly` the color code entry field is always read-only. Normally, color code can be edited on the wheel picker, set this to true to make it read only there as well. Copy/paste operations still work if they are enabled even if the color code field entry is in read-only mode. * **New feature:** The `copyPasteBehavior` property received three new features and properties: @@ -546,23 +550,15 @@ example folder, in "example/lib/demo/main.dart". * **Improvement:** The wheel picker now moves on pointer-down to point location, it no longer requires a slight movement for its thumbs to move to the selected start tracking point. -* **Improvements:** Keyboard traversal of the colors and selecting indicator colors with the keyboard via - enter or space. The wheel can however still not be operated with a keyboard, only touch and mouse controlled. -* **New property:** `onColorChangeStart` called when user starts color selection with current color before the change. +* **Improvements:** Keyboard traversal of the colors and selecting indicator colors with the keyboard using enter or space key. The wheel can however still not be operated with a keyboard, only touch and mouse controlled. +* **New property:** `onColorChangeStart` called when user starts color selection with current color before the change. * **New property:** `onColorChangeEnd` called when user ends color selection with the new color value. -* **New property:** `selectedPickerTypeColor` the color of the thumb on the slider that shows the selected picker. - Ported from none null-safe version 1.1.4, does not exist in version 2.0.0-nullsafety.0. +* **New property:** `selectedPickerTypeColor` the color of the thumb on the slider that shows the selected picker. Ported from none null-safe version 1.1.4, does not exist in version 2.0.0-nullsafety.0. * **New property:** `colorCodePrefixStyle` defines the text style of the prefix for the color code. If not defined it defaults to same style as `colorCodeTextStyle`. Ported from none null-safe version 1.1.4, does not exist in version 2.0.0-nullsafety.0. -* **New property:** `title` is a Widget used as an app bar type of title widget above the heading. Can also - include copy, paste, select-close and cancel-cancel icon buttons when the picker is used as a dialog. -* **New feature:** There is an `actionButtons` property that takes an `ColorPickerActionButtons()`. It is used to - define what type of **Ok** and **Cancel** action buttons the color picker has when used in a dialog. - It is possible to define if bottom action buttons should be `TextButton`, `OutlinedButton` or `ElevatedButton` - per button. If not defined, the labels on the buttons come from Material localizations, not from - hard-coded default values. See breaking label for the 'Select' label. There are optional select/OK and - cancel icon buttons that can be used in the title bar for a more compact dialog. +* **New property:** `title` is a Widget used as an app bar type of title widget above the heading. Can also include copy, paste, select-close and cancel-cancel icon buttons when the picker is used as a dialog. +* **New feature:** There is an `actionButtons` property that takes an `ColorPickerActionButtons()`. It is used to define what type of **Ok** and **Cancel** action buttons the color picker has when used in a dialog. It is possible to define if bottom action buttons should be `TextButton`, `OutlinedButton` or `ElevatedButton` per button. If not defined, the labels on the buttons come from Material localizations, not from hard-coded default values. See breaking label for the 'Select' label. There are optional select/OK and cancel icon buttons that can be used in the title bar for a more compact dialog. * **New feature**: There is a `copyPasteBehavior` property that takes an `ColorPickerCopyPasteBehavior()`. It is used to define the copy/paste behavior of the color picker, including: * Keyboard shortcuts: CTRL-C, CMD-C, CTRL-V, CMD-V @@ -571,28 +567,18 @@ example folder, in "example/lib/demo/main.dart". All copy/paste behaviors are optional and can be enabled based on what is needed. - For the copy format, the desired resulting RGB color string format can be configured to use #RRGGBB - RRGGBB #AARRGGBB AARRGGBB and 0xAARRGGBB (default) options. The selected copy format is indicated with the - corresponding prefix in the color code display/edit field when it is enabled. + For the copy format, the desired resulting RGB color string format can be configured to use #RRGGBB RRGGBB #AARRGGBB AARRGGBB and 0xAARRGGBB (default) options. The selected copy format is indicated with the corresponding prefix in the color code display/edit field when it is enabled. - Paste supports parsing multiple RGB color string formats. It automatically detects what format is used and auto - parses to correct Flutter/Dart color value. You can, for example, paste string formatted as #RRGGBB RRGGBB #AARRGGBB - AARRGGBB #RGB RGB or 0xAARRGGBB, partial color string values also work. You can also activate - a snack bar that informs the users if they paste color strings in an unsupported RGB string format into the - color picker. + Paste supports parsing multiple RGB color string formats. It automatically detects what format is used and auto parses to correct Flutter/Dart color value. You can, for example, paste string formatted as #RRGGBB RRGGBB #AARRGGBB AARRGGBB #RGB RGB or 0xAARRGGBB, partial color string values also work. You can also activate a snack bar that informs the users if they paste color strings in an unsupported RGB string format into the color picker. *See API documentation for more information.* -* **New feature**: The picker can display recently used colors in a list of color indicators at the bottom of - the picker. You can use the following properties to control it. +* **New feature**: The picker can display recently used colors in a list of color indicators at the bottom of the picker. You can use the following properties to control it. * `showRecentColors` set to true/false to enable/disable the usage of the recent colors feature. - * `recentColorsSubheading` subheading widget for the recently used colors. Typically, a Text widget, - e.g., Text('Recent colors'). If not provided, there is no sub heading for the recently used colors. + * `recentColorsSubheading` subheading widget for the recently used colors. Typically, a Text widget, e.g., `Text('Recent colors')`. If not provided, there is no sub heading for the recently used colors. * `maxRecentColors` number of recent colors to track, from 2 to 20 allowed. - * `recentColors` a list with current recent color, defaults to empty. You can store the last list - and use this list to restore the previous recent colors list. - * `onRecentColorsChanged` optional value callback that returns a copy the current list of recently - used colors. Use it to store a copy of the recent colors in order to be able to restore it later. + * `recentColors` a list with current recent color, defaults to empty. You can store the last list and use this list to restore the previous recent colors list. + * `onRecentColorsChanged` optional value callback that returns a copy the current list of recently used colors. Use it to store a copy of the recent colors to be able to restore it later. *See API documentation for more information.* @@ -600,15 +586,9 @@ example folder, in "example/lib/demo/main.dart". The following are **minor breaking changes** from version 1.1.5, they mostly concern visual nuances and label defaults. -* The `colorCodeIcon` has been deprecated and no longer has any function. To modify the copy icon on the color - code entry field, define the `ColorPickerCopyPasteBehavior(copyIcon: myIcon)` and provide it to the - `copyPasteBehavior` property, it defaults to same icon as in version 1.1.5. -* The bottom dialog action button that selects the color now says **OK** instead of **Select**. The label for the OK - button by default comes from a Material localization. You can as before change it to whatever string you want. -* The dialog bottom action button for **OK** by default now uses just a plain `TextButton` and - not an `OutlinedButton`, this change is done to conform to a less opinionated default style. You can still - manually configure it to use an `OutlinedButton` instead as before. Now you can choose, before there was - no choice. +* The `colorCodeIcon` has been deprecated and no longer has any function. To modify the copy icon on the color code entry field, define the `ColorPickerCopyPasteBehavior(copyIcon: myIcon)` and provide it to the `copyPasteBehavior` property, it defaults to same icon as in version 1.1.5. +* The bottom dialog action button that selects the color now says **OK** instead of **Select**. The label for the OK button by default comes from a Material localization. You can as before change it to whatever string you want. +* The dialog bottom action button for **OK** by default now uses just a plain `TextButton` and not an `OutlinedButton`, this change is done to conform to a less opinionated default style. You can still manually configure it to use an `OutlinedButton` instead as before. Now you can choose, before there was no choice. * The dialog bottom **OK** button is no longer autofocused. ## 2.0.0-nullsafety.0 @@ -616,29 +596,21 @@ The following are **minor breaking changes** from version 1.1.5, they mostly con **February 15, 2021** * First version with null safety. -* A workaround to issue [#71687](https://github.com/flutter/flutter/issues/71687) was introduced. - The issue has not been solved. However, the workaround allows for the Wrap implementation that was - changed to a Row in version 1.1.2, to be used again. -* The almost full API-configurable Web demo is included in the package in - "example/lib/demo/main.dart" together with the previous default example in "example/lib/main.dart". - Previously, this Web example was in a separate GitHub repository. The example was updated to make it - responsive, to offer better usability on Web builds. +* A workaround to issue [#71687](https://github.com/flutter/flutter/issues/71687) was introduced. The issue has not been solved. However, the workaround allows for the Wrap implementation, that was changed to a Row in version 1.1.2, to be used again. +* The almost full API-configurable Web demo is included in the package in "example/lib/demo/main.dart" together with the previous default example in `example/lib/main.dart`. Previously, this Web example was in a separate GitHub repository. The example was updated to make it responsive, to offer better usability on Web builds. ## 1.1.5 **March 3, 2021** -* **Fix:** When `selectedPickerTypeColor` color was undefined, the thumb did not receive the same text color as the - default and only one before in version 1.1.3 and earlier, in dark-mode. This broke compatibility with past style - when using dark-mode. This fix restores the correct past style when the `selectedPickerTypeColor` is undefined. +* **Fix:** When `selectedPickerTypeColor` color was undefined, the thumb did not receive the same text color as the default and only one before in version 1.1.3 and earlier, in dark-mode. This broke compatibility with past style when using dark-mode. This fix restores the correct past style when the `selectedPickerTypeColor` is undefined. ## 1.1.4 **March 3, 2021** * **Feature:** New property `selectedPickerTypeColor`: Defines the color of the thumb on the slider that shows the selected picker. -* **Feature:** New property `colorCodePrefixStyle`: Defines the text style of the prefix for the color code. - If not defined it defaults to same style as `colorCodeTextStyle`. +* **Feature:** New property `colorCodePrefixStyle`: Defines the text style of the prefix for the color code. If not defined it defaults to same style as `colorCodeTextStyle`. ## 1.1.3 @@ -654,8 +626,8 @@ The following are **minor breaking changes** from version 1.1.5, they mostly con **December 5, 2020** -* Temporary: The Wrap implementation for showing the color code and integer value was changed to a Row due to a regression in Flutter SDK causing a crash issue on channels dev and master when showing the ColorPicker in a Dialog. For more info see here: https://github.com/flutter/flutter/issues/71687 -When the issue is resolved, the implementation will be reverted to Wrap again. Using a Wrap has the added benefit of breaking the color code display+input field, and the rarely used int value, into two rows in case a large font is used in a narrow view when they are both configured to be shown. The Row may overflow in some rare cases. If you do not plan to use the ColorPicker with channels and versions affected by the issue, you can still use the previous version 1.1.1 to keep using the Wrap implementation if you need it. With normal styling, it is typically not needed. +* Temporary: The Wrap implementation for showing the color code and integer value was changed to a Row due to a regression in Flutter SDK causing a crash issue on channels dev and master when showing the ColorPicker in a Dialog. For more info see here: https://github.com/flutter/flutter/issues/71687 + * When the issue is resolved, the implementation will be reverted to Wrap again. Using a Wrap has the benefit of breaking the color code display+input field and the rarely used int value, into two rows in case a large font is used in a narrow view. The Row may overflow in some rare cases. If you do not plan to use the ColorPicker with channels and versions affected by the issue, you can still use the previous version 1.1.1 to keep using the Wrap implementation if you need it. With normal styling, it is typically not needed. * Fixed that the provided `TextStyle` via property `colorCodeTextStyle` was not also applied to the shown color integer value when `showColorValue` was set to `true`, as stated in API doc and intended. ## 1.1.1 @@ -724,16 +696,14 @@ When the issue is resolved, the implementation will be reverted to Wrap again. U # Planned Updates and New Features -These are the topics I currently have on the TODO list for this package. Do you have a new suggestion and idea? -Feel free to open a [suggestion or issue](https://github.com/rydmike/flex_color_picker/issues) in the repo. +These are the topics I currently have on the TODO list for this package. Do you have a new suggestion and idea? Feel free to open a [suggestion or issue](https://github.com/rydmike/flex_color_picker/issues) in the repo. -### TODO -- [ ] Additional controls for selecting active picker, maybe a custom slider and ToggleButtons. -- [ ] Add one more color picker type _advanced_, using only sliders as controls. -- [ ] Add support for other color formats than RGB. -- [ ] Maybe: Add selected colors to the custom colors section. -- [ ] Add more tests. -- [ ] Finalize tests. +- [ ] Additional controls for selecting active picker, maybe a custom slider and ToggleButtons, probably also a dropdown. +- [ ] Add one more color picker type _advanced_, using sliders as controls for the other formats. +- [ ] Add support for other color formats than RGB, HSL, HSV, CMYK, M3 HCT. +- [ ] Add possibility to in picker add selected colors to a custom picker. +- [ ] Reactor the code to prepare for making a major new version 4.0.0. +- [x] Add more tests, now at 84%, pretty good now, but even more tests are always welcome. - [x] Release the stable version 2.0.0 - [x] Add GitHub actions for test, analyze, coverage, build and web demo deployment. - [x] Add a simpler optional async dialog picker function that returns selected color. diff --git a/LICENSE b/LICENSE index 93c8b2b..19971a4 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ BSD 3-Clause License -Copyright (c) 2020, 2021, 2022 Mike Rydstrom (Rydmike) +Copyright (c) 2020-2024 Mike Rydstrom (Rydmike) All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/coverage/lcov.info b/coverage/lcov.info index 4eb94f4..468ac0e 100644 --- a/coverage/lcov.info +++ b/coverage/lcov.info @@ -1,46 +1,33 @@ SF:lib/src/models/color_picker_action_buttons.dart -DA:51,36 -DA:282,1 -DA:306,1 -DA:307,1 -DA:308,1 -DA:309,1 -DA:310,1 -DA:311,1 -DA:312,1 -DA:313,1 -DA:314,1 -DA:315,1 -DA:316,1 -DA:317,1 +DA:57,56 +DA:293,1 DA:318,1 DA:319,1 DA:320,1 DA:321,1 DA:322,1 DA:323,1 +DA:324,1 DA:325,1 +DA:326,1 DA:327,1 DA:328,1 DA:329,1 DA:330,1 -DA:334,2 -DA:337,3 -DA:338,1 -DA:339,3 -DA:340,3 -DA:341,3 -DA:342,3 -DA:343,3 -DA:344,3 -DA:345,3 -DA:346,3 -DA:347,3 -DA:348,3 -DA:349,3 -DA:350,3 +DA:331,1 +DA:332,1 +DA:333,1 +DA:335,1 +DA:336,1 +DA:337,1 +DA:339,1 +DA:341,1 +DA:342,1 +DA:343,1 +DA:344,1 +DA:348,2 DA:351,3 -DA:352,3 +DA:352,1 DA:353,3 DA:354,3 DA:355,3 @@ -50,23 +37,23 @@ DA:358,3 DA:359,3 DA:360,3 DA:361,3 -DA:364,1 -DA:365,2 -DA:366,1 -DA:367,1 -DA:368,1 -DA:369,1 -DA:370,1 -DA:371,1 -DA:372,1 -DA:373,1 -DA:374,1 -DA:375,1 -DA:376,1 -DA:377,1 -DA:378,1 +DA:362,3 +DA:363,3 +DA:364,3 +DA:365,3 +DA:366,3 +DA:367,3 +DA:368,3 +DA:369,3 +DA:370,3 +DA:371,3 +DA:372,3 +DA:373,3 +DA:374,3 +DA:375,3 +DA:376,3 DA:379,1 -DA:380,1 +DA:380,2 DA:381,1 DA:382,1 DA:383,1 @@ -74,39 +61,57 @@ DA:384,1 DA:385,1 DA:386,1 DA:387,1 +DA:388,1 +DA:389,1 DA:390,1 +DA:391,1 DA:392,1 -DA:393,3 -DA:394,3 -DA:395,3 -DA:396,3 -DA:397,3 -DA:398,3 -DA:399,3 +DA:393,1 +DA:394,1 +DA:395,1 +DA:396,1 +DA:397,1 +DA:398,1 +DA:399,1 DA:400,1 -DA:401,2 -DA:402,2 +DA:401,1 +DA:402,1 DA:403,1 -DA:404,1 -DA:405,2 -DA:407,3 -DA:408,3 +DA:406,1 +DA:408,1 +DA:409,3 DA:410,3 -DA:411,1 -DA:412,2 -DA:413,2 -DA:414,1 -DA:416,3 -DA:417,1 +DA:411,3 +DA:412,3 +DA:413,3 +DA:414,3 +DA:415,3 +DA:416,1 +DA:417,2 DA:418,2 -DA:419,2 +DA:419,1 DA:420,1 -DA:421,3 -DA:422,2 -DA:423,1 -DA:425,3 -LF:106 -LH:106 +DA:421,2 +DA:423,3 +DA:424,3 +DA:426,3 +DA:427,1 +DA:428,2 +DA:429,2 +DA:430,1 +DA:431,2 +DA:432,1 +DA:434,3 +DA:435,1 +DA:436,2 +DA:437,2 +DA:438,1 +DA:439,3 +DA:440,2 +DA:441,1 +DA:443,3 +LF:111 +LH:111 end_of_record SF:lib/src/functions/picker_functions.dart DA:14,4 @@ -125,11 +130,11 @@ DA:34,24 DA:35,4 DA:44,3 DA:46,3 -DA:47,1 -DA:48,1 -DA:50,1 -DA:51,1 -DA:52,1 +DA:47,2 +DA:48,2 +DA:50,2 +DA:51,2 +DA:52,2 DA:53,1 DA:54,1 DA:59,1 @@ -139,40 +144,45 @@ DA:63,1 DA:65,1 DA:66,1 DA:67,1 -DA:76,3 -DA:86,6 -DA:87,3 -DA:88,9 -DA:90,3 +DA:76,4 +DA:86,8 +DA:87,4 +DA:88,12 +DA:90,4 DA:92,3 +DA:98,4 DA:99,2 -DA:100,1 -DA:108,3 -DA:110,6 -DA:111,3 -DA:115,2 -DA:123,3 -DA:128,6 -DA:129,12 -DA:139,3 -DA:140,3 -DA:141,9 -DA:142,9 -DA:143,9 -DA:144,9 -DA:145,9 -DA:146,9 -DA:147,9 -DA:148,9 -DA:149,9 -DA:150,9 -DA:151,9 -DA:156,3 -DA:157,6 -DA:159,10 -DA:162,15 -LF:62 -LH:62 +DA:100,6 +DA:102,4 +DA:106,4 +DA:113,2 +DA:114,1 +DA:122,4 +DA:124,8 +DA:125,4 +DA:129,3 +DA:155,4 +DA:160,8 +DA:161,16 +DA:171,4 +DA:172,4 +DA:173,12 +DA:174,12 +DA:175,12 +DA:176,12 +DA:177,12 +DA:178,12 +DA:179,12 +DA:180,12 +DA:181,12 +DA:182,10 +DA:183,12 +DA:188,3 +DA:189,6 +DA:191,10 +DA:194,15 +LF:67 +LH:67 end_of_record SF:lib/src/universal_widgets/dry_intrisinic.dart DA:20,3 @@ -195,738 +205,858 @@ LF:16 LH:6 end_of_record SF:lib/src/color_indicator.dart -DA:12,4 -DA:26,6 -DA:27,6 -DA:28,6 -DA:29,6 -DA:109,3 -DA:110,3 -DA:116,3 -DA:118,6 -DA:119,3 -DA:122,1 -DA:126,2 -DA:127,2 +DA:12,5 +DA:26,8 +DA:27,8 +DA:28,8 +DA:29,8 +DA:109,4 +DA:110,4 +DA:116,4 +DA:118,8 +DA:119,4 +DA:122,2 +DA:126,4 +DA:127,4 DA:128,4 DA:129,2 -DA:131,1 -DA:134,3 -DA:136,6 -DA:137,3 -DA:140,3 -DA:144,12 -DA:150,12 -DA:152,3 -DA:154,6 -DA:155,6 -DA:157,3 -DA:158,9 -DA:159,6 +DA:131,2 +DA:134,4 +DA:136,8 +DA:137,4 +DA:140,4 +DA:144,16 +DA:150,16 +DA:152,4 +DA:154,8 +DA:155,8 +DA:157,4 +DA:158,12 +DA:159,8 DA:160,3 -DA:162,3 -DA:163,6 -DA:164,6 -DA:165,3 -DA:177,3 -DA:178,3 -DA:180,6 -DA:186,6 -DA:188,6 -DA:190,6 -DA:191,1 -DA:192,3 -DA:193,4 -DA:196,6 -DA:197,3 -DA:198,6 -DA:202,15 +DA:162,4 +DA:163,8 +DA:164,8 +DA:165,4 +DA:177,4 +DA:178,4 +DA:180,8 +DA:186,8 +DA:188,8 +DA:190,8 +DA:191,2 +DA:192,6 +DA:193,8 +DA:196,8 +DA:197,4 +DA:198,8 +DA:202,20 DA:203,0 -DA:204,9 +DA:204,12 LF:48 LH:47 end_of_record SF:lib/src/color_picker.dart -DA:70,2 -DA:162,6 -DA:164,6 -DA:166,6 -DA:168,4 -DA:169,6 -DA:171,6 -DA:174,6 -DA:176,3 -DA:178,6 -DA:180,6 -DA:182,6 -DA:184,6 -DA:187,4 -DA:188,2 -DA:697,2 -DA:698,2 -DA:722,0 -DA:901,0 -DA:904,0 -DA:908,0 -DA:909,0 -DA:912,0 -DA:913,0 -DA:914,0 -DA:916,0 -DA:918,0 -DA:922,0 -DA:923,0 -DA:924,0 -DA:925,0 -DA:926,0 -DA:927,0 -DA:932,0 -DA:933,0 -DA:934,0 -DA:935,0 -DA:936,0 -DA:941,0 -DA:942,0 -DA:943,0 -DA:944,0 -DA:945,0 -DA:954,0 -DA:955,0 -DA:958,0 -DA:959,0 -DA:960,0 -DA:962,0 -DA:964,0 -DA:968,0 -DA:969,0 -DA:970,0 -DA:971,0 -DA:972,0 -DA:973,0 -DA:978,0 -DA:979,0 -DA:980,0 -DA:981,0 -DA:982,0 -DA:987,0 -DA:988,0 -DA:989,0 -DA:990,0 -DA:991,0 -DA:1002,0 -DA:1003,0 -DA:1004,0 -DA:1006,0 -DA:1011,0 -DA:1017,0 -DA:1022,0 -DA:1023,0 -DA:1024,0 -DA:1027,0 -DA:1048,0 -DA:1054,0 -DA:1057,0 -DA:1059,0 -DA:1070,0 -DA:1072,0 -DA:1075,0 -DA:1078,0 -DA:1080,0 -DA:1085,0 -DA:1086,0 -DA:1091,0 -DA:1093,0 -DA:1094,0 -DA:1097,0 +DA:69,3 +DA:167,9 +DA:170,3 +DA:171,2 +DA:174,3 +DA:175,2 +DA:177,9 +DA:179,9 +DA:181,6 +DA:182,9 +DA:184,9 +DA:187,7 +DA:189,4 +DA:191,9 +DA:193,9 +DA:195,9 +DA:197,9 +DA:200,6 +DA:201,3 +DA:742,3 +DA:743,3 +DA:767,2 +DA:946,2 +DA:949,2 +DA:953,6 +DA:954,2 +DA:956,4 +DA:957,2 +DA:958,4 +DA:959,1 +DA:960,1 +DA:961,1 +DA:962,2 +DA:963,1 +DA:965,3 +DA:968,2 +DA:969,1 +DA:970,1 +DA:971,2 +DA:972,1 +DA:976,1 +DA:977,2 +DA:978,1 +DA:979,1 +DA:980,1 +DA:981,2 +DA:982,1 +DA:984,3 +DA:987,1 +DA:988,1 +DA:989,1 +DA:990,2 +DA:991,1 +DA:995,1 +DA:996,2 +DA:997,1 +DA:998,1 +DA:999,1 +DA:1000,2 +DA:1001,1 +DA:1003,3 +DA:1006,1 +DA:1007,1 +DA:1008,1 +DA:1009,2 +DA:1010,1 +DA:1014,1 +DA:1015,2 +DA:1016,1 +DA:1017,1 +DA:1018,1 +DA:1019,2 +DA:1020,1 +DA:1022,3 +DA:1025,1 +DA:1026,1 +DA:1027,1 +DA:1028,2 +DA:1029,1 +DA:1033,1 +DA:1034,2 +DA:1035,1 +DA:1036,1 +DA:1037,1 +DA:1038,2 +DA:1039,1 +DA:1041,3 +DA:1044,1 +DA:1045,1 +DA:1046,1 +DA:1047,2 +DA:1048,1 +DA:1056,6 +DA:1057,2 +DA:1059,4 +DA:1060,2 +DA:1061,4 +DA:1062,1 +DA:1063,1 +DA:1064,1 +DA:1065,2 +DA:1066,1 +DA:1068,3 +DA:1071,2 +DA:1072,1 +DA:1073,1 +DA:1074,2 +DA:1075,1 +DA:1079,1 +DA:1080,2 +DA:1081,1 +DA:1082,1 +DA:1083,1 +DA:1084,2 +DA:1085,1 +DA:1087,3 +DA:1090,1 +DA:1091,1 +DA:1092,1 +DA:1093,2 +DA:1094,1 +DA:1098,1 +DA:1099,2 +DA:1100,1 +DA:1101,1 +DA:1102,1 +DA:1103,2 +DA:1104,1 +DA:1106,3 +DA:1109,1 +DA:1110,1 +DA:1111,1 +DA:1112,2 +DA:1113,1 +DA:1117,1 +DA:1118,2 +DA:1119,1 +DA:1120,1 +DA:1121,1 +DA:1122,2 +DA:1123,1 +DA:1125,3 +DA:1128,1 +DA:1129,1 +DA:1130,1 +DA:1131,2 +DA:1132,1 +DA:1136,1 +DA:1137,2 +DA:1138,1 +DA:1139,1 +DA:1140,1 +DA:1141,2 +DA:1142,1 +DA:1144,3 +DA:1147,1 +DA:1148,1 +DA:1149,1 +DA:1150,2 +DA:1151,1 +DA:1161,4 +DA:1162,2 +DA:1163,3 +DA:1165,6 +DA:1170,2 +DA:1176,1 +DA:1181,4 +DA:1182,2 +DA:1183,1 +DA:1185,3 +DA:1186,2 +DA:1187,6 DA:1188,2 -DA:1190,2 -DA:1193,2 -DA:1194,14 -DA:1196,12 -DA:1198,4 -DA:1199,6 -DA:1202,6 -DA:1204,6 -DA:1206,6 -DA:1208,6 -DA:1210,6 -DA:1214,4 -DA:1219,8 -DA:1220,2 -DA:1222,6 -DA:1226,4 -DA:1228,6 -DA:1230,6 -DA:1232,6 -DA:1233,6 -DA:1236,6 -DA:1237,6 -DA:1239,6 -DA:1242,4 -DA:1243,4 -DA:1244,4 -DA:1246,2 -DA:1247,2 -DA:1249,2 -DA:1250,2 -DA:1253,2 -DA:1254,4 -DA:1256,8 -DA:1258,2 -DA:1260,8 -DA:1261,2 -DA:1264,0 -DA:1267,0 -DA:1273,0 -DA:1275,0 -DA:1276,0 -DA:1278,0 -DA:1279,0 -DA:1280,0 -DA:1284,0 -DA:1286,0 -DA:1288,0 -DA:1291,0 -DA:1292,0 -DA:1295,0 -DA:1298,0 -DA:1300,0 -DA:1303,0 -DA:1305,0 -DA:1312,0 -DA:1313,0 -DA:1314,0 -DA:1332,0 -DA:1335,0 -DA:1340,0 -DA:1341,0 -DA:1342,0 -DA:1344,0 -DA:1350,0 -DA:1352,0 -DA:1354,0 -DA:1356,0 -DA:1357,0 -DA:1360,0 -DA:1361,0 -DA:1363,0 -DA:1366,0 -DA:1367,0 -DA:1377,0 -DA:1378,0 -DA:1382,0 -DA:1384,0 -DA:1386,0 -DA:1387,0 -DA:1388,0 -DA:1390,0 -DA:1391,0 -DA:1394,0 -DA:1395,0 -DA:1397,0 -DA:1398,0 -DA:1405,0 -DA:1408,0 -DA:1410,0 -DA:1413,0 -DA:1417,2 -DA:1419,2 -DA:1426,2 -DA:1427,12 -DA:1431,2 -DA:1432,4 -DA:1434,4 -DA:1435,2 -DA:1436,2 -DA:1437,4 -DA:1438,4 +DA:1207,2 +DA:1213,4 +DA:1216,2 +DA:1218,3 +DA:1229,1 +DA:1231,1 +DA:1233,2 +DA:1234,1 +DA:1237,1 +DA:1239,1 +DA:1244,2 +DA:1245,2 +DA:1250,1 +DA:1252,2 +DA:1253,1 +DA:1256,2 +DA:1355,3 +DA:1357,3 +DA:1359,12 +DA:1360,9 +DA:1362,15 +DA:1364,6 +DA:1365,9 +DA:1368,9 +DA:1370,9 +DA:1372,9 +DA:1374,9 +DA:1377,9 +DA:1379,9 +DA:1383,6 +DA:1388,12 +DA:1390,12 +DA:1391,3 +DA:1393,6 +DA:1397,6 +DA:1399,9 +DA:1401,9 +DA:1403,9 +DA:1404,9 +DA:1407,9 +DA:1408,6 +DA:1411,9 +DA:1412,3 +DA:1414,9 +DA:1417,6 +DA:1418,6 +DA:1419,6 +DA:1421,3 +DA:1422,3 +DA:1424,3 +DA:1425,3 +DA:1426,3 +DA:1429,3 +DA:1430,6 +DA:1432,12 +DA:1434,3 +DA:1436,12 +DA:1437,3 +DA:1440,0 +DA:1442,0 DA:1444,0 -DA:1445,0 -DA:1446,0 -DA:1447,0 -DA:1448,0 -DA:1449,0 DA:1450,0 DA:1451,0 -DA:1452,0 DA:1453,0 DA:1454,0 DA:1455,0 DA:1459,0 -DA:1467,4 -DA:1468,4 -DA:1469,0 +DA:1461,0 +DA:1462,0 +DA:1464,0 +DA:1465,0 +DA:1467,0 +DA:1468,0 DA:1470,0 -DA:1475,2 -DA:1478,6 -DA:1480,8 -DA:1483,4 -DA:1484,4 -DA:1485,2 -DA:1486,4 -DA:1493,6 +DA:1471,0 +DA:1473,0 +DA:1474,0 +DA:1476,0 +DA:1477,0 +DA:1483,0 +DA:1485,0 +DA:1487,0 +DA:1490,0 +DA:1491,0 DA:1494,0 -DA:1495,6 -DA:1496,0 DA:1497,0 -DA:1498,2 -DA:1499,4 -DA:1500,0 -DA:1501,0 -DA:1503,4 -DA:1504,8 -DA:1514,4 -DA:1517,2 -DA:1519,4 -DA:1520,4 -DA:1521,4 -DA:1522,2 -DA:1525,2 -DA:1529,4 -DA:1530,6 -DA:1533,10 -DA:1538,10 -DA:1543,6 -DA:1544,6 -DA:1545,6 -DA:1546,6 -DA:1551,6 -DA:1552,6 -DA:1556,2 -DA:1557,2 -DA:1558,2 -DA:1563,2 -DA:1565,1 -DA:1566,1 -DA:1567,3 -DA:1568,3 -DA:1570,3 -DA:1572,3 -DA:1573,0 +DA:1499,0 +DA:1502,0 +DA:1505,0 +DA:1507,0 +DA:1513,0 +DA:1514,0 +DA:1515,0 +DA:1531,0 +DA:1534,0 +DA:1539,0 +DA:1540,0 +DA:1541,0 +DA:1542,0 +DA:1543,0 +DA:1544,0 +DA:1548,0 +DA:1554,0 +DA:1556,0 +DA:1558,0 +DA:1560,0 +DA:1561,0 +DA:1564,0 +DA:1565,0 +DA:1568,0 +DA:1569,0 +DA:1571,0 DA:1574,0 DA:1575,0 -DA:1577,0 -DA:1581,0 -DA:1583,0 +DA:1585,0 DA:1586,0 -DA:1587,0 -DA:1594,2 -DA:1595,4 -DA:1598,2 -DA:1599,4 -DA:1600,2 -DA:1602,4 -DA:1603,6 -DA:1604,6 -DA:1605,6 -DA:1606,6 -DA:1607,2 -DA:1608,6 -DA:1609,2 -DA:1610,4 -DA:1611,6 -DA:1612,2 -DA:1614,6 -DA:1615,2 +DA:1590,0 +DA:1591,0 +DA:1592,0 +DA:1594,0 +DA:1598,3 +DA:1600,3 +DA:1607,3 +DA:1608,18 +DA:1611,3 +DA:1612,6 +DA:1613,3 +DA:1614,3 +DA:1615,3 +DA:1616,6 DA:1617,6 -DA:1618,0 DA:1623,0 +DA:1624,0 DA:1625,0 DA:1626,0 DA:1627,0 +DA:1628,0 DA:1629,0 DA:1630,0 -DA:1634,6 +DA:1631,0 +DA:1632,0 +DA:1633,0 +DA:1634,0 DA:1635,0 +DA:1636,0 DA:1640,0 -DA:1642,0 -DA:1643,0 -DA:1644,0 -DA:1646,0 -DA:1647,0 -DA:1651,4 -DA:1652,4 -DA:1653,4 -DA:1657,4 -DA:1658,2 -DA:1659,6 -DA:1660,4 -DA:1663,2 -DA:1664,2 -DA:1665,2 -DA:1666,0 -DA:1667,0 -DA:1670,2 -DA:1671,2 -DA:1672,2 -DA:1673,2 -DA:1674,1 -DA:1680,2 -DA:1681,2 -DA:1682,1 -DA:1684,1 +DA:1648,6 +DA:1649,4 +DA:1650,0 +DA:1651,0 +DA:1656,3 +DA:1659,9 +DA:1661,12 +DA:1664,0 +DA:1668,6 +DA:1669,3 +DA:1670,3 +DA:1671,6 +DA:1674,0 +DA:1681,4 +DA:1682,0 +DA:1683,4 +DA:1684,3 +DA:1685,4 +DA:1686,0 DA:1687,2 -DA:1688,2 +DA:1688,6 DA:1689,0 DA:1690,0 -DA:1692,1 -DA:1693,1 +DA:1691,2 +DA:1692,6 +DA:1693,2 +DA:1694,3 DA:1696,4 -DA:1697,4 -DA:1698,4 -DA:1703,4 -DA:1706,4 -DA:1707,2 -DA:1708,4 -DA:1709,4 -DA:1710,4 -DA:1711,2 -DA:1712,4 -DA:1713,1 -DA:1714,1 -DA:1715,1 -DA:1717,4 -DA:1718,4 -DA:1719,4 -DA:1720,4 -DA:1721,4 -DA:1722,4 -DA:1723,4 -DA:1724,4 -DA:1725,2 -DA:1728,4 -DA:1729,2 -DA:1730,6 -DA:1731,2 -DA:1732,4 -DA:1733,4 -DA:1734,2 -DA:1735,4 -DA:1736,4 -DA:1737,4 -DA:1738,4 -DA:1739,4 -DA:1740,4 -DA:1741,2 -DA:1742,2 -DA:1743,1 -DA:1744,2 -DA:1745,3 +DA:1697,6 +DA:1708,7 +DA:1711,3 +DA:1713,6 +DA:1714,6 +DA:1715,6 +DA:1716,3 +DA:1719,3 +DA:1723,6 +DA:1724,9 +DA:1727,15 +DA:1731,15 +DA:1735,9 +DA:1736,9 +DA:1737,9 +DA:1738,9 +DA:1742,9 +DA:1743,9 DA:1746,3 -DA:1748,1 -DA:1749,2 -DA:1750,3 -DA:1751,1 -DA:1752,1 -DA:1753,1 -DA:1754,1 -DA:1755,1 -DA:1756,1 -DA:1757,1 -DA:1759,4 -DA:1761,1 -DA:1762,3 -DA:1763,2 -DA:1766,1 -DA:1767,2 -DA:1768,1 -DA:1775,4 -DA:1776,4 -DA:1777,4 -DA:1778,2 -DA:1779,6 -DA:1780,4 -DA:1783,4 -DA:1784,4 -DA:1785,4 -DA:1786,2 +DA:1747,3 +DA:1748,3 +DA:1750,9 +DA:1751,9 +DA:1753,9 +DA:1755,9 +DA:1756,0 +DA:1760,0 +DA:1761,0 +DA:1764,0 +DA:1765,0 +DA:1769,3 +DA:1771,3 +DA:1772,6 +DA:1773,3 +DA:1774,6 +DA:1775,3 +DA:1777,6 +DA:1778,9 +DA:1779,9 +DA:1780,9 +DA:1781,9 +DA:1782,2 +DA:1783,2 +DA:1784,8 +DA:1785,2 +DA:1786,4 DA:1787,6 -DA:1788,4 -DA:1791,4 -DA:1792,2 -DA:1793,4 -DA:1794,4 -DA:1795,4 -DA:1796,2 -DA:1797,4 -DA:1798,1 +DA:1788,2 +DA:1790,6 +DA:1791,2 +DA:1793,6 +DA:1794,1 DA:1799,1 -DA:1800,1 -DA:1801,2 -DA:1802,0 -DA:1803,0 -DA:1804,0 -DA:1808,4 -DA:1809,4 -DA:1810,4 -DA:1811,4 -DA:1812,4 -DA:1813,4 -DA:1814,4 -DA:1815,4 -DA:1816,2 -DA:1819,8 -DA:1820,2 -DA:1821,6 -DA:1822,4 -DA:1825,4 -DA:1826,2 +DA:1801,3 +DA:1802,1 +DA:1803,1 +DA:1805,3 +DA:1806,1 +DA:1810,6 +DA:1811,1 +DA:1816,1 +DA:1818,3 +DA:1819,1 +DA:1820,1 +DA:1822,3 +DA:1823,1 DA:1827,4 DA:1828,4 DA:1829,4 -DA:1830,4 -DA:1831,1 -DA:1832,1 -DA:1833,1 +DA:1833,6 DA:1834,2 -DA:1835,1 -DA:1836,1 -DA:1839,2 -DA:1840,4 -DA:1841,2 -DA:1842,8 -DA:1843,2 -DA:1844,4 -DA:1845,2 -DA:1846,8 -DA:1847,2 -DA:1848,4 -DA:1849,4 -DA:1850,4 -DA:1851,4 -DA:1852,4 +DA:1835,6 +DA:1836,4 +DA:1839,3 +DA:1840,3 +DA:1841,3 +DA:1842,3 +DA:1843,3 +DA:1844,3 +DA:1845,3 +DA:1846,1 +DA:1852,2 DA:1853,2 -DA:1856,8 -DA:1857,2 -DA:1858,6 -DA:1859,4 -DA:1862,4 -DA:1863,2 -DA:1864,6 -DA:1865,2 -DA:1866,6 -DA:1868,2 -DA:1869,2 -DA:1870,2 -DA:1871,4 -DA:1872,2 -DA:1873,4 -DA:1874,4 -DA:1875,2 -DA:1876,0 -DA:1877,0 -DA:1879,0 -DA:1880,0 -DA:1881,0 -DA:1882,0 -DA:1883,0 -DA:1885,0 -DA:1887,0 -DA:1889,0 -DA:1890,0 -DA:1891,0 -DA:1892,0 -DA:1893,0 -DA:1894,0 -DA:1895,0 -DA:1896,0 -DA:1897,0 -DA:1899,0 -DA:1901,0 -DA:1902,0 -DA:1903,0 -DA:1904,0 -DA:1905,0 -DA:1906,0 -DA:1908,0 -DA:1919,8 -DA:1920,2 -DA:1921,2 -DA:1923,4 -DA:1924,2 -DA:1925,6 -DA:1926,2 -DA:1927,2 -DA:1928,4 -DA:1930,4 -DA:1937,8 +DA:1854,1 +DA:1856,1 +DA:1859,2 +DA:1860,2 +DA:1861,0 +DA:1862,0 +DA:1864,1 +DA:1865,1 +DA:1868,6 +DA:1869,6 +DA:1870,6 +DA:1875,6 +DA:1878,6 +DA:1879,3 +DA:1880,6 +DA:1881,6 +DA:1882,6 +DA:1883,3 +DA:1885,11 +DA:1886,2 +DA:1887,2 +DA:1888,2 +DA:1890,6 +DA:1891,6 +DA:1892,6 +DA:1893,6 +DA:1894,6 +DA:1895,6 +DA:1896,6 +DA:1897,6 +DA:1898,3 +DA:1901,6 +DA:1902,2 +DA:1903,6 +DA:1904,2 +DA:1905,4 +DA:1906,4 +DA:1907,2 +DA:1908,2 +DA:1909,4 +DA:1910,4 +DA:1911,4 +DA:1912,4 +DA:1913,4 +DA:1914,2 +DA:1915,2 +DA:1916,1 +DA:1917,2 +DA:1918,3 +DA:1919,3 +DA:1921,1 +DA:1922,2 +DA:1923,1 +DA:1924,1 +DA:1925,1 +DA:1926,1 +DA:1927,1 +DA:1928,1 +DA:1929,1 +DA:1930,1 +DA:1931,1 +DA:1933,1 +DA:1934,5 +DA:1936,1 +DA:1937,3 DA:1938,2 -DA:1940,4 -DA:1941,2 -DA:1942,6 -DA:1943,2 -DA:1944,2 -DA:1945,4 -DA:1955,8 -DA:1956,2 -DA:1957,6 -DA:1958,2 -DA:1962,2 -DA:1964,4 -DA:1965,2 -DA:1966,2 -DA:1967,4 -DA:1968,4 -DA:1969,4 -DA:1970,4 -DA:1971,4 -DA:1972,4 -DA:1973,2 -DA:1974,0 -DA:1975,0 -DA:1976,0 -DA:1977,0 +DA:1941,1 +DA:1942,2 +DA:1943,1 +DA:1950,6 +DA:1951,4 +DA:1952,4 +DA:1953,2 +DA:1954,6 +DA:1955,4 +DA:1958,6 +DA:1959,4 +DA:1960,4 +DA:1961,2 +DA:1962,6 +DA:1963,4 +DA:1966,6 +DA:1967,3 +DA:1968,6 +DA:1969,6 +DA:1970,12 +DA:1971,3 +DA:1973,11 +DA:1974,1 +DA:1975,1 +DA:1976,1 +DA:1977,2 +DA:1978,0 +DA:1979,0 DA:1980,0 -DA:1981,0 -DA:1982,0 -DA:1983,0 -DA:1985,0 -DA:1986,0 -DA:1987,0 -DA:1989,1 -DA:1990,2 -DA:1991,1 -DA:1992,1 -DA:1993,1 -DA:1994,1 +DA:1984,6 +DA:1985,6 +DA:1986,6 +DA:1987,6 +DA:1988,6 +DA:1989,6 +DA:1990,6 +DA:1991,6 +DA:1992,3 +DA:1995,10 +DA:1996,2 +DA:1997,6 DA:1998,4 -DA:1999,4 -DA:2003,8 -DA:2004,2 -DA:2005,4 -DA:2006,2 -DA:2007,6 -DA:2014,4 +DA:2001,8 +DA:2002,2 +DA:2003,4 +DA:2004,4 +DA:2005,2 +DA:2006,1 +DA:2007,1 +DA:2008,1 +DA:2009,2 +DA:2010,1 +DA:2011,1 +DA:2014,2 DA:2015,4 DA:2016,2 -DA:2017,6 -DA:2018,4 -DA:2021,4 +DA:2017,8 +DA:2018,2 +DA:2019,4 +DA:2020,2 +DA:2021,8 DA:2022,2 DA:2023,4 DA:2024,4 DA:2025,4 -DA:2026,2 -DA:2027,2 -DA:2028,1 -DA:2029,1 -DA:2031,4 -DA:2032,4 -DA:2033,4 -DA:2034,4 -DA:2035,4 +DA:2026,4 +DA:2027,4 +DA:2028,2 +DA:2030,6 +DA:2033,10 +DA:2034,2 +DA:2035,6 DA:2036,4 -DA:2037,4 -DA:2038,4 -DA:2039,2 -DA:2049,1 -DA:2060,4 -DA:2062,2 -DA:2064,2 -DA:2066,3 -DA:2067,2 -DA:2068,3 -DA:2072,1 -DA:2073,1 -DA:2076,1 -DA:2077,1 -DA:2079,1 -DA:2084,3 -DA:2085,3 -DA:2087,1 -DA:2093,2 -DA:2094,1 -DA:2095,2 -DA:2096,1 -DA:2098,1 -DA:2102,4 -DA:2104,4 -DA:2107,1 -DA:2109,2 -DA:2111,2 -DA:2114,3 -DA:2116,3 -DA:2117,5 -DA:2119,2 -DA:2120,3 -DA:2123,4 -DA:2127,0 -DA:2131,0 -DA:2137,0 -DA:2139,0 -DA:2141,0 -DA:2149,0 -DA:2155,0 +DA:2039,6 +DA:2040,2 +DA:2041,6 +DA:2042,2 +DA:2043,6 +DA:2045,2 +DA:2046,2 +DA:2047,2 +DA:2048,4 +DA:2049,2 +DA:2050,4 +DA:2051,4 +DA:2052,2 +DA:2053,0 +DA:2054,0 +DA:2056,0 +DA:2057,0 +DA:2058,0 +DA:2059,0 +DA:2061,0 +DA:2062,0 +DA:2063,0 +DA:2064,0 +DA:2067,0 +DA:2068,0 +DA:2069,0 +DA:2070,0 +DA:2071,0 +DA:2072,0 +DA:2073,0 +DA:2074,0 +DA:2076,0 +DA:2077,0 +DA:2079,0 +DA:2080,0 +DA:2081,0 +DA:2082,0 +DA:2083,0 +DA:2085,0 +DA:2086,0 +DA:2099,12 +DA:2100,2 +DA:2101,2 +DA:2103,4 +DA:2104,2 +DA:2105,6 +DA:2106,2 +DA:2107,2 +DA:2108,4 +DA:2110,4 +DA:2117,8 +DA:2118,2 +DA:2120,4 +DA:2121,2 +DA:2122,6 +DA:2123,2 +DA:2124,2 +DA:2125,4 +DA:2135,12 +DA:2136,2 +DA:2137,6 +DA:2138,2 +DA:2142,2 +DA:2144,4 +DA:2145,2 +DA:2146,4 +DA:2147,6 +DA:2148,1 +DA:2149,4 +DA:2150,4 +DA:2151,4 +DA:2152,4 +DA:2153,4 +DA:2154,4 +DA:2155,2 DA:2156,0 +DA:2157,0 +DA:2158,0 +DA:2159,0 +DA:2160,0 DA:2161,0 +DA:2164,0 +DA:2165,0 +DA:2166,0 DA:2167,0 +DA:2169,0 DA:2170,0 +DA:2171,0 DA:2172,0 DA:2173,0 DA:2174,0 +DA:2176,0 +DA:2177,0 DA:2178,0 +DA:2179,0 DA:2180,0 DA:2181,0 -DA:2182,0 -DA:2189,1 -DA:2190,1 -DA:2194,0 -DA:2195,0 -DA:2198,0 -DA:2200,0 -DA:2209,0 -DA:2210,0 -DA:2212,0 -DA:2213,0 -DA:2214,0 -DA:2217,0 -DA:2218,0 -DA:2220,0 -DA:2221,0 -DA:2224,0 -DA:2225,0 -DA:2228,0 -DA:2229,0 -DA:2242,0 -DA:2243,0 -DA:2244,0 -DA:2245,0 -DA:2248,0 -DA:2249,0 -DA:2255,0 -DA:2258,0 -DA:2259,0 -DA:2260,0 -DA:2263,0 +DA:2185,4 +DA:2186,4 +DA:2190,8 +DA:2191,2 +DA:2192,4 +DA:2193,2 +DA:2194,6 +DA:2201,6 +DA:2202,4 +DA:2203,2 +DA:2204,6 +DA:2205,4 +DA:2208,6 +DA:2209,2 +DA:2210,4 +DA:2211,4 +DA:2212,2 +DA:2213,2 +DA:2214,1 +DA:2215,1 +DA:2216,1 +DA:2218,4 +DA:2219,4 +DA:2220,4 +DA:2221,4 +DA:2222,4 +DA:2223,4 +DA:2224,4 +DA:2225,4 +DA:2226,2 +DA:2228,12 +DA:2236,2 +DA:2244,2 +DA:2250,2 +DA:2253,4 +DA:2254,6 +DA:2255,4 +DA:2259,4 +DA:2260,4 +DA:2263,4 +DA:2265,2 +DA:2266,6 +DA:2267,0 DA:2268,0 -DA:2270,0 -DA:2271,0 -DA:2272,0 -DA:2273,0 -DA:2282,1 -DA:2284,3 -DA:2285,1 -DA:2286,3 -DA:2288,0 -DA:2289,0 -DA:2291,0 -DA:2292,0 -DA:2294,0 -DA:2295,0 -DA:2297,0 -DA:2298,0 -DA:2301,1 -DA:2302,1 -LF:670 -LH:397 +DA:2271,2 +DA:2272,2 +DA:2275,2 +DA:2276,2 +DA:2278,2 +DA:2283,3 +DA:2284,2 +DA:2286,1 +DA:2294,4 +DA:2295,1 +DA:2296,2 +DA:2297,2 +DA:2300,0 +DA:2301,0 +DA:2302,0 +DA:2303,0 +DA:2305,2 +DA:2309,4 +DA:2310,6 +DA:2311,6 +DA:2315,8 +DA:2316,4 +DA:2320,2 +DA:2322,4 +DA:2324,2 +DA:2327,3 +DA:2329,3 +DA:2330,5 +DA:2332,2 +DA:2333,3 +DA:2336,4 +DA:2341,1 +DA:2342,1 +DA:2346,0 +DA:2347,0 +DA:2350,0 +DA:2352,0 +DA:2361,0 +DA:2362,0 +DA:2364,0 +DA:2366,0 +DA:2367,0 +DA:2370,0 +DA:2371,0 +DA:2373,0 +DA:2374,0 +DA:2377,0 +DA:2378,0 +DA:2381,0 +DA:2382,0 +DA:2395,0 +DA:2396,0 +DA:2397,0 +DA:2398,0 +DA:2401,0 +DA:2402,0 +DA:2408,0 +DA:2411,0 +DA:2412,0 +DA:2413,0 +DA:2416,0 +DA:2421,0 +DA:2423,0 +DA:2424,0 +DA:2425,0 +DA:2426,0 +DA:2435,1 +DA:2437,3 +DA:2438,1 +DA:2439,3 +DA:2440,0 +DA:2441,0 +DA:2442,0 +DA:2443,0 +DA:2444,0 +DA:2445,0 +DA:2446,0 +DA:2447,0 +DA:2449,1 +DA:2450,1 +LF:790 +LH:599 end_of_record SF:lib/src/show_color_picker_dialog.dart -DA:7,0 -DA:637,0 -DA:639,0 -DA:693,0 +DA:7,2 +DA:674,2 +DA:676,2 +DA:734,2 LF:4 -LH:0 +LH:4 end_of_record SF:lib/src/color_picker_extensions.dart DA:12,3 @@ -999,23 +1129,23 @@ DA:196,18 DA:210,2 DA:214,4 DA:215,12 -DA:231,3 -DA:232,6 -DA:233,6 -DA:234,18 +DA:231,4 +DA:232,8 +DA:233,8 +DA:234,24 DA:241,3 DA:281,3 DA:282,3 DA:283,3 DA:284,3 DA:285,3 -DA:286,6 +DA:286,3 DA:287,6 DA:288,6 -DA:289,15 -DA:290,15 +DA:289,6 DA:291,15 -DA:297,9 +DA:292,15 +DA:293,15 DA:298,9 DA:299,9 DA:300,9 @@ -1023,123 +1153,125 @@ DA:301,9 DA:302,9 DA:303,9 DA:304,9 -DA:305,6 -DA:386,6 -DA:387,3 -DA:409,3 -DA:410,6 +DA:305,9 +DA:306,6 +DA:387,6 +DA:388,3 +DA:410,3 DA:411,6 -DA:412,18 -DA:430,1 -DA:431,2 +DA:412,6 +DA:413,18 +DA:431,1 DA:432,2 -DA:433,6 -DA:440,1 -DA:452,3 +DA:433,2 +DA:434,6 +DA:441,1 DA:453,3 DA:454,3 DA:455,3 DA:456,3 -DA:457,6 -DA:458,6 +DA:457,3 +DA:458,3 DA:459,6 -DA:460,15 -DA:461,15 -DA:462,15 -DA:467,9 -DA:468,9 -DA:469,6 -DA:577,6 -DA:578,3 -DA:586,3 -DA:587,6 -DA:588,6 -DA:589,18 -DA:606,1 -DA:607,2 -DA:608,2 -DA:609,6 -DA:616,1 -DA:622,1 -DA:625,2 -DA:626,2 +DA:460,6 +DA:461,6 +DA:463,15 +DA:464,15 +DA:465,15 +DA:469,9 +DA:470,9 +DA:471,6 +DA:579,6 +DA:580,3 +DA:588,3 +DA:589,6 +DA:590,6 +DA:591,18 +DA:608,1 +DA:609,2 +DA:610,2 +DA:611,6 +DA:618,1 +DA:624,3 DA:627,6 -DA:645,3 -DA:648,6 -DA:649,6 -DA:650,18 -DA:658,3 -DA:666,1 -DA:669,1 -DA:670,1 -DA:672,2 +DA:628,6 +DA:629,18 +DA:647,3 +DA:650,6 +DA:651,6 +DA:652,18 +DA:660,3 +DA:668,1 +DA:671,1 +DA:672,1 DA:674,2 -DA:683,1 -DA:686,1 -DA:687,1 -DA:689,2 +DA:676,2 +DA:685,1 +DA:688,1 +DA:689,1 DA:691,2 -DA:703,3 -DA:707,9 -DA:708,6 -DA:709,18 -DA:711,4 -DA:713,1 -DA:719,9 -DA:720,6 -DA:721,18 -DA:723,9 -DA:725,1 -DA:732,9 -DA:733,6 -DA:734,18 -DA:736,9 -DA:738,1 -DA:747,6 -DA:748,4 -DA:749,18 -DA:751,5 -DA:753,1 -DA:765,1 -DA:766,3 -DA:781,3 -DA:782,6 -DA:799,8 -DA:802,3 -DA:803,6 +DA:693,2 +DA:705,3 +DA:709,9 +DA:710,6 +DA:711,18 +DA:713,4 +DA:715,1 +DA:721,9 +DA:722,6 +DA:723,18 +DA:725,9 +DA:727,1 +DA:734,9 +DA:735,6 +DA:736,18 +DA:738,9 +DA:740,1 +DA:749,6 +DA:750,4 +DA:751,18 +DA:753,5 +DA:755,1 +DA:767,1 +DA:768,3 +DA:783,3 +DA:784,6 +DA:801,13 DA:804,3 -DA:805,3 +DA:805,6 DA:806,3 +DA:807,3 DA:808,3 -DA:809,6 -DA:810,9 -DA:811,9 -DA:816,3 -DA:817,3 -DA:818,9 -DA:819,10 -DA:821,18 -DA:822,18 -DA:823,15 +DA:810,3 +DA:811,6 +DA:812,9 +DA:813,9 +DA:818,3 +DA:819,3 +DA:820,9 +DA:821,10 +DA:823,18 DA:824,18 -DA:825,18 -DA:826,15 -DA:827,6 -DA:828,6 -DA:833,3 -DA:834,0 +DA:825,15 +DA:826,18 +DA:827,18 +DA:828,15 +DA:829,6 +DA:830,6 DA:835,3 -DA:841,9 -DA:842,9 -DA:843,12 +DA:836,0 +DA:837,3 +DA:843,9 DA:844,9 DA:845,12 -DA:846,15 -DA:847,6 -DA:848,9 -DA:849,15 -LF:148 -LH:147 +DA:846,9 +DA:847,12 +DA:848,15 +DA:849,6 +DA:850,9 +DA:851,15 +LF:150 +LH:149 end_of_record SF:lib/src/color_wheel_picker.dart DA:22,3 @@ -1172,7 +1304,7 @@ DA:163,4 DA:164,3 DA:169,12 DA:172,1 -DA:173,4 +DA:173,5 DA:174,1 DA:175,3 DA:177,0 @@ -1462,7 +1594,7 @@ LF:316 LH:242 end_of_record SF:lib/src/models/color_picker_copy_paste_behavior.dart -DA:58,36 +DA:58,56 DA:348,1 DA:375,1 DA:376,1 @@ -1691,46 +1823,34 @@ DA:308,0 DA:310,0 DA:311,0 DA:312,0 +DA:313,0 DA:314,0 DA:315,0 +DA:316,0 DA:317,0 DA:318,0 +DA:319,0 DA:320,0 -DA:321,0 +DA:322,0 DA:323,0 -DA:324,0 -DA:327,0 -DA:328,0 -DA:333,2 -DA:334,6 -DA:335,6 -DA:336,2 -DA:337,2 -DA:338,0 +DA:328,2 +DA:329,6 +DA:330,6 +DA:331,2 +DA:332,2 +DA:333,0 +DA:335,0 +DA:336,0 +DA:337,0 +DA:339,0 DA:340,0 -DA:341,0 -DA:342,0 -DA:344,0 -DA:345,0 +DA:347,0 +DA:350,0 +DA:351,0 DA:352,0 -DA:355,0 -DA:356,0 -DA:357,0 LF:129 LH:89 end_of_record -SF:lib/src/universal_widgets/if_wrapper.dart -DA:32,3 -DA:38,3 -DA:54,3 -DA:56,3 -DA:57,6 -DA:58,3 -DA:59,3 -DA:61,3 -LF:8 -LH:8 -end_of_record SF:lib/src/widgets/color_picker_toolbar.dart DA:14,2 DA:24,2 @@ -1746,25 +1866,25 @@ DA:79,8 DA:82,8 DA:84,4 DA:85,4 -DA:86,0 +DA:86,2 DA:87,0 -DA:88,0 +DA:88,1 DA:91,2 DA:92,4 -DA:93,0 +DA:93,1 DA:96,4 -DA:97,0 -DA:99,0 +DA:97,1 +DA:99,1 DA:101,2 DA:102,2 DA:103,2 DA:104,2 DA:105,6 DA:106,2 -DA:107,0 -DA:108,0 -DA:109,0 -DA:110,0 +DA:107,1 +DA:108,1 +DA:109,1 +DA:110,1 DA:111,2 DA:112,2 DA:113,2 @@ -1785,14 +1905,14 @@ DA:131,4 DA:132,4 DA:134,4 DA:136,6 -DA:137,1 -DA:138,3 -DA:139,1 -DA:141,2 -DA:142,2 -DA:143,2 -DA:144,2 -DA:146,2 +DA:137,2 +DA:138,6 +DA:139,2 +DA:141,4 +DA:142,4 +DA:143,4 +DA:144,4 +DA:146,4 DA:148,2 DA:149,2 DA:150,6 @@ -1812,288 +1932,282 @@ DA:167,4 DA:168,4 DA:170,4 LF:79 -LH:69 +LH:78 end_of_record -SF:lib/src/widgets/context_copy_paste_menu.dart -DA:24,1 -DA:41,1 -DA:146,1 -DA:148,1 -DA:152,2 -DA:153,1 -DA:154,2 -DA:155,2 -DA:156,1 -DA:157,2 -DA:158,1 -DA:159,1 -DA:160,2 -DA:162,3 -DA:163,1 -DA:164,2 -DA:165,2 -DA:167,1 -DA:168,2 -DA:174,2 -DA:175,3 -DA:176,3 -DA:177,3 -DA:181,1 -DA:183,1 -DA:184,1 -DA:186,1 -DA:187,1 -DA:188,1 -DA:189,1 -DA:191,1 -DA:192,1 -DA:193,1 -DA:195,1 -DA:196,1 -DA:197,1 -DA:198,1 -DA:200,1 -DA:201,3 -DA:202,2 -DA:207,1 -DA:209,1 -DA:210,1 -DA:211,1 -DA:212,1 -DA:214,1 -DA:215,3 -DA:216,2 -DA:222,1 -DA:223,1 -DA:224,1 -LF:51 -LH:51 +SF:lib/src/widgets/copy_paste_handler.dart +DA:17,4 +DA:74,4 +DA:76,8 +DA:78,8 +DA:79,4 +DA:80,4 +DA:82,0 +DA:85,8 +DA:88,0 +DA:91,8 +DA:94,4 +DA:95,4 +DA:96,8 +DA:97,8 +DA:99,8 +DA:100,4 +DA:101,4 +DA:102,4 +DA:104,4 +DA:106,8 +DA:107,4 +DA:108,4 +DA:109,2 +DA:110,2 +DA:111,2 +DA:112,2 +DA:114,2 +DA:116,2 +DA:117,0 +DA:118,0 +DA:119,0 +DA:121,0 +DA:122,0 +DA:125,2 +DA:129,4 +DA:140,13 +DA:146,4 +DA:154,0 +DA:156,0 +DA:164,13 +DA:170,4 +DA:178,0 +DA:180,0 +LF:43 +LH:32 end_of_record SF:lib/src/widgets/main_colors.dart -DA:10,2 -DA:81,2 -DA:83,6 -DA:84,2 -DA:85,4 -DA:86,2 -DA:87,2 -DA:88,2 -DA:89,2 -DA:90,2 -DA:91,2 -DA:92,6 -DA:93,2 -DA:94,6 +DA:10,3 +DA:81,3 +DA:83,9 +DA:84,3 +DA:85,6 +DA:86,3 +DA:87,3 +DA:88,3 +DA:89,3 +DA:90,3 +DA:91,3 +DA:92,9 +DA:93,3 +DA:94,9 DA:95,2 -DA:97,2 -DA:98,2 -DA:100,2 -DA:101,2 -DA:102,2 -DA:103,2 -DA:104,1 -DA:105,2 -DA:106,2 -DA:108,2 +DA:97,3 +DA:98,3 +DA:100,3 +DA:101,3 +DA:102,3 +DA:103,3 +DA:104,2 +DA:105,4 +DA:106,3 +DA:108,3 LF:25 LH:25 end_of_record SF:lib/src/widgets/opacity/opacity_slider.dart -DA:31,2 -DA:41,6 -DA:43,6 -DA:45,2 -DA:98,2 -DA:100,2 -DA:101,2 -DA:103,2 -DA:104,2 -DA:105,2 -DA:106,0 -DA:107,0 -DA:108,0 -DA:109,0 -DA:113,0 -DA:114,0 -DA:115,0 -DA:116,0 -DA:131,2 -DA:132,1 -DA:133,2 -DA:135,2 -DA:136,6 -DA:137,2 -DA:138,2 -DA:140,2 -DA:145,0 -DA:151,0 -DA:154,0 -DA:155,0 -DA:159,0 -DA:165,0 +DA:31,3 +DA:41,9 +DA:43,9 +DA:45,3 +DA:98,3 +DA:100,3 +DA:101,3 +DA:103,3 +DA:104,3 +DA:105,3 +DA:106,1 +DA:107,4 +DA:108,1 +DA:109,1 +DA:113,1 +DA:114,1 +DA:115,1 +DA:116,1 +DA:131,3 +DA:132,2 +DA:133,3 +DA:135,3 +DA:136,9 +DA:137,3 +DA:138,3 +DA:140,3 +DA:145,1 +DA:151,2 +DA:154,2 +DA:155,1 +DA:159,1 +DA:165,1 LF:32 -LH:18 +LH:32 end_of_record SF:lib/src/widgets/picker_selector.dart -DA:14,2 -DA:23,2 -DA:50,2 -DA:53,2 -DA:54,6 -DA:57,2 -DA:63,2 +DA:14,3 +DA:23,3 +DA:50,3 +DA:53,3 +DA:54,9 +DA:57,3 +DA:63,3 DA:65,4 -DA:70,2 -DA:72,2 -DA:73,4 -DA:74,2 -DA:75,2 -DA:76,4 +DA:70,3 +DA:72,3 +DA:73,6 +DA:74,3 +DA:75,3 +DA:76,6 DA:77,4 DA:79,2 DA:80,4 DA:82,4 DA:83,2 -DA:87,4 -DA:88,4 -DA:90,2 -DA:91,4 -DA:93,4 -DA:94,2 -DA:98,4 -DA:99,4 -DA:101,2 -DA:102,4 -DA:104,4 +DA:87,6 +DA:88,6 +DA:90,3 +DA:91,6 +DA:93,6 +DA:94,3 +DA:98,6 +DA:99,6 +DA:101,3 +DA:102,6 +DA:104,6 DA:105,2 -DA:109,4 +DA:109,6 DA:110,4 DA:112,2 DA:113,4 DA:115,4 DA:116,1 -DA:120,4 +DA:120,6 DA:121,4 DA:123,2 DA:124,4 DA:126,4 DA:127,2 -DA:131,4 -DA:132,4 -DA:134,2 -DA:135,4 -DA:137,4 -DA:138,2 -DA:143,2 -DA:148,1 +DA:131,6 +DA:132,2 +DA:134,1 +DA:135,2 +DA:137,2 +DA:138,1 +DA:142,6 +DA:143,4 +DA:145,2 +DA:146,4 +DA:148,4 DA:149,2 -DA:151,2 -LF:53 -LH:53 +DA:154,3 +DA:159,1 +DA:160,2 +DA:162,3 +LF:59 +LH:59 end_of_record SF:lib/src/widgets/recent_colors.dart DA:12,2 -DA:29,2 +DA:28,2 +DA:79,2 +DA:81,2 +DA:82,2 +DA:83,2 +DA:84,2 +DA:85,2 DA:86,2 -DA:88,2 -DA:89,2 -DA:90,4 +DA:87,2 +DA:89,12 DA:91,2 DA:92,2 -DA:93,2 DA:94,2 DA:95,2 DA:96,2 -DA:98,12 -DA:100,2 +DA:97,2 +DA:98,1 +DA:99,2 DA:101,2 -DA:103,2 -DA:104,2 -DA:105,2 -DA:106,2 -DA:107,1 -DA:108,2 -DA:110,2 -LF:22 -LH:22 +LF:20 +LH:20 end_of_record SF:lib/src/widgets/shade_colors.dart -DA:13,2 -DA:30,2 -DA:87,2 -DA:89,6 -DA:90,2 -DA:91,4 -DA:92,2 -DA:93,2 -DA:94,2 -DA:95,2 -DA:97,6 -DA:98,2 -DA:100,12 -DA:102,2 -DA:103,2 -DA:105,2 -DA:106,2 -DA:107,2 -DA:108,2 +DA:13,3 +DA:30,3 +DA:87,3 +DA:89,9 +DA:90,3 +DA:91,6 +DA:92,3 +DA:93,3 +DA:94,3 +DA:95,3 +DA:97,9 +DA:98,3 +DA:100,18 +DA:102,3 +DA:103,3 +DA:105,3 +DA:106,3 +DA:107,3 +DA:108,3 DA:109,1 DA:110,2 -DA:112,2 +DA:112,3 LF:22 LH:22 end_of_record SF:lib/src/widgets/tonal_palette_colors.dart DA:13,2 -DA:29,2 -DA:80,2 -DA:81,2 -DA:87,2 -DA:89,2 -DA:90,8 -DA:93,1 -DA:95,2 -DA:96,4 -DA:98,1 +DA:28,2 +DA:76,2 +DA:77,2 +DA:83,2 +DA:85,2 +DA:86,8 +DA:89,1 +DA:91,2 +DA:92,4 +DA:94,1 +DA:97,2 +DA:100,7 DA:101,2 -DA:104,4 +DA:102,4 +DA:103,4 +DA:104,2 DA:105,2 -DA:106,6 -DA:107,2 -DA:108,4 -DA:109,4 -DA:110,2 -DA:111,2 -DA:112,2 -DA:113,6 -DA:114,10 +DA:106,2 +DA:107,6 +DA:108,10 +DA:110,4 +DA:111,4 +DA:113,4 +DA:114,4 +DA:115,4 DA:116,4 -DA:117,4 -DA:119,4 +DA:117,1 +DA:118,3 DA:120,4 -DA:121,4 -DA:122,4 -DA:123,1 -DA:124,3 -DA:126,4 -LF:32 -LH:32 +LF:30 +LH:30 end_of_record SF:lib/src/universal_widgets/context_popup_menu.dart -DA:35,1 -DA:94,1 -DA:95,1 -DA:101,1 -DA:103,2 -DA:104,2 +DA:35,2 +DA:94,2 +DA:95,2 +DA:101,2 +DA:103,4 +DA:104,4 DA:105,0 DA:106,0 DA:107,0 -DA:108,2 +DA:108,4 DA:109,0 DA:110,0 DA:111,0 -DA:113,1 +DA:113,2 DA:116,0 DA:117,0 DA:118,0 @@ -2102,7 +2216,7 @@ DA:123,0 DA:124,0 DA:125,0 DA:126,0 -DA:129,2 +DA:129,4 DA:133,0 DA:134,0 DA:136,0 @@ -2118,91 +2232,158 @@ DA:148,0 LF:35 LH:9 end_of_record +SF:lib/src/universal_widgets/if_wrapper.dart +DA:32,5 +DA:38,5 +DA:54,5 +DA:56,5 +DA:57,9 +DA:58,4 +DA:59,3 +DA:61,4 +LF:8 +LH:8 +end_of_record +SF:lib/src/widgets/context_copy_paste_menu.dart +DA:24,2 +DA:41,2 +DA:146,2 +DA:148,2 +DA:151,4 +DA:152,2 +DA:153,4 +DA:154,4 +DA:155,2 +DA:156,4 +DA:157,2 +DA:158,2 +DA:159,4 +DA:161,6 +DA:162,2 +DA:163,4 +DA:164,4 +DA:166,2 +DA:167,4 +DA:172,4 +DA:173,6 +DA:174,6 +DA:175,6 +DA:178,2 +DA:179,2 +DA:180,2 +DA:182,2 +DA:183,2 +DA:184,2 +DA:185,2 +DA:187,2 +DA:188,2 +DA:189,2 +DA:191,2 +DA:192,2 +DA:193,2 +DA:194,2 +DA:196,2 +DA:197,6 +DA:198,4 +DA:203,2 +DA:205,2 +DA:206,2 +DA:207,2 +DA:208,2 +DA:210,2 +DA:211,6 +DA:212,4 +DA:218,2 +DA:219,2 +DA:220,2 +LF:51 +LH:51 +end_of_record SF:lib/src/widgets/opacity/opacity_slider_thumb.dart -DA:22,0 -DA:33,0 -DA:35,0 -DA:50,0 -DA:52,0 -DA:54,0 -DA:56,0 -DA:57,0 -DA:58,0 -DA:61,0 -DA:62,0 -DA:63,0 -DA:64,0 -DA:66,0 -DA:67,0 -DA:70,0 -DA:73,0 -DA:74,0 -DA:75,0 -DA:77,0 -DA:78,0 -DA:79,0 -DA:81,0 -DA:83,0 -DA:86,0 -DA:91,0 -DA:93,0 -DA:94,0 -DA:95,0 -DA:97,0 +DA:22,2 +DA:33,6 +DA:35,2 +DA:50,4 +DA:52,4 +DA:54,2 +DA:56,2 +DA:57,2 +DA:58,2 +DA:61,2 +DA:62,2 +DA:63,2 +DA:64,2 +DA:66,2 +DA:67,2 +DA:70,2 +DA:73,2 +DA:74,6 +DA:75,10 +DA:77,2 +DA:78,2 +DA:79,4 +DA:81,2 +DA:83,4 +DA:86,2 +DA:91,2 +DA:93,2 +DA:94,8 +DA:95,8 +DA:97,2 LF:30 -LH:0 +LH:30 end_of_record SF:lib/src/widgets/opacity/opacity_slider_track.dart -DA:13,0 -DA:17,0 -DA:18,0 -DA:22,0 -DA:48,0 -DA:57,0 -DA:59,0 -DA:60,0 -DA:61,0 -DA:62,0 -DA:65,0 -DA:67,0 -DA:69,0 -DA:70,0 -DA:73,0 -DA:74,0 -DA:77,0 -DA:91,0 -DA:93,0 -DA:95,0 -DA:97,0 -DA:99,0 -DA:102,0 -DA:106,0 -DA:113,0 -DA:114,0 -DA:116,0 -DA:117,0 -DA:118,0 -DA:120,0 -DA:121,0 -DA:122,0 -DA:127,0 +DA:14,2 +DA:18,2 +DA:19,4 +DA:23,4 +DA:49,2 +DA:58,6 +DA:60,6 +DA:61,2 +DA:62,4 +DA:63,4 +DA:66,10 +DA:68,12 +DA:70,10 +DA:71,2 +DA:74,4 +DA:75,2 +DA:78,2 +DA:92,4 +DA:94,4 +DA:96,4 +DA:98,4 +DA:100,4 +DA:103,4 +DA:107,2 +DA:114,6 +DA:115,8 +DA:117,4 +DA:118,2 +DA:119,4 +DA:121,4 +DA:122,10 +DA:123,2 +DA:128,2 DA:131,0 -DA:137,0 -DA:138,0 -DA:139,0 +DA:136,2 +DA:137,6 +DA:138,2 +DA:139,6 DA:140,0 -DA:141,0 -DA:142,0 -DA:143,0 +DA:141,6 +DA:142,2 +DA:143,6 DA:144,0 -DA:145,0 -DA:146,0 -DA:149,0 -DA:152,0 -DA:155,0 -DA:160,0 -DA:161,0 -DA:162,0 +DA:145,2 +DA:148,2 +DA:151,2 +DA:154,2 +DA:159,4 +DA:160,6 +DA:161,4 LF:50 -LH:0 +LH:47 end_of_record diff --git a/example/android/build.gradle b/example/android/build.gradle index 4256f91..0a2d6a1 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -26,6 +26,6 @@ subprojects { project.evaluationDependsOn(':app') } -task clean(type: Delete) { +tasks.register("clean", Delete) { delete rootProject.buildDir } diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties index bc6a58a..cfe88f6 100644 --- a/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/example/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-all.zip diff --git a/example/lib/demo/pods/pods.dart b/example/lib/demo/pods/pods.dart index 9bd3a19..115cb9f 100644 --- a/example/lib/demo/pods/pods.dart +++ b/example/lib/demo/pods/pods.dart @@ -443,6 +443,14 @@ final StateProvider dialogActionButtonsPod = defaultValue: Keys.defaults[Keys.dialogActionButtons]! as bool) as bool; }, name: Keys.dialogActionButtons); +// State of having only OK bottom action in the dialog. +final StateProvider dialogActionOnlyOkButtonPod = + StateProvider((StateProviderRef ref) { + return hiveStore.get(Keys.dialogActionOnlyOkButton, + defaultValue: Keys.defaults[Keys.dialogActionOnlyOkButton]! as bool) + as bool; +}, name: Keys.dialogActionOnlyOkButton); + // State of order OK cancel buttons in dialog actions final StateProvider dialogActionsOrderPod = StateProvider( diff --git a/example/lib/demo/pods/pods_reset.dart b/example/lib/demo/pods/pods_reset.dart index 1281641..a12d104 100644 --- a/example/lib/demo/pods/pods_reset.dart +++ b/example/lib/demo/pods/pods_reset.dart @@ -150,6 +150,9 @@ void resetSettings(WidgetRef ref) { ref.read(dialogActionButtonsPod.notifier).state = Keys.defaults[Keys.dialogActionButtons]! as bool; + ref.read(dialogActionOnlyOkButtonPod.notifier).state = + Keys.defaults[Keys.dialogActionOnlyOkButton]! as bool; + ref.read(dialogActionsOrderPod.notifier).state = Keys.defaults[Keys.dialogActionOrder]! as ColorPickerActionButtonOrder; diff --git a/example/lib/demo/screens/color_picker/color_picker_card.dart b/example/lib/demo/screens/color_picker/color_picker_card.dart index 8a6dec7..a1dc3a2 100644 --- a/example/lib/demo/screens/color_picker/color_picker_card.dart +++ b/example/lib/demo/screens/color_picker/color_picker_card.dart @@ -12,7 +12,7 @@ class ColorPickerCard extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final ColorScheme colorScheme = Theme.of(context).colorScheme; + final ThemeData theme = Theme.of(context); return Card( elevation: 1, child: ColorPicker( @@ -67,6 +67,7 @@ class ColorPickerCard extends ConsumerWidget { hasBorder: ref.watch(hasBorderPod), borderRadius: ref.watch(borderRadiusPod), columnSpacing: ref.watch(columnSpacingPod), + toolbarSpacing: 0, wheelDiameter: ref.watch(wheelDiameterPod), wheelWidth: ref.watch(wheelWidthPod), wheelSquarePadding: ref.watch(wheelSquarePaddingPod), @@ -74,47 +75,51 @@ class ColorPickerCard extends ConsumerWidget { wheelHasBorder: ref.watch(wheelHasBorderPod), enableTooltips: ref.watch(enableTooltipsPod), pickersEnabled: ref.watch(pickersEnabledPod), - selectedPickerTypeColor: colorScheme.primary, + pickerTypeLabels: const { + ColorPickerType.both: 'P & A', + ColorPickerType.bw: 'B & W', + }, + selectedPickerTypeColor: theme.colorScheme.primary, title: ref.watch(showTitlePod) ? Text( 'ColorPicker', - style: Theme.of(context).textTheme.titleLarge, + style: theme.textTheme.titleLarge, ) : null, heading: ref.watch(showHeadingPod) ? Text( 'Select color', - style: Theme.of(context).textTheme.headlineSmall, + style: theme.textTheme.headlineSmall, ) : null, subheading: ref.watch(showSubheadingPod) ? Text( 'Select color shade', - style: Theme.of(context).textTheme.titleMedium, + style: theme.textTheme.titleMedium, ) : null, tonalSubheading: ref.watch(showTonalSubheadingPod) ? Text( 'Material 3 tonal palette', - style: Theme.of(context).textTheme.titleMedium, + style: theme.textTheme.titleMedium, ) : null, wheelSubheading: ref.watch(showSubheadingPod) ? Text( 'Selected color and its color swatch', - style: Theme.of(context).textTheme.titleMedium, + style: theme.textTheme.titleMedium, ) : null, opacitySubheading: ref.watch(showOpacitySubheadingPod) ? Text( 'Opacity', - style: Theme.of(context).textTheme.titleMedium, + style: theme.textTheme.titleMedium, ) : null, recentColorsSubheading: ref.watch(showRecentSubheadingPod) ? Text( 'Recent colors', - style: Theme.of(context).textTheme.titleMedium, + style: theme.textTheme.titleMedium, ) : null, showMaterialName: ref.watch(showMaterialNamePod), @@ -127,8 +132,9 @@ class ColorPickerCard extends ConsumerWidget { recentColors: ref.watch(cardRecentColorsPod), maxRecentColors: 8, customColorSwatchesAndNames: App.colorsNameMap, - colorCodeTextStyle: Theme.of(context).textTheme.titleMedium, - colorCodePrefixStyle: Theme.of(context).textTheme.bodySmall, + customSecondaryColorSwatchesAndNames: App.colorsOptionsMap, + colorCodeTextStyle: theme.textTheme.titleMedium, + colorCodePrefixStyle: theme.textTheme.bodySmall, ), ); } diff --git a/example/lib/demo/screens/color_picker/color_picker_dialog.dart b/example/lib/demo/screens/color_picker/color_picker_dialog.dart index 7fb27b9..cc5984f 100644 --- a/example/lib/demo/screens/color_picker/color_picker_dialog.dart +++ b/example/lib/demo/screens/color_picker/color_picker_dialog.dart @@ -11,7 +11,7 @@ Future colorPickerDialog( WidgetRef ref, { bool cardRemote = false, }) async { - final ColorScheme colorScheme = Theme.of(context).colorScheme; + final ThemeData theme = Theme.of(context); return ColorPicker( color: cardRemote ? ref.watch(cardPickerColorPod) @@ -67,10 +67,11 @@ Future colorPickerDialog( closeButton: ref.watch(closeButtonPod), closeIsLast: ref.watch(closeIsLastPod), dialogActionButtons: ref.watch(dialogActionButtonsPod), + dialogActionOnlyOkButton: ref.watch(dialogActionOnlyOkButtonPod), dialogActionOrder: ref.watch(dialogActionsOrderPod), dialogActionIcons: ref.watch(dialogActionIconsPod), - dialogOkButtonType: ColorPickerActionButtonType.outlined, - dialogCancelButtonType: ColorPickerActionButtonType.text, + dialogOkButtonType: ColorPickerActionButtonType.filled, + dialogCancelButtonType: ColorPickerActionButtonType.filledTonal, ), width: ref.watch(sizePod), height: ref.watch(sizePod), @@ -81,6 +82,7 @@ Future colorPickerDialog( hasBorder: ref.watch(hasBorderPod), borderRadius: ref.watch(borderRadiusPod), columnSpacing: ref.watch(columnSpacingPod), + toolbarSpacing: 0, wheelDiameter: ref.watch(wheelDiameterPod), wheelWidth: ref.watch(wheelWidthPod), wheelHasBorder: ref.watch(wheelHasBorderPod), @@ -88,47 +90,51 @@ Future colorPickerDialog( wheelSquareBorderRadius: ref.watch(wheelSquareBorderRadiusPod), enableTooltips: ref.watch(enableTooltipsPod), pickersEnabled: ref.watch(pickersEnabledPod), - selectedPickerTypeColor: colorScheme.primary, + pickerTypeLabels: const { + ColorPickerType.both: 'P & A', + ColorPickerType.bw: 'B & W', + }, + selectedPickerTypeColor: theme.colorScheme.primary, title: ref.watch(showTitlePod) ? Text( 'ColorPicker', - style: Theme.of(context).textTheme.titleLarge, + style: theme.textTheme.titleLarge, ) : null, heading: ref.watch(showHeadingPod) ? Text( 'Select color', - style: Theme.of(context).textTheme.headlineSmall, + style: theme.textTheme.headlineSmall, ) : null, subheading: ref.watch(showSubheadingPod) ? Text( 'Select color shade', - style: Theme.of(context).textTheme.titleMedium, + style: theme.textTheme.titleMedium, ) : null, tonalSubheading: ref.watch(showTonalSubheadingPod) ? Text( 'Material 3 tonal palette', - style: Theme.of(context).textTheme.titleMedium, + style: theme.textTheme.titleMedium, ) : null, wheelSubheading: ref.watch(showSubheadingPod) ? Text( 'Selected color and its color swatch', - style: Theme.of(context).textTheme.titleMedium, + style: theme.textTheme.titleMedium, ) : null, opacitySubheading: ref.watch(showOpacitySubheadingPod) ? Text( 'Opacity', - style: Theme.of(context).textTheme.titleMedium, + style: theme.textTheme.titleMedium, ) : null, recentColorsSubheading: ref.watch(showRecentSubheadingPod) ? Text( 'Recent colors', - style: Theme.of(context).textTheme.titleMedium, + style: theme.textTheme.titleMedium, ) : null, showMaterialName: ref.watch(showMaterialNamePod), @@ -143,6 +149,7 @@ Future colorPickerDialog( : ref.watch(dialogRecentColorsPod), maxRecentColors: cardRemote ? 8 : 5, customColorSwatchesAndNames: App.colorsNameMap, + customSecondaryColorSwatchesAndNames: App.colorsOptionsMap, ).showPickerDialog( context, elevation: 2, diff --git a/example/lib/demo/screens/color_picker/color_picker_screen.dart b/example/lib/demo/screens/color_picker/color_picker_screen.dart index ca25694..e4f2b5d 100644 --- a/example/lib/demo/screens/color_picker/color_picker_screen.dart +++ b/example/lib/demo/screens/color_picker/color_picker_screen.dart @@ -5,6 +5,7 @@ import '../../widgets/flex_app_bar.dart'; import 'about.dart'; import 'all_control_widgets.dart'; import 'color_picker_card.dart'; +import 'picker_switches/dialog_action_only_ok_button_switch.dart'; import 'picker_text_fields/text_field_focus_demo.dart'; import 'picker_toggle_buttons/actions_order_switch.dart'; @@ -20,7 +21,7 @@ class ColorPickerScreen extends StatelessWidget { @override Widget build(BuildContext context) { // Used for a simple scrollable column based responsive layout - final double width = MediaQuery.of(context).size.width; + final double width = MediaQuery.sizeOf(context).width; // That can have 1 to max 4 columns int columns = width ~/ App.minColumnWidth; if (columns < 1) columns = 1; @@ -320,6 +321,7 @@ class _Column4 extends StatelessWidget { const OkButtonSwitch(), const CloseIsLastSwitch(), const DialogActionsButtonsSwitch(), + const DialogActionOnlyOkButtonSwitch(), const ActionsOrderSwitch(), const DialogActionIconsSwitch(), const Divider(), diff --git a/example/lib/demo/screens/color_picker/picker_switches/dialog_action_only_ok_button_switch.dart b/example/lib/demo/screens/color_picker/picker_switches/dialog_action_only_ok_button_switch.dart new file mode 100644 index 0000000..0900eba --- /dev/null +++ b/example/lib/demo/screens/color_picker/picker_switches/dialog_action_only_ok_button_switch.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../../pods/pods.dart'; +import '../../../widgets/switch_tile_tooltip.dart'; + +@immutable +class DialogActionOnlyOkButtonSwitch extends ConsumerWidget { + const DialogActionOnlyOkButtonSwitch({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return SwitchTileTooltip( + title: const Text('Dialog has only OK button'), + subtitle: const Text('Turn ON to remove the dialog bottom Cancel button'), + value: ref.watch(dialogActionOnlyOkButtonPod), + onChanged: (bool value) => + ref.read(dialogActionOnlyOkButtonPod.notifier).state = value, + tooltipEnabled: ref.watch(enableTooltipsPod), + tooltip: 'ColorPicker(actionButtons:\n' + ' ColorPickerActionButtons(dialogActionOnlyOkButton: ' + '${ref.read(dialogActionOnlyOkButtonPod)}))', + ); + } +} diff --git a/example/lib/demo/screens/color_picker/picker_toggle_buttons/pickers_enabled_switch.dart b/example/lib/demo/screens/color_picker/picker_toggle_buttons/pickers_enabled_switch.dart index 7da7e64..a903532 100644 --- a/example/lib/demo/screens/color_picker/picker_toggle_buttons/pickers_enabled_switch.dart +++ b/example/lib/demo/screens/color_picker/picker_toggle_buttons/pickers_enabled_switch.dart @@ -19,6 +19,7 @@ class PickersEnabledSwitch extends ConsumerWidget { pickersEnabled[ColorPickerType.accent] ?? false, pickersEnabled[ColorPickerType.bw] ?? false, pickersEnabled[ColorPickerType.custom] ?? false, + pickersEnabled[ColorPickerType.customSecondary] ?? false, pickersEnabled[ColorPickerType.wheel] ?? false, ]; return Column( @@ -52,19 +53,17 @@ class PickersEnabledSwitch extends ConsumerWidget { ColorPickerType.accent: isSelected[2], ColorPickerType.bw: isSelected[3], ColorPickerType.custom: isSelected[4], - ColorPickerType.wheel: isSelected[5], + ColorPickerType.customSecondary: isSelected[5], + ColorPickerType.wheel: isSelected[6], }; }, children: const [ - Text('Primary\nAccent', - textAlign: TextAlign.center, - style: TextStyle(fontSize: _kToggleFontSize)), + Text('P & A', style: TextStyle(fontSize: _kToggleFontSize)), Text('Primary', style: TextStyle(fontSize: _kToggleFontSize)), Text('Accent', style: TextStyle(fontSize: _kToggleFontSize)), - Text('Black\nWhite', - textAlign: TextAlign.center, - style: TextStyle(fontSize: _kToggleFontSize)), - Text('Custom ', style: TextStyle(fontSize: _kToggleFontSize)), + Text('B & W', style: TextStyle(fontSize: _kToggleFontSize)), + Text('Custom', style: TextStyle(fontSize: _kToggleFontSize)), + Text('Option', style: TextStyle(fontSize: _kToggleFontSize)), Text('Wheel', style: TextStyle(fontSize: _kToggleFontSize)), ], ), diff --git a/example/lib/demo/screens/home/home_screen.dart b/example/lib/demo/screens/home/home_screen.dart index 6c8728f..64ffdb4 100644 --- a/example/lib/demo/screens/home/home_screen.dart +++ b/example/lib/demo/screens/home/home_screen.dart @@ -15,7 +15,8 @@ class HomeScreen extends StatelessWidget { @override Widget build(BuildContext context) { - final bool isLight = Theme.of(context).brightness == Brightness.light; + final ThemeData theme = Theme.of(context); + final bool isLight = theme.brightness == Brightness.light; // Use annotated region to style the status bar when there is no // AppBar and also style system navigation bar. return AnnotatedRegion( @@ -26,7 +27,7 @@ class HomeScreen extends StatelessWidget { body: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - SizedBox(height: MediaQuery.of(context).padding.top), + SizedBox(height: MediaQuery.paddingOf(context).top), const Spacer(), Text( App.appName, @@ -135,7 +136,7 @@ class HomeScreen extends StatelessWidget { padding: const EdgeInsets.all(7), child: Text( 'Try the color picker', - style: Theme.of(context).textTheme.titleLarge, + style: theme.textTheme.titleLarge, ), ), ), @@ -154,11 +155,11 @@ class HomeScreen extends StatelessWidget { const Spacer(), Text( 'Using flex_color_picker version ${App.version}', - style: Theme.of(context).textTheme.bodySmall, + style: theme.textTheme.bodySmall, ), Text( 'Web build with Flutter ${App.flutterVersion}', - style: Theme.of(context).textTheme.bodySmall, + style: theme.textTheme.bodySmall, ), const SizedBox(height: 10), ], diff --git a/example/lib/demo/utils/app.dart b/example/lib/demo/utils/app.dart index 38cb5a1..cee3e4a 100644 --- a/example/lib/demo/utils/app.dart +++ b/example/lib/demo/utils/app.dart @@ -9,15 +9,15 @@ class App { // Web demo with inside the app. Shown on the start screen in the demo, // so people testing it don't have to ask. Also info for the About screen. static const String appName = 'FlexColorPicker'; - static const String version = '3.3.1'; + static const String version = '3.4.0'; static const String packageVersion = 'FlexColorPicker package $version'; static final Uri packageUri = Uri( scheme: 'https', host: 'pub.dev', path: 'packages/flex_color_picker', ); - static const String flutterVersion = 'Channel stable 3.16.8 (canvaskit)'; - static const String copyright = '© 2020 - 2023'; + static const String flutterVersion = 'Channel stable 3.19.2 (canvaskit)'; + static const String copyright = '© 2020 - 2024'; static const String author = 'Mike Rydstrom'; static const String license = 'BSD 3-Clause License'; static const String icon = 'assets/images/app_icon.png'; @@ -55,6 +55,29 @@ class App { static const Color blueJean = Color(0xFF4f75b8); static const Color deepBlueSea = Color(0xFF132b80); + static const Color mojo = Color(0xFFC74141); + static const Color trendyPink = Color(0xFF9565A5); + static const Color parsley = Color(0xFF16552C); + static const Color walnut = Color(0xFF753D1F); + static const Color parsleyOpacity = Color(0x5516552C); + + // Add a custom white to black grey scale. + static const MaterialColor whiteBlueBlack = MaterialColor( + 0xFF4355B9, // Set the 500 index value here. + { + 50: Color(0xFFFFFFFF), + 100: Color(0xFFF0EFFF), + 200: Color(0xFFBAC3FF), + 300: Color(0xFF7789F0), + 400: Color(0xFF5D6FD4), + 500: Color(0xFF4355B9), + 600: Color(0xFF293CA0), + 700: Color(0xFF08218A), + 800: Color(0xFF00105C), + 900: Color(0xFF000000), + }, + ); + // Add a custom white to black grey scale. static const MaterialColor whiteToBlack = MaterialColor( 0xFF7C7D80, // Set the 500 index value here. @@ -72,20 +95,41 @@ class App { }, ); - // Add a custom white to black grey scale. - static const MaterialColor whiteBlueBlack = MaterialColor( - 0xFF4355B9, // Set the 500 index value here. + // Add a custom black transparency. + // + // Index 50 is fully transparent black. + // + // Index 100 is 10% of max alpha value or 10% opacity, and so on. + static const MaterialColor blackTransparency = MaterialColor( + 0x7F000000, // Set the 500 index value here. { - 50: Color(0xFFFFFFFF), - 100: Color(0xFFF0EFFF), - 200: Color(0xFFBAC3FF), - 300: Color(0xFF7789F0), - 400: Color(0xFF5D6FD4), - 500: Color(0xFF4355B9), - 600: Color(0xFF293CA0), - 700: Color(0xFF08218A), - 800: Color(0xFF00105C), - 900: Color(0xFF000000), + 50: Color(0x00000000), + 100: Color(0x19000000), + 200: Color(0x33000000), + 300: Color(0x4C000000), + 400: Color(0x66000000), + 500: Color(0x7F000000), + 600: Color(0x99000000), + 700: Color(0xB2000000), + 800: Color(0xCC000000), + 900: Color(0xE5000000), + }, + ); + + // Add a custom parsley swatch + static const MaterialColor allSwatchParsleyTransparent = MaterialColor( + 0x9930874C, // Set the 500 index value here. + { + 50: Color(0x99F5FFF2), + 100: Color(0x99ADF3B9), + 200: Color(0x9982D995), + 300: Color(0x9967BD7C), + 400: Color(0x994CA164), + 500: Color(0x9930874C), + 600: Color(0x990C6D35), + 700: Color(0x99005226), + 800: Color(0x99003918), + 900: Color(0x9900210B), }, ); @@ -113,5 +157,16 @@ class App { ColorTools.createPrimarySwatch(deepBlueSea): 'Deep blue sea', whiteBlueBlack: 'White via Blue to Black', whiteToBlack: 'White to black', + blackTransparency: 'Black transparency', + }; + + static Map, String> get colorsOptionsMap => + , String>{ + ColorTools.createPrimarySwatch(mojo): 'Mojo', + ColorTools.createPrimarySwatch(trendyPink): 'Trendy pink', + ColorTools.createPrimarySwatch(parsley): 'Parsley', + ColorTools.createPrimarySwatch(walnut): 'Walnut', + ColorTools.createPrimarySwatch(parsleyOpacity): 'Transparent parsley', + allSwatchParsleyTransparent: 'Parsley all indexes transparent', }; } diff --git a/example/lib/demo/utils/keys.dart b/example/lib/demo/utils/keys.dart index 8c02f0d..81087b1 100644 --- a/example/lib/demo/utils/keys.dart +++ b/example/lib/demo/utils/keys.dart @@ -59,6 +59,7 @@ class Keys { static const String okButton = 'okButton'; static const String closeIsLast = 'closeIsLast'; static const String dialogActionButtons = 'dialogActionButtons'; + static const String dialogActionOnlyOkButton = 'dialogActionOnlyOkButton'; static const String dialogActionOrder = 'dialogActionOrder'; static const String dialogActionIcons = 'dialogActionIcons'; // Copy Paste Actions Riverpod providers. @@ -136,6 +137,7 @@ class Keys { okButton: true, closeIsLast: true, dialogActionButtons: true, + dialogActionOnlyOkButton: true, dialogActionOrder: ColorPickerActionButtonOrder.okIsRight, dialogActionIcons: true, // Copy Paste Actions Riverpod providers. diff --git a/example/macos/Runner.xcodeproj/project.pbxproj b/example/macos/Runner.xcodeproj/project.pbxproj index 957741a..3c79f59 100644 --- a/example/macos/Runner.xcodeproj/project.pbxproj +++ b/example/macos/Runner.xcodeproj/project.pbxproj @@ -203,7 +203,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 1430; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 33CC10EC2044A3C60003C045 = { diff --git a/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 83d8872..5b055a3 100644 --- a/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ =3.2.0 <4.0.0" - flutter: ">=3.16.0" + dart: ">=3.3.0 <4.0.0" + flutter: ">=3.19.0" diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 67e9876..7b462f9 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -1,10 +1,10 @@ name: color_picker_example description: Two examples of how to use the FlexColorPicker package. -version: 3.3.1 +version: 3.4.0 publish_to: 'none' environment: sdk: '>=2.19.0 <4.0.0' - flutter: '>=3.7.0' + flutter: '>=3.19.0' dependencies: # https://pub.dev/packages/flex_color_picker @@ -15,7 +15,7 @@ dependencies: sdk: flutter # https://pub.dev/packages/flutter_riverpod - flutter_riverpod: ^2.4.9 + flutter_riverpod: ^2.4.10 # https://pub.dev/packages/google_fonts google_fonts: ^6.1.0 @@ -28,7 +28,7 @@ dependencies: # https://pub.dev/packages/url_launcher # Used for launching a WEB URL (by Google flutter.dev). - url_launcher: ^6.2.3 + url_launcher: ^6.2.5 dev_dependencies: flutter_test: diff --git a/lib/src/color_picker.dart b/lib/src/color_picker.dart index 20a4bf4..b024aa1 100644 --- a/lib/src/color_picker.dart +++ b/lib/src/color_picker.dart @@ -13,10 +13,9 @@ import 'functions/picker_functions.dart'; import 'models/color_picker_action_buttons.dart'; import 'models/color_picker_copy_paste_behavior.dart'; import 'models/color_picker_type.dart'; -import 'universal_widgets/if_wrapper.dart'; import 'widgets/color_code_field.dart'; import 'widgets/color_picker_toolbar.dart'; -import 'widgets/context_copy_paste_menu.dart'; +import 'widgets/copy_paste_handler.dart'; import 'widgets/main_colors.dart'; import 'widgets/opacity/opacity_slider.dart'; import 'widgets/picker_selector.dart'; @@ -81,6 +80,7 @@ class ColorPicker extends StatefulWidget { ColorPickerType.accent: true, ColorPickerType.bw: false, ColorPickerType.custom: false, + ColorPickerType.customSecondary: false, ColorPickerType.wheel: false, }, this.enableShadesSelection = true, @@ -90,6 +90,8 @@ class ColorPicker extends StatefulWidget { this.crossAxisAlignment = CrossAxisAlignment.center, this.padding = const EdgeInsets.all(16), this.columnSpacing = 8, + this.toolbarSpacing, + this.shadesSpacing, // Opacity slider this.enableOpacity = false, this.opacityTrackHeight = 36, @@ -154,13 +156,24 @@ class ColorPicker extends StatefulWidget { ColorPickerType.bw: _selectBlackWhiteLabel, ColorPickerType.both: _selectBothLabel, ColorPickerType.custom: _selectCustomLabel, + ColorPickerType.customSecondary: _selectCustomSecondaryLabel, ColorPickerType.wheel: _selectWheelAnyLabel, }, - // Custom color swatches and name map for the custom color swatches. + // Custom color, swatches and name map for the custom color swatches. this.customColorSwatchesAndNames = const , String>{}, + this.customSecondaryColorSwatchesAndNames = + const , String>{}, // }) : assert(columnSpacing >= 0 && columnSpacing <= 300, 'The picker item column spacing must be from 0 to max 300 dp.'), + assert( + toolbarSpacing == null || + (toolbarSpacing >= 0 && toolbarSpacing <= 300), + 'The spacing must be null or from 0 to max 300 dp.'), + assert( + shadesSpacing == null || + (shadesSpacing >= 0 && shadesSpacing <= 300), + 'The spacing must be null or from 0 to max 300 dp.'), assert(spacing >= 0 && spacing <= 50, 'The picker item spacing must be from 0 to max 50 dp.'), assert(runSpacing >= 0 && runSpacing <= 50, @@ -284,6 +297,18 @@ class ColorPicker extends StatefulWidget { /// Defaults to 8 dp. Must be from 0 to 300 dp. final double columnSpacing; + /// Vertical spacing below the top toolbar header and action buttons. + /// + /// If not defined, defaults to [columnSpacing]. + /// Must be null or from 0 to 300 dp. + final double? toolbarSpacing; + + /// Vertical spacing below the Material-2 based color shades palette. + /// + /// If not defined, defaults to [columnSpacing]. + /// Must be null or from 0 to 300 dp. + final double? shadesSpacing; + /// Enable the opacity control for the color value. /// /// Set to true to allow users to control the opacity value of the @@ -294,6 +319,11 @@ class ColorPicker extends StatefulWidget { /// it is returned in the alpha channel of the returned ARGB color value, in /// the onColor callbacks. /// + /// When false, colors that has any other alpha value than 0xFF are changed + /// to 0xFF. To allow custom colors and pasted in color values without + /// setting [enableOpacity] to true and showing the opacity slider, set + /// [enableTransparentCustomColors] to true. + /// /// Defaults to false. final bool enableOpacity; @@ -346,7 +376,7 @@ class ColorPicker extends StatefulWidget { /// for main and swatch shades indicator items. /// /// If false, the tonal color items will be smaller and auto sized for the - /// palette to be same width as the Material Color palette. + /// palette to be same width as the Material-2 Color palette. /// /// Defaults to false. The color boxes are smaller, but length of their /// items is the same as MaterialColor swatch. You may prefer true to get @@ -662,6 +692,7 @@ class ColorPicker extends StatefulWidget { /// * [ColorPickerType.accent] : 'Primary' /// * [ColorPickerType.bw] : 'Black & White' /// * [ColorPickerType.custom] : 'Custom' + /// * [ColorPickerType.customSecondary] : 'Option' /// * [ColorPickerType.wheel] : 'Wheel' final Map pickerTypeLabels; @@ -676,6 +707,17 @@ class ColorPicker extends StatefulWidget { /// will not be shown even if it is enabled in [pickersEnabled]. final Map, String> customColorSwatchesAndNames; + /// Color swatch to name map, with custom swatches and their names. + /// + /// Used to provide custom [ColorSwatch] objects to the custom color picker, + /// including their custom name label. These colors, their swatch shades + /// and names, are shown and used when the picker type + /// [ColorPickerType.customSecondary] option is enabled in the color picker. + /// + /// Defaults to an empty map. If the map is empty, the custom colors picker + /// will not be shown even if it is enabled in [pickersEnabled]. + final Map, String> customSecondaryColorSwatchesAndNames; + /// English default label for picker with both primary and accent colors. static const String _selectBothLabel = 'Primary & Accent'; @@ -691,6 +733,9 @@ class ColorPicker extends StatefulWidget { /// English default label for picker with custom defined colors. static const String _selectCustomLabel = 'Custom'; + /// English default label for picker with custom secondary defined colors. + static const String _selectCustomSecondaryLabel = 'Option'; + /// English default label for the HSV wheel picker that can select any color. static const String _selectWheelAnyLabel = 'Wheel'; @@ -906,93 +951,207 @@ class ColorPicker extends StatefulWidget { // Make the dialog OK button. final String okButtonLabel = actionButtons.dialogOkButtonLabel ?? translate.okButtonLabel; - final Widget okButtonContent = Wrap( - alignment: WrapAlignment.center, - crossAxisAlignment: WrapCrossAlignment.center, - children: [ - if (actionButtons.dialogActionIcons) - Padding( - padding: const EdgeInsetsDirectional.only(end: 4), - child: Icon(actionButtons.okIcon), - ), - Text(okButtonLabel), - ], - ); + final Text okButtonContent = Text(okButtonLabel); Widget okButton; switch (actionButtons.dialogOkButtonType) { case ColorPickerActionButtonType.text: - okButton = TextButton( - onPressed: () { - Navigator.of(context, rootNavigator: actionButtons.useRootNavigator) - .pop(true); - }, - child: okButtonContent, - ); - break; + okButton = actionButtons.dialogActionIcons + ? TextButton.icon( + onPressed: () { + Navigator.of(context, + rootNavigator: actionButtons.useRootNavigator) + .pop(true); + }, + icon: Icon(actionButtons.okIcon), + label: okButtonContent, + ) + : TextButton( + onPressed: () { + Navigator.of(context, + rootNavigator: actionButtons.useRootNavigator) + .pop(true); + }, + child: okButtonContent, + ); case ColorPickerActionButtonType.outlined: - okButton = OutlinedButton( - onPressed: () { - Navigator.of(context, rootNavigator: actionButtons.useRootNavigator) - .pop(true); - }, - child: okButtonContent, - ); - break; + okButton = actionButtons.dialogActionIcons + ? OutlinedButton.icon( + onPressed: () { + Navigator.of(context, + rootNavigator: actionButtons.useRootNavigator) + .pop(true); + }, + icon: Icon(actionButtons.okIcon), + label: okButtonContent, + ) + : OutlinedButton( + onPressed: () { + Navigator.of(context, + rootNavigator: actionButtons.useRootNavigator) + .pop(true); + }, + child: okButtonContent, + ); case ColorPickerActionButtonType.elevated: - okButton = ElevatedButton( - onPressed: () { - Navigator.of(context, rootNavigator: actionButtons.useRootNavigator) - .pop(true); - }, - child: okButtonContent, - ); - break; + okButton = actionButtons.dialogActionIcons + ? ElevatedButton.icon( + onPressed: () { + Navigator.of(context, + rootNavigator: actionButtons.useRootNavigator) + .pop(true); + }, + icon: Icon(actionButtons.okIcon), + label: okButtonContent, + ) + : ElevatedButton( + onPressed: () { + Navigator.of(context, + rootNavigator: actionButtons.useRootNavigator) + .pop(true); + }, + child: okButtonContent, + ); + case ColorPickerActionButtonType.filled: + okButton = actionButtons.dialogActionIcons + ? FilledButton.icon( + onPressed: () { + Navigator.of(context, + rootNavigator: actionButtons.useRootNavigator) + .pop(true); + }, + icon: Icon(actionButtons.okIcon), + label: okButtonContent, + ) + : FilledButton( + onPressed: () { + Navigator.of(context, + rootNavigator: actionButtons.useRootNavigator) + .pop(true); + }, + child: okButtonContent, + ); + case ColorPickerActionButtonType.filledTonal: + okButton = actionButtons.dialogActionIcons + ? FilledButton.tonalIcon( + onPressed: () { + Navigator.of(context, + rootNavigator: actionButtons.useRootNavigator) + .pop(true); + }, + icon: Icon(actionButtons.okIcon), + label: okButtonContent, + ) + : FilledButton.tonal( + onPressed: () { + Navigator.of(context, + rootNavigator: actionButtons.useRootNavigator) + .pop(true); + }, + child: okButtonContent, + ); } // Make the dialog OK button. final String cancelButtonLabel = actionButtons.dialogCancelButtonLabel ?? translate.cancelButtonLabel; - final Widget cancelButtonContent = Wrap( - alignment: WrapAlignment.center, - crossAxisAlignment: WrapCrossAlignment.center, - children: [ - if (actionButtons.dialogActionIcons) - Padding( - padding: const EdgeInsetsDirectional.only(end: 4), - child: Icon(actionButtons.closeIcon), - ), - Text(cancelButtonLabel), - ], - ); + final Widget cancelButtonContent = Text(cancelButtonLabel); Widget cancelButton; switch (actionButtons.dialogCancelButtonType) { case ColorPickerActionButtonType.text: - cancelButton = TextButton( - onPressed: () { - Navigator.of(context, rootNavigator: actionButtons.useRootNavigator) - .pop(false); - }, - child: cancelButtonContent, - ); - break; + cancelButton = actionButtons.dialogActionIcons + ? TextButton.icon( + onPressed: () { + Navigator.of(context, + rootNavigator: actionButtons.useRootNavigator) + .pop(false); + }, + icon: Icon(actionButtons.closeIcon), + label: cancelButtonContent, + ) + : TextButton( + onPressed: () { + Navigator.of(context, + rootNavigator: actionButtons.useRootNavigator) + .pop(false); + }, + child: cancelButtonContent, + ); case ColorPickerActionButtonType.outlined: - cancelButton = OutlinedButton( - onPressed: () { - Navigator.of(context, rootNavigator: actionButtons.useRootNavigator) - .pop(false); - }, - child: cancelButtonContent, - ); - break; + cancelButton = actionButtons.dialogActionIcons + ? OutlinedButton.icon( + onPressed: () { + Navigator.of(context, + rootNavigator: actionButtons.useRootNavigator) + .pop(false); + }, + icon: Icon(actionButtons.closeIcon), + label: cancelButtonContent, + ) + : OutlinedButton( + onPressed: () { + Navigator.of(context, + rootNavigator: actionButtons.useRootNavigator) + .pop(false); + }, + child: cancelButtonContent, + ); case ColorPickerActionButtonType.elevated: - cancelButton = ElevatedButton( - onPressed: () { - Navigator.of(context, rootNavigator: actionButtons.useRootNavigator) - .pop(false); - }, - child: cancelButtonContent, - ); - break; + cancelButton = actionButtons.dialogActionIcons + ? ElevatedButton.icon( + onPressed: () { + Navigator.of(context, + rootNavigator: actionButtons.useRootNavigator) + .pop(false); + }, + icon: Icon(actionButtons.closeIcon), + label: cancelButtonContent, + ) + : ElevatedButton( + onPressed: () { + Navigator.of(context, + rootNavigator: actionButtons.useRootNavigator) + .pop(false); + }, + child: cancelButtonContent, + ); + case ColorPickerActionButtonType.filled: + cancelButton = actionButtons.dialogActionIcons + ? FilledButton.icon( + onPressed: () { + Navigator.of(context, + rootNavigator: actionButtons.useRootNavigator) + .pop(false); + }, + icon: Icon(actionButtons.closeIcon), + label: cancelButtonContent, + ) + : FilledButton( + onPressed: () { + Navigator.of(context, + rootNavigator: actionButtons.useRootNavigator) + .pop(false); + }, + child: cancelButtonContent, + ); + case ColorPickerActionButtonType.filledTonal: + cancelButton = actionButtons.dialogActionIcons + ? FilledButton.tonalIcon( + onPressed: () { + Navigator.of(context, + rootNavigator: actionButtons.useRootNavigator) + .pop(false); + }, + icon: Icon(actionButtons.closeIcon), + label: cancelButtonContent, + ) + : FilledButton.tonal( + onPressed: () { + Navigator.of(context, + rootNavigator: actionButtons.useRootNavigator) + .pop(false); + }, + child: cancelButtonContent, + ); } // False if dialog cancelled, true if color selected @@ -1023,9 +1182,9 @@ class ColorPicker extends StatefulWidget { ? [ if (okIsLeft) ...[ okButton, - cancelButton, + if (!actionButtons.dialogActionOnlyOkButton) cancelButton, ] else ...[ - cancelButton, + if (!actionButtons.dialogActionOnlyOkButton) cancelButton, okButton, ] ] @@ -1071,7 +1230,7 @@ class ColorPicker extends StatefulWidget { from: context, to: Navigator.of( context, - rootNavigator: true, + rootNavigator: actionButtons.useRootNavigator, ).context, ); if (useSafeArea) { @@ -1123,10 +1282,15 @@ class _ColorPickerState extends State { // Which picker are we using now. late ColorPickerType _activePicker; - // Current selected color + // Current selected color, it never has opacity. The slider sets opacity to + // in the _opacity property. late Color _selectedColor; - // Current color opacity value + // A color that is tapped on in indicators. This color may have opacity if + // the source color has opacity. + late Color _tappedColor; + + // Current color opacity value, this is the opacity set by the slider. late double _opacity; // Color picker indicator selected item should request focus. @@ -1158,6 +1322,9 @@ class _ColorPickerState extends State { // sub palette, it is is not selected. bool _tonalOperated = false; + // Set to true when widget update triggered via internal state change. + bool _fromInternal = false; + // Color wheel picker should request focus. bool _wheelShouldFocus = false; @@ -1188,11 +1355,10 @@ class _ColorPickerState extends State { @override void initState() { super.initState(); - // Set selected color to the widget constructor provided start color, - // if opacity is not enabled, override alpha to fully opaque. - _selectedColor = - widget.enableOpacity ? widget.color : widget.color.withAlpha(0xFF); - // Get current opacity of passed in color, if opacity is enabled. + // We always remove alpha from incoming color. + _selectedColor = widget.color.withAlpha(0xFF); + _tappedColor = widget.color; + // Opacity is captured in _opacity if enabled. _opacity = widget.enableOpacity ? widget.color.opacity : 1; // Picker labels, use english fallbacks if none provided. _pickerLabels = { @@ -1207,6 +1373,9 @@ class _ColorPickerState extends State { ColorPicker._selectBlackWhiteLabel, ColorPickerType.custom: widget.pickerTypeLabels[ColorPickerType.custom] ?? ColorPicker._selectCustomLabel, + ColorPickerType.customSecondary: + widget.pickerTypeLabels[ColorPickerType.customSecondary] ?? + ColorPicker._selectCustomSecondaryLabel, ColorPickerType.wheel: widget.pickerTypeLabels[ColorPickerType.wheel] ?? ColorPicker._selectWheelAnyLabel, }; @@ -1217,9 +1386,11 @@ class _ColorPickerState extends State { ColorPickerType.accent: ColorTools.accentColors, ColorPickerType.bw: ColorTools.blackAndWhite, ColorPickerType.custom: widget.customColorSwatchesAndNames.keys.toList(), + ColorPickerType.customSecondary: + widget.customSecondaryColorSwatchesAndNames.keys.toList(), ColorPickerType.wheel: >[ // Make a swatch of the selected color in the wheel. - ColorTools.primarySwatch(_selectedColor.withAlpha(0xFF)) + ColorTools.primarySwatch(_selectedColor) ], }; // Enabled color pickers, with defaults if not specified. @@ -1235,6 +1406,10 @@ class _ColorPickerState extends State { // Custom picker is always disabled if no custom swatches are given. (widget.pickersEnabled[ColorPickerType.custom] ?? false) && widget.customColorSwatchesAndNames.isNotEmpty, + ColorPickerType.customSecondary: + // Custom secondary is always disabled if no custom swatches are given + (widget.pickersEnabled[ColorPickerType.customSecondary] ?? false) && + widget.customSecondaryColorSwatchesAndNames.isNotEmpty, ColorPickerType.wheel: widget.pickersEnabled[ColorPickerType.wheel] ?? false, }; @@ -1248,6 +1423,7 @@ class _ColorPickerState extends State { // Always update tonal when ColorPicker is initialized. _tonalShouldUpdate = true; _tonalOperated = false; + _fromInternal = false; // If there are no shade or tonal colors displayed, the wheel must // focus on init. _wheelShouldFocus = @@ -1263,23 +1439,46 @@ class _ColorPickerState extends State { @override void didUpdateWidget(ColorPicker oldWidget) { + super.didUpdateWidget(oldWidget); if (_debug) { - debugPrint('didUpdateWidget called **********************************'); + debugPrint('didUpdateWidget called: fromInternal: $_fromInternal' + ' ******************************'); } // Set to true if a change was done where we need to find the picker again. bool shouldFindPickerAndSwatch = false; - // Opacity enable/disable changed, update selected color and opacity. if (widget.enableOpacity != oldWidget.enableOpacity) { + _opacity = widget.enableOpacity ? widget.color.opacity : 1; if (_debug) { - debugPrint('didUpdateWidget enableOpacity = ' - '${widget.enableOpacity == oldWidget.enableOpacity}'); + debugPrint('didUpdateWidget changed: enableOpacity = ' + '${widget.enableOpacity == oldWidget.enableOpacity}' + ' opacity=$_opacity'); } - _selectedColor = - widget.enableOpacity ? widget.color : widget.color.withAlpha(0xFF); + } + // The color was updated externally, update to new color and find picker. + if (widget.color != _selectedColor || widget.color != _tappedColor) { + if (_debug) { + debugPrint('didUpdateWidget changed color: ' + 'color=${widget.color} selectedColor=$_selectedColor'); + } + _selectedColor = widget.color.withAlpha(0xFF); _opacity = widget.enableOpacity ? widget.color.opacity : 1; + // Make a swatch too be to find it on wheel, if color is there. + _typeToSwatchMap[ColorPickerType.wheel] = >[ + ColorTools.createPrimarySwatch(_selectedColor), + ]; + if (!_fromInternal) { + _tappedColor = _selectedColor; + // Wheel and edit needs to update. + _wheelShouldUpdate = true; + _editShouldUpdate = true; + // Tonal picker should update from external change. + _tonalShouldUpdate = true; + _tonalOperated = false; + // We need to find the right picker again. + shouldFindPickerAndSwatch = true; + } } - // Picker labels map changed, update used one, with its default fallbacks. if (!mapEquals(widget.pickerTypeLabels, oldWidget.pickerTypeLabels)) { if (_debug) { @@ -1302,32 +1501,32 @@ class _ColorPickerState extends State { ColorPickerType.custom: widget.pickerTypeLabels[ColorPickerType.custom] ?? ColorPicker._selectCustomLabel, + ColorPickerType.customSecondary: + widget.pickerTypeLabels[ColorPickerType.customSecondary] ?? + ColorPicker._selectCustomSecondaryLabel, ColorPickerType.wheel: widget.pickerTypeLabels[ColorPickerType.wheel] ?? ColorPicker._selectWheelAnyLabel, }; } - // Pickers customColorSwatchesAndNames map changed, or pickersEnabled map // changed, they depend on each other, so we always update state of both. if (widget.customColorSwatchesAndNames.toString() != oldWidget.customColorSwatchesAndNames.toString() || !mapEquals(widget.pickersEnabled, oldWidget.pickersEnabled)) { - // TODO(rydmike): Investigate the mapEquals issue. // In above un-equality check, the mapEquals, or with map != operator, // does not work if you provide a map made with createPrimarySwatch or - // createAccentSwatch. It should, not sure why it does not. The Wheel - // Picker does not work entirely as intended if the above if is - // evaluated incorrectly. The issue will be seen via that it will not - // keep the Wheel picker active when selecting known swatch color in it, - // it will instead move to the picker where it exists. This is not - // desired, we want to stay on the Wheel in this case. If you see the - // wrong behavior it is due to the above IF being evaluated incorrectly. + // createAccentSwatch. The Wheel Picker does not work entirely as + // intended if the above IF is evaluated incorrectly. The wrong result + // will be noticed seen as that Wheel picker will not be kept active + // when selecting known swatch color in it. It will instead move to + // the picker to Swatch where the color exists. This is not desired. + // We want to stay on the Wheel in this case. If you see the + // wrong behavior it is due to the above IF being evaluated incorrectly. // When comparing the `customColorSwatchesAndNames` new and old Widget - // maps toString results, it was observed they they were equal for the + // maps toString results, it was observed that they were equal for the // problem use cases, while the [mapEquals] or == operator was not. // Therefore using `toString` comparisons for now to get around the issue, - // not ideal, but it seems to work. - + // not ideal, but it works OK. if (_debug) { debugPrint('didUpdateWidget pickersEnabled or custom swatch updated!'); } @@ -1339,9 +1538,14 @@ class _ColorPickerState extends State { ColorPickerType.bw: ColorTools.blackAndWhite, ColorPickerType.custom: widget.customColorSwatchesAndNames.keys .toList(), // Use empty map if no custom swatch given. + ColorPickerType.customSecondary: widget + .customSecondaryColorSwatchesAndNames.keys + .toList(), // Use empty map if no custom swatch given. ColorPickerType.wheel: >[ // Make a swatch of the selected color in the wheel. - ColorTools.primarySwatch(_selectedColor.withAlpha(0xFF)) + // If color has opacity, it will make a swatch of the color with + // same opacity in it as well. + ColorTools.primarySwatch(_selectedColor) ], }; // Update enabled color pickers, with defaults if none given, depends @@ -1359,6 +1563,10 @@ class _ColorPickerState extends State { // Custom picker is always disabled if no custom swatches are given. (widget.pickersEnabled[ColorPickerType.custom] ?? false) && widget.customColorSwatchesAndNames.isNotEmpty, + ColorPickerType.customSecondary: + // Custom second is always disabled if no custom swatches are given. + (widget.pickersEnabled[ColorPickerType.customSecondary] ?? false) && + widget.customSecondaryColorSwatchesAndNames.isNotEmpty, ColorPickerType.wheel: widget.pickersEnabled[ColorPickerType.wheel] ?? false, }; @@ -1377,43 +1585,16 @@ class _ColorPickerState extends State { if (!listEquals(widget.recentColors, _recentColors)) { _recentColors = [...widget.recentColors]; } - // The widget color was updated externally since it differs from internally - // kept state, we should update the widget to new color and find picker. - if (widget.color != _selectedColor) { - if (_debug) { - debugPrint('didUpdateWidget external color update!'); - } - _selectedColor = - widget.enableOpacity ? widget.color : widget.color.withAlpha(0xFF); - _opacity = widget.enableOpacity ? widget.color.opacity : 1; - // Need to make a swatch too be able to find it on wheel, if it is there. - _typeToSwatchMap[ColorPickerType.wheel] = >[ - ColorTools.createPrimarySwatch(_selectedColor.withAlpha(0xFF)), - ]; - // Wheel and edit needs to update. - _wheelShouldUpdate = true; - _editShouldUpdate = true; - // Tonal picker should update from external change. - _tonalShouldUpdate = true; - _tonalOperated = false; - // We need to find the right picker again. - shouldFindPickerAndSwatch = true; - } - // + // Last find picker and swatch, if the flag to do so is set. if (shouldFindPickerAndSwatch) { - if (_debug) { - debugPrint('didUpdateWidget calls findPicker and updateActiveSwatch'); - } - // When in this IF branch, we need to find the right picker again. + if (_debug) debugPrint('didUpdateWidget shouldFindPickerAndSwatch'); _findPicker(); - // And also update the active swatch again. _updateActiveSwatch(); } - // - super.didUpdateWidget(oldWidget); + _fromInternal = false; } - // Find the best matching picker of available ones to show current color. + // Find the best matching picker of available ones to show selected color. void _findPicker() { // The selected color indicator in the picker should request focus. _selectedShouldFocus = true; @@ -1425,13 +1606,11 @@ class _ColorPickerState extends State { // the same thing! :) _usePickerSelector = _pickers.values.fold(0, (int t, bool e) => t + (e ? 1 : 0)) > 1; - // If we have a picker selector, we get the best one of the enabled ones, - // to show the current _selectedColor. + // to show the current selectedColor. if (_usePickerSelector) { _activePicker = findColorInSelector( - // Disregard any alpha on selected color to find its color value only. - color: _selectedColor.withAlpha(0xFF), + color: _tappedColor, typeToSwatchMap: _typeToSwatchMap, pickersEnabled: _pickers, lookInShades: widget.enableShadesSelection, @@ -1451,6 +1630,8 @@ class _ColorPickerState extends State { _activePicker = ColorPickerType.bw; } else if (_pickers[ColorPickerType.custom]!) { _activePicker = ColorPickerType.custom; + } else if (_pickers[ColorPickerType.customSecondary]!) { + _activePicker = ColorPickerType.customSecondary; } else if (_pickers[ColorPickerType.wheel]!) { _activePicker = ColorPickerType.wheel; } @@ -1474,34 +1655,46 @@ class _ColorPickerState extends State { // Update shades swatch to the correct swatch for the selected color. void _updateActiveSwatch() { // The typical case is that we have a normal swatch where we need to - // find the swatch that contains the _selectedColor. + // find the swatch that contains the selectedColor. if (_activePicker != ColorPickerType.wheel && !_tonalOperated) { // Get list of color swatches from the map for the active picker. _activeColorSwatchList = _typeToSwatchMap[_activePicker]!; + + if (_debug) { + debugPrint('_updateActiveSwatch _tappedColor= $_tappedColor'); + } // Find the swatch that selected color belongs to from the swatches in // the active picker and set this swatch as _activeSwatch. _activeSwatch = findColorSwatch( - _selectedColor.withAlpha(0xFF), + _tappedColor, _activeColorSwatchList, widget.includeIndex850, ) as ColorSwatch?; + if (_debug) { + debugPrint('_updateActiveSwatch _activeSwatch= $_activeSwatch'); + } // For the wheel picker we need to check if the selected color belongs to // a pre-defined swatch and if it does, return that as the active swatch. // If the selected color does not belong to any pre-defined color swatch, // then we compute a color swatch for it. } else { - if (ColorTools.isAccentColor(_selectedColor.withAlpha(0xFF))) { - _activeSwatch = ColorTools.accentSwatch(_selectedColor.withAlpha(0xFF)); - } else if (ColorTools.isPrimaryColor(_selectedColor.withAlpha(0xFF))) { - _activeSwatch = - ColorTools.primarySwatch(_selectedColor.withAlpha(0xFF)); - } else if (ColorTools.isBlackAndWhiteColor( - _selectedColor.withAlpha(0xFF))) { - _activeSwatch = - ColorTools.blackAndWhiteSwatch(_selectedColor.withAlpha(0xFF)); + if (ColorTools.isAccentColor(_tappedColor)) { + _activeSwatch = ColorTools.accentSwatch(_tappedColor); + } else if (ColorTools.isPrimaryColor(_tappedColor)) { + _activeSwatch = ColorTools.primarySwatch(_tappedColor); + } else if (ColorTools.isBlackAndWhiteColor(_tappedColor)) { + _activeSwatch = ColorTools.blackAndWhiteSwatch(_tappedColor); + } else if (ColorTools.isCustomColor( + _tappedColor, widget.customColorSwatchesAndNames)) { + _activeSwatch = ColorTools.customSwatch( + _tappedColor, widget.customColorSwatchesAndNames); + } else if (ColorTools.isCustomColor( + _tappedColor, widget.customSecondaryColorSwatchesAndNames)) { + _activeSwatch = ColorTools.customSwatch( + _tappedColor, widget.customSecondaryColorSwatchesAndNames); } else { _activeSwatch = ColorTools.customSwatch( - _selectedColor.withAlpha(0xFF), widget.customColorSwatchesAndNames); + _tappedColor, widget.customSecondaryColorSwatchesAndNames); } } // We did not find the selected color in any active swatch list, in that @@ -1510,7 +1703,8 @@ class _ColorPickerState extends State { // situation where a selected color was passed to the color picker that was // not found in any of the provided swatches in any enabled pickers. // If the wheel picker is enabled, then the color will always be found - // in it as a last resort. + // in it as a last resort, if not we may end up here, using the first + // swatch in the list as the active swatch. _activeSwatch ??= _activeColorSwatchList[0]; } @@ -1532,514 +1726,507 @@ class _ColorPickerState extends State { final TextStyle effectiveGenericNameStyle = (widget.colorNameTextStyle ?? Theme.of(context).textTheme.bodyMedium) ?? const TextStyle(); - // Set the default integer code value text style to bodyMedium if not given. final TextStyle effectiveCodeStyle = (widget.colorCodeTextStyle ?? Theme.of(context).textTheme.bodyMedium) ?? const TextStyle(); - // The logic below is used to determine if we will have a context menu // present at all in the Widget tree. final bool useContextMenu = widget.copyPasteBehavior.longPressMenu || widget.copyPasteBehavior.secondaryMenu || widget.copyPasteBehavior.secondaryOnDesktopLongOnDevice || widget.copyPasteBehavior.secondaryOnDesktopLongOnDeviceAndWeb; - // Should keyboard listener grab focus? If neither copy and paste keyboard // shortcuts are enabled, there is no need to autofocus, so let's skip it // then too, regardless of autofocus setting. final bool autoFocus = widget.copyPasteBehavior.autoFocus && (widget.copyPasteBehavior.ctrlC || widget.copyPasteBehavior.ctrlV); - - // We start with a RawKeyboardListener that is used to handle keyboard - // copy and paste events. - return RawKeyboardListener( + // Use a copy paste handler to handle copy and paste keyboard shortcuts, + // and also to handle the context menu for copy and paste. + return CopyPasteHandler( + pasteFromClipboard: _getClipboard, + copyToClipboard: _setClipboard, + useContextMenu: useContextMenu, + useLongPress: widget.copyPasteBehavior.longPressMenu, + useSecondaryTapDown: widget.copyPasteBehavior.secondaryMenu, + useSecondaryOnDesktopLongOnDevice: + widget.copyPasteBehavior.secondaryOnDesktopLongOnDevice, + useSecondaryOnDesktopLongOnDeviceAndWeb: + widget.copyPasteBehavior.secondaryOnDesktopLongOnDeviceAndWeb, + onCopyPasteMenuOpened: () { + // If we were on the wheel when the menu got opened, it's + // operation got cancelled by the context menu and we need to + // call onColorChangeEnd event with the selected color. + if (_onWheel) { + widget.onColorChangeEnd?.call(_selectedColor); + // We set onWheel to false as well, as we are no longer on + // the wheel and we handled the event. + setState(() { + _onWheel = false; + }); + } + }, focusNode: _focusNode, - onKey: _handleKeyEvent, - autofocus: autoFocus, - // If the Copy-Paste context menu feature is enabled we wrap the - // entire color picker with a Copy-Paste context menu, if it is not - // enabled, it is not a part of the widget tree at all. - child: IfWrapper( - condition: useContextMenu, - builder: (BuildContext context, Widget child) { - return ContextCopyPasteMenu( - useLongPress: widget.copyPasteBehavior.longPressMenu, - useSecondaryTapDown: widget.copyPasteBehavior.secondaryMenu, - useSecondaryOnDesktopLongOnDevice: - widget.copyPasteBehavior.secondaryOnDesktopLongOnDevice, - useSecondaryOnDesktopLongOnDeviceAndWeb: - widget.copyPasteBehavior.secondaryOnDesktopLongOnDeviceAndWeb, - onSelected: (CopyPasteCommands? value) { - if (value == CopyPasteCommands.copy) unawaited(_setClipboard()); - if (value == CopyPasteCommands.paste) unawaited(_getClipboard()); - }, - onOpen: () { - // If we were on the wheel when the menu got opened, it's - // operation got cancelled by the context menu and we need to - // call onColorChangeEnd event with the selected color. - if (_onWheel) { - // But only if there is call back for it. - widget.onColorChangeEnd?.call(_selectedColor); - // We set onWheel to false as well, as we are no longer on - // the wheel and we handled the event. - setState(() { - _onWheel = false; - }); - } - }, - child: child, - ); - }, - child: Padding( - padding: widget.padding, - // The ColorPicker layout is a column. It is up to the user to ensure - // that it fits, or use a scrolling parent if it does not. - child: Column( - crossAxisAlignment: widget.crossAxisAlignment, - children: [ - // Show title bar widget if we have one. - if (widget.title != null || - widget.copyPasteBehavior.copyButton || - widget.copyPasteBehavior.pasteButton || - widget.actionButtons.okButton || - widget.actionButtons.closeButton) - Padding( - padding: EdgeInsets.only(bottom: widget.columnSpacing), - child: ColorPickerToolbar( - title: widget.title, - onCopy: widget.copyPasteBehavior.copyButton - ? _setClipboard - : null, - onPaste: widget.copyPasteBehavior.pasteButton - ? _getClipboard - : null, - onOk: widget.actionButtons.okButton - ? () { - // OK was pressed, we pop and return TRUE. - // In case this was not used in a dialog the - // canPop will at least avoid a crash, but may - // still do the wrong thing. - if (Navigator.of(context, + autoFocus: autoFocus, + child: Padding( + padding: widget.padding, + child: Column( + crossAxisAlignment: widget.crossAxisAlignment, + children: [ + // Show title bar widget if we have one. + if (widget.title != null || + widget.copyPasteBehavior.copyButton || + widget.copyPasteBehavior.pasteButton || + widget.actionButtons.okButton || + widget.actionButtons.closeButton) + Padding( + padding: EdgeInsets.only( + bottom: widget.toolbarSpacing ?? widget.columnSpacing), + child: ColorPickerToolbar( + title: widget.title, + onCopy: widget.copyPasteBehavior.copyButton + ? _setClipboard + : null, + onPaste: widget.copyPasteBehavior.pasteButton + ? _getClipboard + : null, + onOk: widget.actionButtons.okButton + ? () { + // OK was pressed, we pop and return TRUE. + // In case this was not used in a dialog the + // canPop will at least avoid a crash, but may + // still do the wrong thing. + if (Navigator.of(context, + rootNavigator: + widget.actionButtons.useRootNavigator) + .canPop()) { + Navigator.of(context, rootNavigator: widget.actionButtons.useRootNavigator) - .canPop()) { - Navigator.of(context, - rootNavigator: - widget.actionButtons.useRootNavigator) - .pop(true); - } + .pop(true); } - : null, - onClose: widget.actionButtons.closeButton - ? () { - // Cancel was pressed, we pop and return FALSE. - // In case this was not used in a dialog the - // canPop will at least avoid a crash, but may - // still do the wrong thing. - if (Navigator.of(context, + } + : null, + onClose: widget.actionButtons.closeButton + ? () { + // Cancel was pressed, we pop and return FALSE. + // In case this was not used in a dialog the + // canPop will at least avoid a crash, but may + // still do the wrong thing. + if (Navigator.of(context, + rootNavigator: + widget.actionButtons.useRootNavigator) + .canPop()) { + Navigator.of(context, rootNavigator: widget.actionButtons.useRootNavigator) - .canPop()) { - Navigator.of(context, - rootNavigator: - widget.actionButtons.useRootNavigator) - .pop(false); - } + .pop(false); } - : null, - toolIcons: widget.actionButtons, - copyPasteBehavior: widget.copyPasteBehavior, - enableTooltips: widget.enableTooltips, - ), - ), - // Show heading widget if we have one. - if (widget.heading != null) - Padding( - padding: EdgeInsets.only(bottom: widget.columnSpacing), - child: widget.heading, + } + : null, + toolIcons: widget.actionButtons, + copyPasteBehavior: widget.copyPasteBehavior, + enableTooltips: widget.enableTooltips, ), - // Show picker selector, if more than one picker is enabled. - if (_usePickerSelector) - Focus( - focusNode: _pickerFocusNode, - onKey: (FocusNode node, RawKeyEvent event) { - _handleKeyEvent(event); - return KeyEventResult.ignored; + ), + // Show heading widget if we have one. + if (widget.heading != null) + Padding( + padding: EdgeInsets.only(bottom: widget.columnSpacing), + child: widget.heading, + ), + // Show picker selector, if more than one picker is enabled. + if (_usePickerSelector) + Focus( + focusNode: _pickerFocusNode, + child: SelectPicker( + pickers: _pickers, + pickerLabels: _pickerLabels, + picker: _activePicker, + onPickerChanged: (ColorPickerType value) { + // If there is no color in the picker that will grab + // focus when we move to the picker, then the picker + // selector itself will keep focus, and kick the key + // events (copy/paste) to the handler, we can thus use + // CTRL-C/V also when picker selector has focus. + _pickerFocusNode.requestFocus(); + setState(() { + _activePicker = value; + // If there is a color indicator, it will grab focus. + _selectedShouldFocus = true; + // If we are on the wheel no shades selection is not + // enabled, then the wheel will grab the focus. + if (_activePicker == ColorPickerType.wheel && + !widget.enableShadesSelection && + !widget.enableTonalPalette) { + _wheelShouldFocus = true; + } + _tonalOperated = false; + _updateActiveSwatch(); + }); }, - child: SelectPicker( - pickers: _pickers, - pickerLabels: _pickerLabels, - picker: _activePicker, - onPickerChanged: (ColorPickerType value) { - // If there is no color in the picker that will grab - // focus when we move to the picker, then the picker - // selector itself will keep focus, and kick the key - // events (copy/paste) to the handler, we can thus use - // CTRL-C/V also when picker selector has focus. - _pickerFocusNode.requestFocus(); + thumbColor: widget.selectedPickerTypeColor, + textStyle: widget.pickerTypeTextStyle, + columnSpacing: widget.columnSpacing, + ), + ), + // Add a tiny bit of extra hard coded space after the picker + // type selector if there was one. + if (_usePickerSelector) const SizedBox(height: 4), + // This is not the Wheel case, so we draw all the main colors + // for the active swatch list. + if (_activePicker != ColorPickerType.wheel) + MainColors( + spacing: widget.spacing, + runSpacing: widget.runSpacing, + columnSpacing: widget.columnSpacing, + activeColorSwatchList: _activeColorSwatchList, + selectedColor: + widget.enableOpacity ? _selectedColor : _tappedColor, + onSelectColor: (Color color) { + _tonalOperated = false; + _onSelectColor(color); + }, + includeIndex850: widget.includeIndex850, + width: widget.width, + height: widget.height, + borderRadius: widget.borderRadius, + hasBorder: widget.hasBorder, + borderColor: widget.borderColor, + elevation: widget.elevation, + selectedColorIcon: widget.selectedColorIcon, + selectedRequestsFocus: _selectedShouldFocus, + ), + // This is the wheel case, draw the custom ColorWheelPicker. + if (_activePicker == ColorPickerType.wheel) + Padding( + padding: EdgeInsets.only(bottom: widget.columnSpacing), + child: SizedBox( + height: widget.wheelDiameter, + width: widget.wheelDiameter, + child: ColorWheelPicker( + color: _selectedColor, + wheelWidth: widget.wheelWidth, + wheelSquarePadding: widget.wheelSquarePadding, + wheelSquareBorderRadius: widget.wheelSquareBorderRadius, + hasBorder: widget.wheelHasBorder, + borderColor: widget.borderColor, + shouldUpdate: _wheelShouldUpdate, + shouldRequestsFocus: _wheelShouldFocus, + onChangeStart: (Color color) { + widget.onColorChangeStart + ?.call(color.withOpacity(_opacity)); + _addToRecentColors(color.withOpacity(_opacity)); + }, + onChanged: (Color color) { setState(() { - _activePicker = value; - // If there is a color indicator, it will grab focus. - _selectedShouldFocus = true; - // If we are on the wheel no shades selection is not - // enabled, then the wheel will grab the focus. - if (_activePicker == ColorPickerType.wheel && - !widget.enableShadesSelection && - !widget.enableTonalPalette) { - _wheelShouldFocus = true; - } + _tappedColor = color; + _selectedColor = color; + _wheelShouldUpdate = false; + _editShouldUpdate = true; + _tonalShouldUpdate = true; _tonalOperated = false; + _selectedShouldFocus = true; + _wheelShouldFocus = false; _updateActiveSwatch(); }); + widget + .onColorChanged(_selectedColor.withOpacity(_opacity)); + }, + onChangeEnd: (Color color) { + widget.onColorChangeEnd?.call( + color.withOpacity(_opacity), + ); + }, + onWheel: (bool value) { + setState(() { + _onWheel = value; + }); }, - thumbColor: widget.selectedPickerTypeColor, - textStyle: widget.pickerTypeTextStyle, - columnSpacing: widget.columnSpacing, ), ), - // Add a tiny bit of extra hard coded space after the picker - // type selector if there was one. - if (_usePickerSelector) const SizedBox(height: 4), - // This is not the Wheel case, so we draw all the main colors - // for the active swatch list. - if (_activePicker != ColorPickerType.wheel) - MainColors( - spacing: widget.spacing, - runSpacing: widget.runSpacing, - columnSpacing: widget.columnSpacing, - activeColorSwatchList: _activeColorSwatchList, - selectedColor: _selectedColor.withAlpha(0xFF), - onSelectColor: (Color color) { - _tonalOperated = false; - _onSelectColor(color); - }, - includeIndex850: widget.includeIndex850, - width: widget.width, - height: widget.height, - borderRadius: widget.borderRadius, - hasBorder: widget.hasBorder, - borderColor: widget.borderColor, - elevation: widget.elevation, - selectedColorIcon: widget.selectedColorIcon, - selectedRequestsFocus: _selectedShouldFocus, - ), - // This is the wheel case, draw the custom ColorWheelPicker. - if (_activePicker == ColorPickerType.wheel) - Padding( - padding: EdgeInsets.only(bottom: widget.columnSpacing), - child: SizedBox( - height: widget.wheelDiameter, - width: widget.wheelDiameter, - child: ColorWheelPicker( + ), + // Show the sub-heading for the none wheel case. + if (widget.subheading != null && + widget.enableShadesSelection && + _activePicker != ColorPickerType.wheel) + Padding( + padding: EdgeInsets.only(bottom: widget.columnSpacing), + child: widget.subheading, + ), + // Show the sub-heading for the wheel case. + if (widget.wheelSubheading != null && + widget.enableShadesSelection && + _activePicker == ColorPickerType.wheel) + Padding( + padding: EdgeInsets.only(bottom: widget.columnSpacing), + child: widget.wheelSubheading, + ), + // Draw the shade colors for the selected main color. + if (widget.enableShadesSelection) + ShadeColors( + spacing: widget.spacing, + runSpacing: widget.runSpacing, + columnSpacing: widget.shadesSpacing ?? widget.columnSpacing, + activeSwatch: _activeSwatch!, + selectedColor: + widget.enableOpacity ? _selectedColor : _tappedColor, + onSelectColor: (Color color) { + _tonalOperated = false; + _onSelectColor(color); + if (_activePicker == ColorPickerType.wheel) { + setState(() { + _selectedShouldFocus = true; + _tonalShouldUpdate = true; + }); + } + }, + includeIndex850: widget.includeIndex850, + width: widget.width, + height: widget.height, + borderRadius: widget.borderRadius, + hasBorder: widget.hasBorder, + borderColor: widget.borderColor, + elevation: widget.elevation, + selectedColorIcon: widget.selectedColorIcon, + selectedRequestsFocus: _selectedShouldFocus, + ), + // Show the tonal sub-heading + if (widget.tonalSubheading != null && widget.enableTonalPalette) + Padding( + padding: EdgeInsets.only(bottom: widget.columnSpacing), + child: widget.tonalSubheading, + ), + // Draw the tonal palette. + if (widget.enableTonalPalette) ...[ + TonalPaletteColors( + spacing: widget.spacing, + runSpacing: widget.runSpacing, + selectedColor: _tappedColor, + onSelectColor: (Color color) { + _tonalOperated = true; + _onSelectColor(color); + setState(() { + _selectedShouldFocus = true; + _tonalShouldUpdate = false; + }); + }, + tonalShouldUpdate: _tonalShouldUpdate, + width: widget.tonalColorSameSize + ? widget.width + : (widget.width + widget.spacing) * 10 / 15 - + widget.spacing, + height: widget.tonalColorSameSize + ? widget.width + : (widget.width + widget.spacing) * 10 / 15 - + widget.spacing, + borderRadius: widget.borderRadius, + hasBorder: widget.hasBorder, + borderColor: widget.borderColor, + elevation: widget.elevation, + selectedColorIcon: widget.selectedColorIcon, + selectedRequestsFocus: _selectedShouldFocus, + ), + SizedBox(height: widget.columnSpacing), + ], + // Show the sub-heading for the opacity control. + if (widget.opacitySubheading != null && widget.enableOpacity) + Padding( + padding: EdgeInsets.only(bottom: widget.columnSpacing), + child: widget.opacitySubheading, + ), + // Draw the opacity slider if enabled. + if (widget.enableOpacity) + Padding( + padding: EdgeInsets.only(bottom: widget.columnSpacing), + child: SizedBox( + width: (widget.opacityTrackWidth ?? 0) < 150 + ? double.infinity + : widget.opacityTrackWidth, + child: RepaintBoundary( + child: OpacitySlider( color: _selectedColor.withAlpha(0xFF), - wheelWidth: widget.wheelWidth, - wheelSquarePadding: widget.wheelSquarePadding, - wheelSquareBorderRadius: widget.wheelSquareBorderRadius, - hasBorder: widget.wheelHasBorder, - borderColor: widget.borderColor, - shouldUpdate: _wheelShouldUpdate, - shouldRequestsFocus: _wheelShouldFocus, - onChangeStart: (Color color) { - widget.onColorChangeStart - ?.call(color.withOpacity(_opacity)); - _addToRecentColors(color.withOpacity(_opacity)); + opacity: _opacity, + trackHeight: widget.opacityTrackHeight, + thumbRadius: widget.opacityThumbRadius, + focusNode: _opacityFocusNode, + onChangeStart: (double value) { + if (widget.onColorChangeStart != null) { + // Request focus when change starts. + _opacityFocusNode.requestFocus(); + setState(() { + _fromInternal = true; + _opacity = value; + }); + widget.onColorChangeStart!( + _selectedColor.withOpacity(_opacity)); + _addToRecentColors( + _selectedColor.withOpacity(_opacity)); + } }, - onChanged: (Color color) { + onChanged: (double value) { setState(() { - _selectedColor = color.withOpacity(_opacity); + _fromInternal = true; + _opacity = value; _wheelShouldUpdate = false; _editShouldUpdate = true; - _tonalShouldUpdate = true; - _tonalOperated = false; _selectedShouldFocus = true; _wheelShouldFocus = false; - _updateActiveSwatch(); }); - widget.onColorChanged(_selectedColor); + widget.onColorChanged( + _selectedColor.withOpacity(_opacity)); }, - onChangeEnd: (Color color) { - widget.onColorChangeEnd?.call( - color.withOpacity(_opacity), - ); - }, - onWheel: (bool value) { - setState(() { - _onWheel = value; - }); + onChangeEnd: (double value) { + if (widget.onColorChangeEnd != null) { + setState(() { + _fromInternal = true; + _opacity = value; + }); + widget.onColorChangeEnd!( + _selectedColor.withOpacity(_opacity)); + // _addToRecentColors( + // _selectedColor.withOpacity(_opacity)); + } }, ), ), ), - // Show the sub-heading for the none wheel case. - if (widget.subheading != null && - widget.enableShadesSelection && - _activePicker != ColorPickerType.wheel) - Padding( - padding: EdgeInsets.only(bottom: widget.columnSpacing), - child: widget.subheading, - ), - // Show the sub-heading for the wheel case. - if (widget.wheelSubheading != null && - widget.enableShadesSelection && - _activePicker == ColorPickerType.wheel) - Padding( - padding: EdgeInsets.only(bottom: widget.columnSpacing), - child: widget.wheelSubheading, - ), - // Draw the shade colors for the selected main color. - if (widget.enableShadesSelection) - ShadeColors( - spacing: widget.spacing, - runSpacing: widget.runSpacing, - columnSpacing: widget.columnSpacing, - activeSwatch: _activeSwatch!, - selectedColor: _selectedColor.withAlpha(0xFF), - onSelectColor: (Color color) { - _tonalOperated = false; - _onSelectColor(color); - if (_activePicker == ColorPickerType.wheel) { - setState(() { - _selectedShouldFocus = true; - _tonalShouldUpdate = true; - }); - } - }, - includeIndex850: widget.includeIndex850, - width: widget.width, - height: widget.height, - borderRadius: widget.borderRadius, - hasBorder: widget.hasBorder, - borderColor: widget.borderColor, - elevation: widget.elevation, - selectedColorIcon: widget.selectedColorIcon, - selectedRequestsFocus: _selectedShouldFocus, - ), - // Show the tonal sub-heading - if (widget.tonalSubheading != null && widget.enableTonalPalette) - Padding( - padding: EdgeInsets.only(bottom: widget.columnSpacing), - child: widget.tonalSubheading, - ), - // Draw the tonal palette. - if (widget.enableTonalPalette) - TonalPaletteColors( - spacing: widget.spacing, - runSpacing: widget.runSpacing, - columnSpacing: widget.columnSpacing, - selectedColor: _selectedColor.withAlpha(0xFF), - onSelectColor: (Color color) { - _tonalOperated = true; - _onSelectColor(color); - setState(() { - _selectedShouldFocus = true; - _tonalShouldUpdate = false; - }); - }, - tonalShouldUpdate: _tonalShouldUpdate, - width: widget.tonalColorSameSize - ? widget.width - : (widget.width + widget.spacing) * 10 / 15 - - widget.spacing, - height: widget.tonalColorSameSize - ? widget.width - : (widget.width + widget.spacing) * 10 / 15 - - widget.spacing, - borderRadius: widget.borderRadius, - hasBorder: widget.hasBorder, - borderColor: widget.borderColor, - elevation: widget.elevation, - selectedColorIcon: widget.selectedColorIcon, - selectedRequestsFocus: _selectedShouldFocus, - ), - // Show the sub-heading for the opacity control. - if (widget.opacitySubheading != null && widget.enableOpacity) - Padding( - padding: EdgeInsets.only(bottom: widget.columnSpacing), - child: widget.opacitySubheading, - ), - // Draw the opacity slider if enabled. - if (widget.enableOpacity) - Padding( - padding: EdgeInsets.only(bottom: widget.columnSpacing), - child: SizedBox( - width: (widget.opacityTrackWidth ?? 0) < 150 - ? double.infinity - : widget.opacityTrackWidth, - child: RepaintBoundary( - child: OpacitySlider( - color: _selectedColor.withAlpha(0xFF), - opacity: _opacity, - trackHeight: widget.opacityTrackHeight, - thumbRadius: widget.opacityThumbRadius, - focusNode: _opacityFocusNode, - onChangeStart: (double value) { - if (widget.onColorChangeStart != null) { - // Request focus when change starts. - _opacityFocusNode.requestFocus(); - setState(() { - _opacity = value; - _selectedColor = - _selectedColor.withOpacity(_opacity); - }); - widget.onColorChangeStart!(_selectedColor); - } - _addToRecentColors(_selectedColor); - }, - onChanged: (double value) { + ), + // If we show material or generic name, we enclose them in a + // Wrap, they will be on same row nicely if there is room + // enough, but also wrap to two rows when so needed when both + // are shown at the same and they don't fit on one row. + if (widget.showMaterialName || widget.showColorName) + Wrap( + children: [ + // Show the Material color name, if enabled. + if (widget.showMaterialName) + Padding( + padding: EdgeInsets.only(bottom: widget.columnSpacing), + child: Text( + ColorTools.materialName( + _selectedColor.withAlpha(0xFF), + colorSwatchNameMap: + widget.customColorSwatchesAndNames, + ), + style: effectiveMaterialNameStyle, + ), + ), + // If we show both material and generic name, add some + // hard coded horizontal space between them. + if (widget.showMaterialName && widget.showColorName) + const SizedBox(width: 8), + // Show the generic color name, if enabled. + if (widget.showColorName) + Padding( + padding: EdgeInsets.only(bottom: widget.columnSpacing), + child: Text( + ColorTools.nameThatColor( + _selectedColor.withAlpha(0xFF)), + style: effectiveGenericNameStyle, + ), + ), + ], + ), + // If we show color code or its int value, we enclose them in a + // Wrap, they will be on same row nicely if there is room enough + // but also wrap to two rows when so needed when both are + // shown at the same and they don't fit on one row. + if (widget.showColorCode || widget.showColorValue) + Padding( + padding: EdgeInsets.only(bottom: widget.columnSpacing), + child: Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + runAlignment: WrapAlignment.center, + alignment: WrapAlignment.center, + children: [ + // Show the color code view and edit field, if enabled. + if (widget.showColorCode) + ColorCodeField( + color: widget.enableOpacity + ? _selectedColor.withOpacity(_opacity) + : _tappedColor, + readOnly: _activePicker != ColorPickerType.wheel || + widget.colorCodeReadOnly, + textStyle: widget.colorCodeTextStyle, + prefixStyle: widget.colorCodePrefixStyle, + colorCodeHasColor: widget.colorCodeHasColor, + enableTooltips: widget.enableTooltips, + shouldUpdate: _editShouldUpdate, + onColorChanged: (Color color) { + widget.onColorChangeStart + ?.call(_selectedColor.withOpacity(_opacity)); setState(() { - _opacity = value; - _selectedColor = - _selectedColor.withOpacity(_opacity); - _wheelShouldUpdate = false; - _editShouldUpdate = true; - _selectedShouldFocus = true; - _wheelShouldFocus = false; + _tappedColor = color; + _selectedColor = color; + // Color changed outside wheel picker, when the + // code was edited, the wheel should update. + _wheelShouldUpdate = true; + _editShouldUpdate = false; + _tonalOperated = false; + _updateActiveSwatch(); }); - widget.onColorChanged(_selectedColor); + widget.onColorChanged( + _selectedColor.withOpacity(_opacity)); + widget.onColorChangeEnd + ?.call(_selectedColor.withOpacity(_opacity)); + _addToRecentColors( + _selectedColor.withOpacity(_opacity)); }, - onChangeEnd: (double value) { - if (widget.onColorChangeEnd != null) { - setState(() { - _opacity = value; - _selectedColor = - _selectedColor.withOpacity(_opacity); - }); - widget.onColorChangeEnd!(_selectedColor); - } + onEditFocused: (bool editInFocus) { + setState(() { + _editCodeFocused = editInFocus; + if (_editCodeFocused) { + _selectedShouldFocus = false; + _wheelShouldFocus = false; + } + }); }, + toolIcons: widget.actionButtons, + copyPasteBehavior: widget.copyPasteBehavior, ), - ), - ), - ), - // If we show material or generic name, we enclose them in a - // Wrap, they will be on same row nicely if there is room - // enough, but also wrap to two rows when so needed when both - // are shown at the same and they don't fit on one row. - if (widget.showMaterialName || widget.showColorName) - Wrap( - children: [ - // Show the Material color name, if enabled. - if (widget.showMaterialName) - Padding( - padding: EdgeInsets.only(bottom: widget.columnSpacing), - child: Text( - ColorTools.materialName( - _selectedColor.withAlpha(0xFF), - colorSwatchNameMap: - widget.customColorSwatchesAndNames, - ), - style: effectiveMaterialNameStyle, - ), - ), - // If we show both material and generic name, add some - // hard coded horizontal space between them. - if (widget.showMaterialName && widget.showColorName) + // If we show both hex code and int value, add some + // hardcoded horizontal space between them. + if (widget.showColorCode && widget.showColorValue) const SizedBox(width: 8), - // Show the generic color name, if enabled. - if (widget.showColorName) - Padding( - padding: EdgeInsets.only(bottom: widget.columnSpacing), - child: Text( - ColorTools.nameThatColor( - _selectedColor.withAlpha(0xFF)), - style: effectiveGenericNameStyle, - ), + if (widget.showColorValue) + SelectableText( + _selectedColor.value.toString(), + style: effectiveCodeStyle, ), ], ), - // If we show color code or its int value, we enclose them in a - // Wrap, they will be on same row nicely if there is room enough - // but also wrap to two rows when so needed when both are - // shown at the same and they don't fit on one row. - if (widget.showColorCode || widget.showColorValue) - Padding( - padding: EdgeInsets.only(bottom: widget.columnSpacing), - child: Wrap( - crossAxisAlignment: WrapCrossAlignment.center, - runAlignment: WrapAlignment.center, - alignment: WrapAlignment.center, - children: [ - // Show the color code view and edit field, if enabled. - if (widget.showColorCode) - ColorCodeField( - color: _selectedColor, - readOnly: _activePicker != ColorPickerType.wheel || - widget.colorCodeReadOnly, - textStyle: widget.colorCodeTextStyle, - prefixStyle: widget.colorCodePrefixStyle, - colorCodeHasColor: widget.colorCodeHasColor, - enableTooltips: widget.enableTooltips, - shouldUpdate: _editShouldUpdate, - onColorChanged: (Color color) { - widget.onColorChangeStart?.call(_selectedColor); - setState(() { - _selectedColor = color.withOpacity(_opacity); - // Color changed outside wheel picker, when the - // code was edited, the wheel should update. - _wheelShouldUpdate = true; - _editShouldUpdate = false; - _tonalOperated = false; - _updateActiveSwatch(); - }); - widget.onColorChanged(_selectedColor); - widget.onColorChangeEnd?.call(_selectedColor); - _addToRecentColors(color); - }, - onEditFocused: (bool editInFocus) { - setState(() { - _editCodeFocused = editInFocus; - if (_editCodeFocused) { - _selectedShouldFocus = false; - _wheelShouldFocus = false; - } - }); - }, - toolIcons: widget.actionButtons, - copyPasteBehavior: widget.copyPasteBehavior, - ), - // If we show both hex code and int value, add some - // hardcoded horizontal space between them. - if (widget.showColorCode && widget.showColorValue) - const SizedBox(width: 8), - if (widget.showColorValue) - SelectableText( - _selectedColor.value.toString(), - style: effectiveCodeStyle, - ), - ], - ), - ), - // Show the sub-heading for recent colors. - if (widget.recentColorsSubheading != null && - widget.showRecentColors) - Padding( - padding: EdgeInsets.only(bottom: widget.columnSpacing), - child: widget.recentColorsSubheading, - ), - // Show the color indicators for the recently used colors. - if (widget.showRecentColors) - RecentColors( - spacing: widget.spacing, - runSpacing: widget.runSpacing, - columnSpacing: widget.columnSpacing, - recentColors: _recentColors, - selectedColor: _selectedColor, - onSelectColor: (Color color) { - _onSelectColor(color, keepOpacity: true, findPicker: true); - }, - includeIndex850: widget.includeIndex850, - width: widget.width, - height: widget.height, - borderRadius: widget.borderRadius, - hasBorder: widget.hasBorder, - borderColor: widget.borderColor, - elevation: widget.elevation, - selectedColorIcon: widget.selectedColorIcon, - selectedRequestsFocus: _selectedShouldFocus, - ), - ], - ), + ), + // Show the sub-heading for recent colors. + if (widget.recentColorsSubheading != null && + widget.showRecentColors) + Padding( + padding: EdgeInsets.only(bottom: widget.columnSpacing), + child: widget.recentColorsSubheading, + ), + // Show the color indicators for the recently used colors. + if (widget.showRecentColors) + RecentColors( + spacing: widget.spacing, + runSpacing: widget.runSpacing, + recentColors: _recentColors, + selectedColor: _tappedColor, + onSelectColor: (Color color) { + _tonalOperated = false; + _onSelectColor(color, findPicker: true); + }, + includeIndex850: widget.includeIndex850, + width: widget.width, + height: widget.height, + borderRadius: widget.borderRadius, + hasBorder: widget.hasBorder, + borderColor: widget.borderColor, + elevation: widget.elevation, + selectedColorIcon: widget.selectedColorIcon, + selectedRequestsFocus: _selectedShouldFocus, + ), + if (widget.showRecentColors) SizedBox(height: widget.columnSpacing), + ], ), ), ); @@ -2048,25 +2235,37 @@ class _ColorPickerState extends State { // A color was selected by tapping it, update state and notify via callbacks. void _onSelectColor( Color color, { - // Set to true to use opacity in `color` and let it replace _opacity. - // Done when selecting colors in recently used colors, otherwise not. - bool keepOpacity = false, // Normally when colors are selected, we do not need find the picker as they // are in the same picker. However, recently used colors sets this to true // as its colors can be in any picker, so it must be found. bool findPicker = false, }) { + // Any callbacks that are called here, should set the _fromInternal flag. + _fromInternal = true; + + // If findPicker is true we came from RecentColors, it is the only one that + // uses this flag. If we use Recent colors, we should also extract the + // opacity from the selected color, and set it as the current opacity. + if (findPicker) { + _opacity = color.opacity; + } // Call start callback with current selectedColor before change. - widget.onColorChangeStart?.call(_selectedColor); - // Add the previously selected color to recent colors. - _addToRecentColors(_selectedColor); + if (widget.enableOpacity) { + widget.onColorChangeStart?.call(_selectedColor.withOpacity(_opacity)); + _addToRecentColors(_selectedColor.withOpacity(_opacity)); + } else { + // This is to allow custom colors with opacity to callback with their + // in-built opacity, when opacity is control is not enabled. + widget.onColorChangeStart?.call(_tappedColor); + _addToRecentColors(_tappedColor); + } // update the state of the selectedColor to the new selected color. setState(() { - // Set selected color to the new value. - _selectedColor = keepOpacity ? color : color.withOpacity(_opacity); - if (keepOpacity && widget.enableOpacity) { - _opacity = _selectedColor.opacity; - } + // Set selected color to the one that was "clicked" + _tappedColor = color; + _selectedColor = _tappedColor.withAlpha(0xFF); + if (_debug) debugPrint('_onSelectColor _tappedColor: $_tappedColor'); + if (_debug) debugPrint('_onSelectColor _selectedColor: $_selectedColor'); // When the a color was clicked and selected, the right item is already // focused an other selected color indicators and wheel should not focus. _selectedShouldFocus = false; @@ -2082,7 +2281,7 @@ class _ColorPickerState extends State { if (findPicker) { // Make a swatch of the selected color in the wheel. _typeToSwatchMap[ColorPickerType.wheel] = >[ - ColorTools.createPrimarySwatch(_selectedColor.withAlpha(0xFF)) + ColorTools.createPrimarySwatch(_selectedColor) ]; _findPicker(); } @@ -2090,18 +2289,29 @@ class _ColorPickerState extends State { // of the current selected swatch, then update the active swatch. // This check eliminates a rebuild when the selected color is a member // of the currently displayed color swatch. + // Needed to update the active swatch when opacity is used on custom + // colors when opacity is not enabled. if ((_activePicker != ColorPickerType.wheel || !ColorTools.swatchContainsColor( _activeSwatch!, _selectedColor)) && !_tonalOperated) { // Update the active swatch to match the selected color. + if (_debug) { + debugPrint('**** _onSelectColor calls _updateActiveSwatch ****'); + } _updateActiveSwatch(); } }); // Call the change call back with the new color. - widget.onColorChanged(_selectedColor); - // We have and end callback, call it with the new color. - widget.onColorChangeEnd?.call(_selectedColor); + if (widget.enableOpacity) { + widget.onColorChanged(_selectedColor.withOpacity(_opacity)); + widget.onColorChangeEnd?.call(_selectedColor.withOpacity(_opacity)); + } else { + // This is to allow custom colors with opacity to callback with their + // in-built opacity, when opacity is control is not enabled. + widget.onColorChanged(_tappedColor); + widget.onColorChangeEnd?.call(_tappedColor); + } } void _addToRecentColors(Color color) { @@ -2123,67 +2333,6 @@ class _ColorPickerState extends State { widget.onRecentColorsChanged?.call(_recentColors); } - // Handle the keyboard events from the RawKeyboardListener. - void _handleKeyEvent(RawKeyEvent event) { - // If edit color code is focused and we do not use the parsed paste feature, - // we exit out of here. The TextField's normal paste action will then handle - // the paste as before in v1.x and normally in a TextField. - if (_editCodeFocused && !widget.copyPasteBehavior.editUsesParsedPaste) { - return; - } - // Make an OS independent copy/paste shortcut modifier key. - // - // Found the usage of these in the SDK TextField copy/paste implementation. - final bool isRawKeyMacOS = event.data is RawKeyEventDataMacOs; - if (_debug) { - debugPrint('KeyEvent isRawKeyMacOS: $isRawKeyMacOS'); - } - final bool isRawKeyIos = event.data is RawKeyEventDataIos; - // **BUT** - // The above RawKeyEventData did not seem to work on Web when using an - // iPad+Safari and an Apple 10.5 Pro iPad keyboard, isRawKeyIos did not - // become true! - // So CMD modifier did not get used, only CTRL worked. We can use context - // based Theme.platform instead here and skip RawKeyEventData, or just - // combine it with RawKeyEventData. Since we have a context it works too. - final TargetPlatform platform = Theme.of(context).platform; - // Should COMMAND modifier be used instead of CTRL for keyboard COPY-PASTE? - // Use all sources we have to determine if it is iOS or macOS that should - // use CMD for copy/paste instead of CTRL. - final bool useCommandModifier = isRawKeyMacOS || - isRawKeyIos || - platform == TargetPlatform.iOS || - platform == TargetPlatform.macOS; - - // isModifierPressed will be true when COMMAND key is pressed on macOS/iOS - // OR when CTRL key is pressed on other platforms. - final bool isModifierPressed = - useCommandModifier ? event.isMetaPressed : event.isControlPressed; - - // The raw keyboard listener reacts to both up and down events, we only use - // down, so that we only execute the copy and paste keyboard command once - // when the keys are pressed down. We do not want to do it 2nd time when - // the key goes up. - if (event.runtimeType == RawKeyDownEvent) { - // If logical key is paste OR modifier+V and we use ctrlV paste behavior, - // we get the latest clipboard data. - if ((event.logicalKey == LogicalKeyboardKey.paste || - (isModifierPressed && - event.logicalKey == LogicalKeyboardKey.keyV)) && - widget.copyPasteBehavior.ctrlV) { - unawaited(_getClipboard()); - } - // If logical key is copy or modifier+C and we used ctrlC copy behavior, - // we set the current color to the clipboard data. - if ((event.logicalKey == LogicalKeyboardKey.copy || - (isModifierPressed && - event.logicalKey == LogicalKeyboardKey.keyC)) && - widget.copyPasteBehavior.ctrlC) { - unawaited(_setClipboard()); - } - } - } - // Get the current clipboard data. Try to parse it to a Color object. // If successful, set the current color to the resulting Color. Future _getClipboard() async { @@ -2208,17 +2357,18 @@ class _ColorPickerState extends State { // pasting when the edit field is not active. await Future.delayed(const Duration(milliseconds: 100)); setState(() { - // If opacity is not enabled, we remove any alpha from pasted colors. - _selectedColor = - widget.enableOpacity ? clipColor : clipColor.withAlpha(0xFF); - _opacity = widget.enableOpacity ? _selectedColor.opacity : 1; + // We always remove any alpha from pasted colors. + _selectedColor = clipColor.withAlpha(0xFF); + // If opacity is enabled, we capture the opacity from the pasted color. + _opacity = widget.enableOpacity ? clipColor.opacity : 1; + _addToRecentColors(_selectedColor.withOpacity(_opacity)); // Color changed outside wheel and edit field, a new shade or color was // selected outside the wheel and edit, they should update! _wheelShouldUpdate = true; _editShouldUpdate = true; // Make a swatch of the new via paste _selectedColor for the wheel. _typeToSwatchMap[ColorPickerType.wheel] = >[ - ColorTools.createPrimarySwatch(_selectedColor.withAlpha(0xFF)), + ColorTools.createPrimarySwatch(_selectedColor), ]; // Move the picker to the pasted color value and update active swatch. _findPicker(); @@ -2284,19 +2434,14 @@ class _ColorPickerState extends State { switch (widget.copyPasteBehavior.copyFormat) { case ColorPickerCopyFormat.dartCode: colorString = '0x${_selectedColor.hexAlpha}'; - break; case ColorPickerCopyFormat.hexRRGGBB: colorString = _selectedColor.hex; - break; case ColorPickerCopyFormat.hexAARRGGBB: colorString = _selectedColor.hexAlpha; - break; case ColorPickerCopyFormat.numHexRRGGBB: colorString = '#${_selectedColor.hex}'; - break; case ColorPickerCopyFormat.numHexAARRGGBB: colorString = '#${_selectedColor.hexAlpha}'; - break; } final ClipboardData data = ClipboardData(text: colorString); await Clipboard.setData(data); diff --git a/lib/src/color_tools.dart b/lib/src/color_tools.dart index 941382f..0d51904 100644 --- a/lib/src/color_tools.dart +++ b/lib/src/color_tools.dart @@ -280,16 +280,17 @@ class ColorTools { /// https://material.io/resources/color/#!/?view.left=0&view.right=0&primary.color=6002ee static MaterialColor createPrimarySwatch(Color color) { final Map swatch = {}; + final int a = color.alpha; final int r = color.red; final int g = color.green; final int b = color.blue; for (final int strength in _indexPrimary) { final double ds = 0.5 - strength / 1000; - swatch[strength] = Color.fromRGBO( + swatch[strength] = Color.fromARGB( + a, r + ((ds < 0 ? r : (255 - r)) * ds).round(), g + ((ds < 0 ? g : (255 - g)) * ds).round(), b + ((ds < 0 ? b : (255 - b)) * ds).round(), - 1, ); } // The above gives a starting point, this tunes it a bit better, still far @@ -451,16 +452,17 @@ class ColorTools { /// for higher indexes. static MaterialAccentColor createAccentSwatch(Color color) { final Map swatch = {}; + final int a = color.alpha; final int r = color.red; final int g = color.green; final int b = color.blue; for (final int strength in _indexAccent) { final double ds = 0.2 - strength / 1000; - swatch[strength] = Color.fromRGBO( + swatch[strength] = Color.fromARGB( + a, r + ((ds < 0 ? r : (255 - r)) * ds).round(), g + ((ds < 0 ? g : (255 - g)) * ds).round(), - b + ((ds < 0 ? b : (255 - b)) * ds).round(), - 1); + b + ((ds < 0 ? b : (255 - b)) * ds).round()); } // The above gives a starting point, this tunes it a bit better, still far // from the real algorithm. diff --git a/lib/src/functions/picker_functions.dart b/lib/src/functions/picker_functions.dart index ac49f53..81a53fc 100644 --- a/lib/src/functions/picker_functions.dart +++ b/lib/src/functions/picker_functions.dart @@ -94,6 +94,20 @@ ColorPickerType findColorInSelector({ } } } + // If we did not find the color, we try once more with any opacity. + for (final ColorPickerType key in typeToSwatchMap.keys) { + if (pickersEnabled[key]!) { + for (final ColorSwatch swatch in typeToSwatchMap[key]!) { + if (lookInShades) { + if (isShadeOfMain(swatch, color.withAlpha(0xFF), include850)) { + return key; + } + } else { + if (swatch.value == color.withAlpha(0xFF).value) return key; + } + } + } + } // If we did not find the color in any of the swatches in the selector, we // will just return the first swatch available in the selector. for (final ColorPickerType key in typeToSwatchMap.keys) { @@ -113,6 +127,24 @@ ColorSwatch? findColorSwatch( } } return (color is ColorSwatch && swatches.contains(color)) ? color : null; + + // final ColorSwatch? foundSwatch = + // (color is ColorSwatch && swatches.contains(color)) ? color : null; + // + // if (foundSwatch != null) { + // debugPrint('findColorSwatch $foundSwatch'); + // return foundSwatch; + // } + // + // // We did not find any matching color. We try with no opacity, as well. + // for (final ColorSwatch mainColor in swatches) { + // if (isShadeOfMain(mainColor, color.withAlpha(0xFF), include850)) { + // return mainColor; + // } + // } + // return (color is ColorSwatch && swatches.contains(color.withAlpha(0xFF))) + // ? color + // : null; } /// Check if a given color is a shade of the main color, return true if it is. diff --git a/lib/src/models/color_picker_action_buttons.dart b/lib/src/models/color_picker_action_buttons.dart index cfb0537..70cad2f 100644 --- a/lib/src/models/color_picker_action_buttons.dart +++ b/lib/src/models/color_picker_action_buttons.dart @@ -12,6 +12,12 @@ enum ColorPickerActionButtonType { /// Use [ElevatedButton] button. elevated, + + /// Use [FilledButton] button. + filled, + + /// Use [FilledButton.tonal] button. + filledTonal, } /// Used to define the order of OK and Cancel buttons on the FlexColorPicker @@ -64,6 +70,7 @@ class ColorPickerActionButtons with Diagnosticable { this.splashRadius = 24, this.constraints = const BoxConstraints(minHeight: 42, minWidth: 42), this.dialogActionButtons = true, + this.dialogActionOnlyOkButton = false, this.dialogActionOrder = ColorPickerActionButtonOrder.okIsRight, this.dialogActionIcons = false, this.dialogCancelButtonLabel, @@ -212,6 +219,10 @@ class ColorPickerActionButtons with Diagnosticable { /// Defaults to true. final bool dialogActionButtons; + /// If set to true, the dialog will only have an OK button and no Cancel when + /// [dialogActionButtons] is true. + final bool dialogActionOnlyOkButton; + /// Defines the order of the OK and Cancel actions buttons at the bottom /// of the dialog. /// @@ -295,6 +306,7 @@ class ColorPickerActionButtons with Diagnosticable { double? splashRadius, BoxConstraints? constraints, bool? dialogActionButtons, + bool? dialogActionOnlyOkButton, ColorPickerActionButtonOrder? dialogActionOrder, bool? dialogActionIcons, String? dialogCancelButtonLabel, @@ -319,6 +331,8 @@ class ColorPickerActionButtons with Diagnosticable { splashRadius: splashRadius ?? this.splashRadius, constraints: constraints ?? this.constraints, dialogActionButtons: dialogActionButtons ?? this.dialogActionButtons, + dialogActionOnlyOkButton: + dialogActionOnlyOkButton ?? this.dialogActionOnlyOkButton, dialogActionOrder: dialogActionOrder ?? this.dialogActionOrder, dialogActionIcons: dialogActionIcons ?? this.dialogActionIcons, dialogCancelButtonLabel: @@ -352,6 +366,7 @@ class ColorPickerActionButtons with Diagnosticable { splashRadius == other.splashRadius && constraints == other.constraints && dialogActionButtons == other.dialogActionButtons && + dialogActionOnlyOkButton == other.dialogActionOnlyOkButton && dialogActionOrder == other.dialogActionOrder && dialogActionIcons == other.dialogActionIcons && dialogCancelButtonLabel == other.dialogCancelButtonLabel && @@ -378,6 +393,7 @@ class ColorPickerActionButtons with Diagnosticable { splashRadius, constraints, dialogActionButtons, + dialogActionOnlyOkButton, dialogActionOrder, dialogActionIcons, dialogCancelButtonLabel, @@ -410,6 +426,8 @@ class ColorPickerActionButtons with Diagnosticable { .add(DiagnosticsProperty('constraints', constraints)); properties.add( DiagnosticsProperty('dialogActionButtons', dialogActionButtons)); + properties.add(DiagnosticsProperty( + 'dialogActionOnlyOkButton', dialogActionOnlyOkButton)); properties.add(EnumProperty( 'dialogActionOrder', dialogActionOrder)); properties diff --git a/lib/src/models/color_picker_type.dart b/lib/src/models/color_picker_type.dart index 50ee139..ff796a4 100644 --- a/lib/src/models/color_picker_type.dart +++ b/lib/src/models/color_picker_type.dart @@ -17,6 +17,10 @@ enum ColorPickerType { /// swatches and a custom name for each color swatch. custom, + /// A secondary color picker that shows custom provided colors and their + /// material like swatches and a custom name for each color swatch. + customSecondary, + /// A HSV color wheel picker that can select any color. wheel, } diff --git a/lib/src/show_color_picker_dialog.dart b/lib/src/show_color_picker_dialog.dart index fc9c2b8..dc576a3 100644 --- a/lib/src/show_color_picker_dialog.dart +++ b/lib/src/show_color_picker_dialog.dart @@ -82,6 +82,18 @@ Future showColorPickerDialog( /// Defaults to 8 dp. Must be from 0 to 300 dp. double columnSpacing = 8, + /// Vertical spacing below the top toolbar header and action buttons. + /// + /// If not defined, defaults to [columnSpacing]. + /// Must be null or from 0 to 300 dp. + final double? toolbarSpacing, + + /// Vertical spacing below the material 2 based color shades palette. + /// + /// If not defined, defaults to [columnSpacing]. + /// Must be null or from 0 to 300 dp. + final double? shadesSpacing, + /// Enable the opacity control for the color value. /// /// Set to true to allow users to control the opacity value of the @@ -141,6 +153,19 @@ Future showColorPickerDialog( /// Defaults to 40 dp. Must be from 15 to 150 dp. double height = 40.0, + /// Set to true to make tonal color items same size as the size defined + /// for main and swatch shades indicator items. + /// + /// If false, the tonal color items will be smaller and auto sized for the + /// palette to be same width as the Material-2 Color palette. + /// + /// Defaults to false. The color boxes are smaller, but length of their + /// items is the same as MaterialColor swatch. You may prefer true to get + /// them to be same size, especially if you only use tonal palette. + /// + /// For legacy compatibility reasons, this property is false by default. + bool tonalColorSameSize = false, + /// The horizontal spacing between the color picker indicator items. /// /// Defaults to 4 dp. Must be from 0 to 50 dp. @@ -449,6 +474,18 @@ Future showColorPickerDialog( Map, String> customColorSwatchesAndNames = const , String>{}, + /// Color swatch to name map, with custom swatches and their names. + /// + /// Used to provide custom [ColorSwatch] objects to the custom color picker, + /// including their custom name label. These colors, their swatch shades + /// and names, are shown and used when the picker type + /// [ColorPickerType.customSecondary] option is enabled in the color picker. + /// + /// Defaults to an empty map. If the map is empty, the custom colors picker + /// will not be shown even if it is enabled in [pickersEnabled]. + Map, String> customSecondaryColorSwatchesAndNames = + const , String>{}, + // *************************************************************************** // Below properties that refer to the dialog // *************************************************************************** @@ -646,6 +683,8 @@ Future showColorPickerDialog( crossAxisAlignment: crossAxisAlignment, padding: padding, columnSpacing: columnSpacing, + toolbarSpacing: toolbarSpacing, + shadesSpacing: shadesSpacing, enableOpacity: enableOpacity, opacityTrackHeight: opacityTrackHeight, opacityTrackWidth: opacityTrackWidth, @@ -656,6 +695,7 @@ Future showColorPickerDialog( width: width, height: height, spacing: spacing, + tonalColorSameSize: tonalColorSameSize, runSpacing: runSpacing, elevation: elevation, hasBorder: hasBorder, @@ -690,6 +730,7 @@ Future showColorPickerDialog( pickerTypeTextStyle: pickerTypeTextStyle, pickerTypeLabels: pickerTypeLabels, customColorSwatchesAndNames: customColorSwatchesAndNames, + customSecondaryColorSwatchesAndNames: customSecondaryColorSwatchesAndNames, ).showPickerDialog( context, title: dialogTitle, diff --git a/lib/src/universal_widgets/context_popup_menu.dart b/lib/src/universal_widgets/context_popup_menu.dart index 61d81e9..df51496 100644 --- a/lib/src/universal_widgets/context_popup_menu.dart +++ b/lib/src/universal_widgets/context_popup_menu.dart @@ -8,7 +8,7 @@ import '../functions/picker_functions.dart'; /// Wrap a child with [ContextPopupMenu] and provide it a list of /// [PopupMenuEntry], typically it is a [PopupMenuItem] where each item have a /// unique value. Often the [PopupMenuItem] has a child of type [ListTile], with -/// and int as value for its list index. The child can also be a custom widget +/// an int as value for its list index. The child can also be a custom widget /// with any type of row content or even images, their values could be an /// enum for its selection as well. /// diff --git a/lib/src/universal_widgets/dry_intrisinic.dart b/lib/src/universal_widgets/dry_intrisinic.dart index ad98fb4..0551776 100644 --- a/lib/src/universal_widgets/dry_intrisinic.dart +++ b/lib/src/universal_widgets/dry_intrisinic.dart @@ -19,7 +19,7 @@ class DryIntrinsicWidth extends SingleChildRenderObjectWidget { /// Default const constructor. const DryIntrinsicWidth({ super.key, - required Widget super.child, + required super.child, }); @override @@ -55,7 +55,7 @@ class DryIntrinsicHeight extends SingleChildRenderObjectWidget { /// Default const constructor. const DryIntrinsicHeight({ super.key, - required Widget super.child, + required super.child, }); @override diff --git a/lib/src/widgets/color_code_field.dart b/lib/src/widgets/color_code_field.dart index ebd8a5f..a03bff9 100644 --- a/lib/src/widgets/color_code_field.dart +++ b/lib/src/widgets/color_code_field.dart @@ -310,19 +310,14 @@ class _ColorCodeFieldState extends State { switch (widget.copyPasteBehavior.copyFormat) { case ColorPickerCopyFormat.dartCode: colorString = '0x${color.hexAlpha}'; - break; case ColorPickerCopyFormat.hexRRGGBB: colorString = color.hex; - break; case ColorPickerCopyFormat.hexAARRGGBB: colorString = color.hexAlpha; - break; case ColorPickerCopyFormat.numHexRRGGBB: colorString = '#${color.hex}'; - break; case ColorPickerCopyFormat.numHexAARRGGBB: colorString = '#${color.hexAlpha}'; - break; } final ClipboardData data = ClipboardData(text: colorString); await Clipboard.setData(data); diff --git a/lib/src/widgets/context_copy_paste_menu.dart b/lib/src/widgets/context_copy_paste_menu.dart index df4432e..290388f 100644 --- a/lib/src/widgets/context_copy_paste_menu.dart +++ b/lib/src/widgets/context_copy_paste_menu.dart @@ -146,7 +146,6 @@ class ContextCopyPasteMenu extends StatelessWidget { @override Widget build(BuildContext context) { final ThemeData theme = Theme.of(context); - // This is a merge of provided menuThemeData, with surrounding theme, with // fallback to default values. final PopupMenuThemeData effectiveMenuTheme = theme.popupMenuTheme.copyWith( @@ -168,7 +167,6 @@ class ContextCopyPasteMenu extends StatelessWidget { theme.popupMenuTheme.enableFeedback ?? true, ); - // This is a merge of provided iconThemeData, with surrounding theme, with // fallback to default values, color has no default, remains as null. final IconThemeData effectiveIconTheme = theme.iconTheme.copyWith( @@ -176,10 +174,8 @@ class ContextCopyPasteMenu extends StatelessWidget { size: menuIconThemeData?.size ?? theme.iconTheme.size ?? 16, opacity: menuIconThemeData?.opacity ?? theme.iconTheme.opacity ?? 0.90, ); - // Get the Material localizations. final MaterialLocalizations translate = MaterialLocalizations.of(context); - return Theme( data: theme.copyWith( popupMenuTheme: effectiveMenuTheme, iconTheme: effectiveIconTheme), diff --git a/lib/src/widgets/copy_paste_handler.dart b/lib/src/widgets/copy_paste_handler.dart new file mode 100644 index 0000000..276797a --- /dev/null +++ b/lib/src/widgets/copy_paste_handler.dart @@ -0,0 +1,183 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +import '../universal_widgets/if_wrapper.dart'; +import 'context_copy_paste_menu.dart'; + +/// A function that returns a future with no return value. +/// +/// Used as signature for the clipboard actions function. +typedef VoidClipboard = Future Function(); + +/// Manages the keyboard shortcuts for the color picker. +class CopyPasteHandler extends StatelessWidget { + /// Manages the keyboard shortcuts for the color picker. + const CopyPasteHandler({ + super.key, + required this.pasteFromClipboard, + required this.copyToClipboard, + required this.useContextMenu, + required this.useLongPress, + required this.useSecondaryTapDown, + required this.useSecondaryOnDesktopLongOnDevice, + required this.useSecondaryOnDesktopLongOnDeviceAndWeb, + required this.onCopyPasteMenuOpened, + required this.focusNode, + required this.autoFocus, + required this.child, + }); + + /// A function that is called when the paste action is invoked. + /// + /// The function should paste from the clipboard, including any custom + /// logic needed. + final VoidClipboard pasteFromClipboard; + + /// A function that is called when the copy action is invoked. + /// + /// The function should copy from the clipboard, including any custom + /// logic needed. + final VoidClipboard copyToClipboard; + + /// The child that will use the keyboard shortcuts. + /// + /// Should wrap the entire color picker. + final Widget child; + + /// Use the context menu for copy and paste. + final bool useContextMenu; + + /// Use long press for copy and paste. + final bool useLongPress; + + /// Use secondary tap down for copy and paste. + final bool useSecondaryTapDown; + + /// Use secondary on desktop and long press on device for copy and paste. + final bool useSecondaryOnDesktopLongOnDevice; + + /// Use secondary on desktop and long press on device and web for copy + /// and paste. + final bool useSecondaryOnDesktopLongOnDeviceAndWeb; + + /// A function that is called when the copy paste menu is opened. + final VoidCallback onCopyPasteMenuOpened; + + /// A focus node that will be used to focus the child. + final FocusNode focusNode; + + /// A boolean that determines if the child should be focused. + final bool autoFocus; + + @override + Widget build(BuildContext context) { + final TargetPlatform platform = Theme.of(context).platform; + final bool useMetaCommand = + platform == TargetPlatform.iOS || platform == TargetPlatform.macOS; + return Shortcuts( + shortcuts: { + if (useMetaCommand) + LogicalKeySet(LogicalKeyboardKey.meta, LogicalKeyboardKey.keyC): + const CopyIntent() + else + LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyC): + const CopyIntent(), + if (useMetaCommand) + LogicalKeySet(LogicalKeyboardKey.meta, LogicalKeyboardKey.keyV): + const PasteIntent() + else + LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyC): + const PasteIntent(), + }, + child: Actions( + actions: >{ + PasteIntent: PasteAction(pasteFromClipboard), + CopyIntent: CopyAction(copyToClipboard), + }, + child: Builder(builder: (BuildContext context) { + return Focus( + focusNode: focusNode, + autofocus: autoFocus, + canRequestFocus: true, + child: GestureDetector( + behavior: HitTestBehavior.translucent, + onTap: focusNode.requestFocus, + child: IfWrapper( + condition: useContextMenu, + builder: (BuildContext context, Widget child) { + return ContextCopyPasteMenu( + useLongPress: useLongPress, + useSecondaryTapDown: useSecondaryTapDown, + useSecondaryOnDesktopLongOnDevice: + useSecondaryOnDesktopLongOnDevice, + useSecondaryOnDesktopLongOnDeviceAndWeb: + useSecondaryOnDesktopLongOnDeviceAndWeb, + onSelected: (CopyPasteCommands? value) { + if (value == CopyPasteCommands.copy) { + unawaited(copyToClipboard()); + } + if (value == CopyPasteCommands.paste) { + unawaited(pasteFromClipboard()); + } + }, + onOpen: onCopyPasteMenuOpened, + child: child, + ); + }, + child: child), + )); + }), + ), + ); + } +} + +/// An intent that is bound to PasteAction to paste from clipboard. +class PasteIntent extends Intent { + /// An intent that is bound to PasteAction to paste from clipboard. + const PasteIntent(); +} + +/// An action that is bound to PasteIntent. +class PasteAction extends Action { + /// An action that is bound to PasteIntent. + PasteAction(this.pasteFromClipboard); + + /// A function that is called when the paste action is invoked. + /// + /// The function should paste from the clipboard, including any custom + /// logic needed. + final VoidClipboard pasteFromClipboard; + + @override + Object? invoke(covariant PasteIntent intent) async { + await pasteFromClipboard.call(); + return KeyEventResult.handled; + } +} + +/// An intent that is bound to CopyAction to copy to ClipBoard. +class CopyIntent extends Intent { + /// An intent that is bound to CopyAction to copy to ClipBoard. + const CopyIntent(); +} + +/// An action that is bound to CopyIntent. +class CopyAction extends Action { + /// An action that is bound to CopyIntent. + CopyAction(this.copyToClipboard); + + /// A function that is called when the copy action is invoked. + /// + /// The function should copy from the clipboard, including any custom + /// logic needed. + final VoidClipboard copyToClipboard; + + @override + Object? invoke(covariant CopyIntent intent) async { + await copyToClipboard.call(); + return KeyEventResult.handled; + } +} diff --git a/lib/src/widgets/opacity/opacity_slider_track.dart b/lib/src/widgets/opacity/opacity_slider_track.dart index c16a865..217f4fb 100644 --- a/lib/src/widgets/opacity/opacity_slider_track.dart +++ b/lib/src/widgets/opacity/opacity_slider_track.dart @@ -1,5 +1,6 @@ import 'dart:math' as math; import 'dart:ui' as ui; + import 'package:flutter/material.dart'; /// A custom slider track for the opacity slider. @@ -127,11 +128,9 @@ class OpacitySliderTrack extends SliderTrackShape { case TextDirection.ltr: leftTrackPaint = activePaint; rightTrackPaint = inactivePaint; - break; case TextDirection.rtl: leftTrackPaint = inactivePaint; rightTrackPaint = activePaint; - break; } final RRect shapeRect = ui.RRect.fromLTRBAndCorners( diff --git a/lib/src/widgets/picker_selector.dart b/lib/src/widgets/picker_selector.dart index a3c1e62..d513887 100644 --- a/lib/src/widgets/picker_selector.dart +++ b/lib/src/widgets/picker_selector.dart @@ -128,6 +128,17 @@ class SelectPicker extends StatelessWidget { : segmentTextStyle, ), ), + if (pickers[ColorPickerType.customSecondary]!) + ColorPickerType.customSecondary: Padding( + padding: const EdgeInsets.all(5), + child: Text( + pickerLabels[ColorPickerType.customSecondary] ?? '', + textAlign: TextAlign.center, + style: picker == ColorPickerType.customSecondary + ? segmentTextStyle.copyWith(color: effectiveThumbOnColor) + : segmentTextStyle, + ), + ), if (pickers[ColorPickerType.wheel]!) ColorPickerType.wheel: Padding( padding: const EdgeInsets.all(5), diff --git a/lib/src/widgets/recent_colors.dart b/lib/src/widgets/recent_colors.dart index 906b1e6..4302685 100644 --- a/lib/src/widgets/recent_colors.dart +++ b/lib/src/widgets/recent_colors.dart @@ -13,7 +13,6 @@ class RecentColors extends StatelessWidget { Key? key, required this.spacing, required this.runSpacing, - required this.columnSpacing, required this.recentColors, required this.selectedColor, required this.onSelectColor, @@ -34,12 +33,6 @@ class RecentColors extends StatelessWidget { /// The run spacing between the color pick items when wrapped on several rows. final double runSpacing; - /// The spacing after the main colors. - final double columnSpacing; - - // /// The currently active used list of color swatches we select color from. - // final List> activeColorSwatchList; - /// List of recently selected colors. final List recentColors; @@ -86,31 +79,28 @@ class RecentColors extends StatelessWidget { @override Widget build(BuildContext context) { final double effectiveBorderRadius = borderRadius ?? width / 4.0; - return Padding( - padding: EdgeInsets.only(bottom: columnSpacing), - child: Wrap( - spacing: spacing, - runSpacing: runSpacing, - children: [ - for (final Color color in recentColors) - ColorIndicator( - isSelected: - selectedColor == color || selectedColor.value == color.value, - color: color, - width: width, - height: height, - borderRadius: effectiveBorderRadius, - hasBorder: hasBorder, - borderColor: borderColor, - elevation: elevation, - selectedIcon: selectedColorIcon, - onSelect: () { - onSelectColor(color); - }, - selectedRequestsFocus: selectedRequestsFocus, - ), - ], - ), + return Wrap( + spacing: spacing, + runSpacing: runSpacing, + children: [ + for (final Color color in recentColors) + ColorIndicator( + isSelected: + selectedColor == color || selectedColor.value == color.value, + color: color, + width: width, + height: height, + borderRadius: effectiveBorderRadius, + hasBorder: hasBorder, + borderColor: borderColor, + elevation: elevation, + selectedIcon: selectedColorIcon, + onSelect: () { + onSelectColor(color); + }, + selectedRequestsFocus: selectedRequestsFocus, + ), + ], ); } } diff --git a/lib/src/widgets/tonal_palette_colors.dart b/lib/src/widgets/tonal_palette_colors.dart index 4721631..8bb524f 100644 --- a/lib/src/widgets/tonal_palette_colors.dart +++ b/lib/src/widgets/tonal_palette_colors.dart @@ -14,7 +14,6 @@ class TonalPaletteColors extends StatefulWidget { Key? key, required this.spacing, required this.runSpacing, - required this.columnSpacing, required this.selectedColor, required this.onSelectColor, required this.tonalShouldUpdate, @@ -34,9 +33,6 @@ class TonalPaletteColors extends StatefulWidget { /// The run spacing between the color pick items when wrapped on several rows. final double runSpacing; - /// The spacing after the main colors. - final double columnSpacing; - /// The selected color. final Color selectedColor; @@ -102,31 +98,28 @@ class _TonalPaletteColorsState extends State { Widget build(BuildContext context) { final double effectiveBorderRadius = widget.borderRadius ?? widget.width / 4.0; - return Padding( - padding: EdgeInsets.only(bottom: widget.columnSpacing), - child: Wrap( - spacing: widget.spacing, - runSpacing: widget.runSpacing, - children: [ - for (final Color color in tonalColors) - ColorIndicator( - isSelected: widget.selectedColor == color || - widget.selectedColor.value == color.value, - color: color, - width: widget.width, - height: widget.height, - borderRadius: effectiveBorderRadius, - hasBorder: widget.hasBorder, - borderColor: widget.borderColor, - elevation: widget.elevation, - selectedIcon: widget.selectedColorIcon, - onSelect: () { - widget.onSelectColor(color); - }, - selectedRequestsFocus: widget.selectedRequestsFocus, - ), - ], - ), + return Wrap( + spacing: widget.spacing, + runSpacing: widget.runSpacing, + children: [ + for (final Color color in tonalColors) + ColorIndicator( + isSelected: widget.selectedColor == color || + widget.selectedColor.value == color.value, + color: color, + width: widget.width, + height: widget.height, + borderRadius: effectiveBorderRadius, + hasBorder: widget.hasBorder, + borderColor: widget.borderColor, + elevation: widget.elevation, + selectedIcon: widget.selectedColorIcon, + onSelect: () { + widget.onSelectColor(color); + }, + selectedRequestsFocus: widget.selectedRequestsFocus, + ), + ], ); } } diff --git a/pubspec.lock b/pubspec.lock index ccf4476..9a37b6a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,26 +5,26 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: "405666cd3cf0ee0a48d21ec67e65406aad2c726d9fa58840d3375e7bdcd32a07" + sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7" url: "https://pub.dev" source: hosted - version: "60.0.0" + version: "67.0.0" analyzer: dependency: transitive description: name: analyzer - sha256: "1952250bd005bacb895a01bf1b4dc00e3ba1c526cf47dca54dfe24979c65f5b3" + sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d" url: "https://pub.dev" source: hosted - version: "5.12.0" + version: "6.4.1" args: dependency: transitive description: name: args - sha256: c372bb384f273f0c2a8aaaa226dad84dc27c8519a691b888725dec59518ad53a + sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.2" async: dependency: transitive description: @@ -77,10 +77,10 @@ packages: dependency: transitive description: name: coverage - sha256: "2fb815080e44a09b85e0f2ca8a820b15053982b2e714b59267719e8a9ff17097" + sha256: "8acabb8306b57a409bf4c83522065672ee13179297a6bb0cb9ead73948df7c76" url: "https://pub.dev" source: hosted - version: "1.6.3" + version: "1.7.2" crypto: dependency: transitive description: @@ -101,18 +101,10 @@ packages: dependency: transitive description: name: file - sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" url: "https://pub.dev" source: hosted - version: "6.1.4" - fixnum: - dependency: transitive - description: - name: fixnum - sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" - url: "https://pub.dev" - source: hosted - version: "1.1.0" + version: "7.0.0" flex_seed_scheme: dependency: "direct main" description: @@ -143,10 +135,10 @@ packages: dependency: transitive description: name: glob - sha256: "4515b5b6ddb505ebdd242a5f2cc5d22d3d6a80013789debfbda7777f47ea308c" + sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" http_multi_server: dependency: transitive description: @@ -179,46 +171,70 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.7" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" + url: "https://pub.dev" + source: hosted + version: "10.0.0" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 + url: "https://pub.dev" + source: hosted + version: "2.0.1" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 + url: "https://pub.dev" + source: hosted + version: "2.0.1" logging: dependency: transitive description: name: logging - sha256: "04094f2eb032cbb06c6f6e8d3607edcfcb0455e2bb6cbc010cb01171dcb64e6d" + sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.2.0" matcher: dependency: transitive description: name: matcher - sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb url: "https://pub.dev" source: hosted - version: "0.12.16" + version: "0.12.16+1" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" url: "https://pub.dev" source: hosted - version: "0.5.0" + version: "0.8.0" meta: dependency: transitive description: name: meta - sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e + sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.11.0" mime: dependency: transitive description: name: mime - sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e + sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.0.5" node_preamble: dependency: transitive description: @@ -239,18 +255,18 @@ packages: dependency: transitive description: name: path - sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" url: "https://pub.dev" source: hosted - version: "1.8.3" + version: "1.9.0" patrol_finders: dependency: "direct dev" description: name: patrol_finders - sha256: "909a5c28ba1730e5e1e06e06ec78f4ccc49f7e211f3a0919adac552841df19ee" + sha256: ac33527cc1b63e3aa131dbd7107cfda8ee2df0fb4a4a423c067174a2e60db77b url: "https://pub.dev" source: hosted - version: "2.0.1+1" + version: "2.0.2" pool: dependency: transitive description: @@ -259,14 +275,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.1" - protobuf: - dependency: transitive - description: - name: protobuf - sha256: "01dd9bd0fa02548bf2ceee13545d4a0ec6046459d847b6b061d8a27237108a08" - url: "https://pub.dev" - source: hosted - version: "2.1.0" pub_semver: dependency: transitive description: @@ -412,42 +420,42 @@ packages: dependency: transitive description: name: vm_service - sha256: "1311639ad908dfdcb64734ac1fbd60416234c06e4351846783a79c56848a5185" + sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 url: "https://pub.dev" source: hosted - version: "11.7.0" + version: "13.0.0" watcher: dependency: transitive description: name: watcher - sha256: "6a7f46926b01ce81bfc339da6a7f20afbe7733eff9846f6d6a5466aa4c6667c0" + sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" url: "https://pub.dev" source: hosted - version: "1.0.2" + version: "1.1.0" web: dependency: transitive description: name: web - sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 + sha256: "1d9158c616048c38f712a6646e317a3426da10e884447626167240d45209cbad" url: "https://pub.dev" source: hosted - version: "0.3.0" + version: "0.5.0" web_socket_channel: dependency: transitive description: name: web_socket_channel - sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b + sha256: "1d8e795e2a8b3730c41b8a98a2dff2e0fb57ae6f0764a1c46ec5915387d257b2" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.4.4" webkit_inspection_protocol: dependency: transitive description: name: webkit_inspection_protocol - sha256: "67d3a8b6c79e1987d19d848b0892e582dbb0c66c57cc1fef58a177dd2aa2823d" + sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.2.1" yaml: dependency: transitive description: @@ -457,5 +465,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.2.0 <4.0.0" + dart: ">=3.3.0 <4.0.0" flutter: ">=3.16.0" diff --git a/pubspec.yaml b/pubspec.yaml index c9862ae..da2b606 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -24,8 +24,8 @@ topics: - rgb environment: - sdk: '>=2.19.0 <4.0.0' - flutter: '>=3.7.0' + sdk: '>=3.0.0 <4.0.0' + flutter: '>=3.16.0' dependencies: # FlexSeedScheme package, by Mike Rydstrom, rydmike.com (@rydmike). diff --git a/test/color_picker_action_buttons_test.dart b/test/color_picker_action_buttons_test.dart index ec9f0cc..81aed30 100644 --- a/test/color_picker_action_buttons_test.dart +++ b/test/color_picker_action_buttons_test.dart @@ -77,6 +77,7 @@ void main() { splashRadius: 30, constraints: BoxConstraints(minHeight: 46, minWidth: 46), dialogActionButtons: false, + dialogActionOnlyOkButton: true, dialogActionOrder: ColorPickerActionButtonOrder.adaptive, dialogActionIcons: true, dialogCancelButtonLabel: 'DONE', @@ -91,7 +92,7 @@ void main() { 'EXPECT exact print string value.', () { expect(m4.toString(), equalsIgnoringHashCodes( // ignore: lines_longer_than_80_chars - 'ColorPickerActionButtons#00000(okButton: true, closeButton: true, okIcon: IconData(U+0E085), closeIcon: IconData(U+0E139), closeIsLast: false, okTooltip: "GO", closeTooltip: "OFF", closeTooltipIsClose: false, toolIconsThemeData: IconThemeData#00000(size: 30.0, color: Color(0xff000000), opacity: 1.0), visualDensity: VisualDensity#00000(h: -1.0, v: -1.0)(horizontal: -1.0, vertical: -1.0), alignment: Alignment.topLeft, splashRadius: 30.0, constraints: BoxConstraints(46.0<=w<=Infinity, 46.0<=h<=Infinity), dialogActionButtons: false, dialogActionOrder: adaptive, dialogActionIcons: true, dialogCancelButtonLabel: "DONE", dialogCancelButtonType: outlined, dialogOkButtonLabel: "OKAY", dialogOkButtonType: elevated, useRootNavigator: false)')); + 'ColorPickerActionButtons#00000(okButton: true, closeButton: true, okIcon: IconData(U+0E085), closeIcon: IconData(U+0E139), closeIsLast: false, okTooltip: "GO", closeTooltip: "OFF", closeTooltipIsClose: false, toolIconsThemeData: IconThemeData#00000(size: 30.0, color: Color(0xff000000), opacity: 1.0), visualDensity: VisualDensity#00000(h: -1.0, v: -1.0)(horizontal: -1.0, vertical: -1.0), alignment: Alignment.topLeft, splashRadius: 30.0, constraints: BoxConstraints(46.0<=w<=Infinity, 46.0<=h<=Infinity), dialogActionButtons: false, dialogActionOnlyOkButton: true, dialogActionOrder: adaptive, dialogActionIcons: true, dialogCancelButtonLabel: "DONE", dialogCancelButtonType: outlined, dialogOkButtonLabel: "OKAY", dialogOkButtonType: elevated, useRootNavigator: false)')); }); test( 'CPAB2.2: Test toStringShort implemented via debugFillProperties ' @@ -133,6 +134,7 @@ void main() { splashRadius: 30, constraints: const BoxConstraints(minHeight: 46, minWidth: 46), dialogActionButtons: false, + dialogActionOnlyOkButton: true, dialogActionOrder: ColorPickerActionButtonOrder.adaptive, dialogActionIcons: true, dialogCancelButtonLabel: 'DONE', @@ -181,6 +183,7 @@ void main() { splashRadius: null, constraints: null, dialogActionButtons: null, + dialogActionOnlyOkButton: null, dialogActionOrder: null, dialogActionIcons: null, dialogCancelButtonLabel: null, @@ -212,6 +215,7 @@ void main() { splashRadius: 30, constraints: const BoxConstraints(minHeight: 46, minWidth: 46), dialogActionButtons: false, + dialogActionOnlyOkButton: true, dialogActionOrder: ColorPickerActionButtonOrder.adaptive, dialogActionIcons: true, dialogCancelButtonLabel: 'DONE', @@ -244,6 +248,7 @@ void main() { splashRadius: null, constraints: null, dialogActionButtons: null, + dialogActionOnlyOkButton: null, dialogActionOrder: null, dialogActionIcons: null, dialogCancelButtonLabel: null, diff --git a/test/color_picker_patrol_test.dart b/test/color_picker_patrol_test.dart index 3b9413a..4b39245 100644 --- a/test/color_picker_patrol_test.dart +++ b/test/color_picker_patrol_test.dart @@ -15,7 +15,7 @@ import 'package:patrol_finders/patrol_finders.dart'; void main() { const ValueKey testKey = ValueKey('test'); - group('PAT1: Default ColorPicker', () { + group('PAT1: Patrol Finder ColorPicker Tests', () { debugDefaultTargetPlatformOverride = null; patrolWidgetTest( @@ -23,7 +23,7 @@ void main() { (PatrolTester $) async { Color resultColor = Colors.blue; await $.pumpWidgetAndSettle( - TestWidget( + TestPicker( widget: ColorPicker( key: testKey, onColorChanged: (Color color) { @@ -69,7 +69,7 @@ void main() { List recentColors = []; await $.pumpWidgetAndSettle( - TestWidget( + TestPicker( widget: ColorPicker( key: testKey, color: Colors.red, // Primary picker s default selected @@ -88,6 +88,7 @@ void main() { ColorPickerType.accent: false, ColorPickerType.bw: true, ColorPickerType.custom: true, + ColorPickerType.customSecondary: true, ColorPickerType.wheel: true, }, includeIndex850: true, @@ -99,6 +100,16 @@ void main() { closeButton: true, okTooltip: 'OK NOW', closeTooltip: 'CLOSE NOW', + toolIconsThemeData: IconThemeData( + color: Colors.red, + size: 20, + opacity: 0.88, + ), + visualDensity: VisualDensity.comfortable, + padding: EdgeInsets.all(2), + splashRadius: 20, + dialogActionButtons: false, + dialogActionOnlyOkButton: true, ), copyPasteBehavior: const ColorPickerCopyPasteBehavior( copyButton: true, @@ -139,6 +150,13 @@ void main() { ColorTools.createAccentSwatch(const Color(0xFF03DAC6)): 'Guide Teal', }, + customSecondaryColorSwatchesAndNames: , + String>{ + ColorTools.createPrimarySwatch(const Color(0xFF00EE4B)): + 'Option 1', + ColorTools.createPrimarySwatch(const Color(0xFF92B300)): + 'Option 2', + }, ), ), ); @@ -162,20 +180,20 @@ void main() { expect(endColor.value, Colors.redAccent.value); expect(resultColor.value, Colors.redAccent.value); // Test recent colors - expect(recentColors.contains(Colors.red), true); + expect(recentColors.contains(Color(Colors.red.value)), true); await $(ColorIndicator).at(34).tap(); expect(startColor.value, Colors.redAccent.value); expect(endColor.value, Colors.grey.value); expect(resultColor.value, Colors.grey.value); // Test recent colors - expect(recentColors.contains(Colors.red), true); + expect(recentColors.contains(Color(Colors.red.value)), true); expect(recentColors.contains(Color(Colors.redAccent.value)), true); await $(ColorIndicator).at(44).tap(); expect(startColor.value, Colors.grey.value); expect(endColor.value, Colors.grey[850]!.value); expect(resultColor.value, Colors.grey[850]!.value); // Test recent colors - expect(recentColors.contains(Colors.red), true); + expect(recentColors.contains(Color(Colors.red.value)), true); expect(recentColors.contains(Color(Colors.redAccent.value)), true); expect(recentColors.contains(Color(Colors.grey.value)), true); @@ -188,7 +206,7 @@ void main() { expect(endColor.value, ColorTools.blackShade[600]!.value); expect(resultColor.value, ColorTools.blackShade[600]!.value); // Test recent colors - expect(recentColors.contains(Colors.red), true); + expect(recentColors.contains(Color(Colors.red.value)), true); expect(recentColors.contains(Color(Colors.redAccent.value)), true); expect(recentColors.contains(Color(Colors.grey.value)), true); expect(recentColors.contains(Color(Colors.grey[850]!.value)), true); @@ -196,7 +214,8 @@ void main() { await $(ColorIndicator).at(1).tap(); expect(resultColor.value, ColorTools.whiteShade[500]!.value); // Test recent colors - expect(recentColors.contains(Colors.red), false); // 4 max, no red + expect(recentColors.contains(Color(Colors.red.value)), + false); // 4 max, no red expect(recentColors.contains(Color(Colors.redAccent.value)), true); expect(recentColors.contains(Color(Colors.grey.value)), true); expect(recentColors.contains(Color(Colors.grey[850]!.value)), true); @@ -215,6 +234,14 @@ void main() { expect(endColor, const Color(0xFF3700B3)); expect(resultColor, const Color(0xFF3700B3)); + // Test Option color picker. + expect(find.text('Option'), findsOneWidget); + await $('Option').tap(); + await $(ColorIndicator).at(1).tap(); + expect(startColor, const Color(0xFF3700B3)); + expect(endColor, const Color(0xFF92B300)); + expect(resultColor, const Color(0xFF92B300)); + // Test Wheel color picker. expect(find.text('Wheel'), findsOneWidget); await $('Wheel').tap(); @@ -222,18 +249,18 @@ void main() { expect(find.text('Tonal'), findsOneWidget); // The 10th ColorIndicator will be first tonal and always black. await $(ColorIndicator).at(10).tap(); - expect(startColor, const Color(0xFF3700B3)); + expect(startColor, const Color(0xFF92B300)); expect(endColor, Colors.black); expect(resultColor, Colors.black); await $(ColorIndicator).at(16).tap(); expect(startColor, Colors.black); - expect(endColor, const Color(0xFF775EF3)); - expect(resultColor, const Color(0xFF775EF3)); + expect(endColor, const Color(0xFF688000)); + expect(resultColor, const Color(0xFF688000)); // Find the ColorWheelPicker expect(find.byType(ColorWheelPicker), findsOneWidget); // Tap center of the ColorWheelPicker await $(ColorWheelPicker).tap(); - expect(resultColor, const Color(0xff4a4080)); + expect(resultColor, const Color(0xff748040)); // The 24th ColorIndicator will be last tonal and always white. await $(ColorIndicator).at(24).tap(); @@ -246,22 +273,23 @@ void main() { // Let's tap the last one, we need to scroll to it first. await $(RecentColors).$(ColorIndicator).at(3).scrollTo().tap(); // Result should be 5 result color from earlier above. - expect(resultColor, const Color(0xFF3700B3)); + expect(resultColor, const Color(0xff92b300)); // Find the OpacitySlider - expect(find.byType(OpacitySlider), findsOneWidget); // TODO(rydmike): Get the slider test working! - // expect(find.byType(Slider), findsOneWidget); - // expect($(OpacitySlider).$(Slider), findsOneWidget); + expect(find.byType(OpacitySlider), findsOneWidget); // expect(find.byType(Slider), findsOneWidget); // Tap center of the OpacitySlider - // await $(Slider).scrollTo().tap(); + // await $(OpacitySlider) + // .scrollTo(settlePolicy: SettlePolicy.trySettle) + // .tap(); // Find the Text entry expect(find.byType(ColorCodeField), findsOneWidget); expect(find.byType(TextField), findsOneWidget); // TODO(rydmike): Get the color code entry test working! - await $(find.byType(TextField)).enterText('613E42'); + // await $(find.byType(TextField)).enterText('613E42'); + // await $(find.byType(ColorCodeField)).enterText('613E42'); // expect(resultColor, const Color(0xFF613E42)); // Find the ToolBar @@ -278,16 +306,25 @@ void main() { // We should have some clipboard data from above tap. But the below // attempt to get the data never completes. The code flow hit paths // from above also indicates the buffer is empty after the tap above. - // final ClipboardData? clipData = // await Clipboard.getData(Clipboard.kTextPlain); // debugPrint('Clip data: $clipData'); + // TEST COPY/PASTE via toolbar buttons // Go to another tab select a new color await $('Primary & Accent').tap(); await $(ColorIndicator).at(1).tap(); expect(resultColor.value, Colors.redAccent.value); - // Paste in the color + // Copy in the redAccent color + await $(ColorPickerToolbar).$(IconButton).at(0).tap( + settlePolicy: SettlePolicy.trySettle, + visibleTimeout: const Duration(seconds: 1), + settleTimeout: const Duration(seconds: 2), + ); + // Select pink color + await $(ColorIndicator).at(2).tap(); + expect(resultColor.value, Colors.pink.value); + // Paste in the red accent value color await $(ColorPickerToolbar).$(IconButton).at(1).tap( settlePolicy: SettlePolicy.trySettle, visibleTimeout: const Duration(seconds: 1), @@ -295,7 +332,8 @@ void main() { ); // This is the color we should find but do not since copy did nothing. // The paste wont work either. - // expect(resultColor, const Color(0xFF3700B3)); + // expect(Color(resultColor.value), Color(Colors.redAccent.value)); + // Color(0xffe91e63); }, ); @@ -305,7 +343,7 @@ void main() { (PatrolTester $) async { Color resultColor = const Color(0xFF613E42); await $.pumpWidgetAndSettle( - TestWidget( + TestPicker( widget: ColorPicker( key: testKey, color: resultColor, @@ -330,6 +368,22 @@ void main() { ColorPickerType.custom: true, ColorPickerType.wheel: true, }, + actionButtons: const ColorPickerActionButtons( + okButton: false, + closeButton: true, + okTooltip: 'DO', + closeTooltipIsClose: false, + toolIconsThemeData: IconThemeData( + color: Colors.blue, + size: 20, + opacity: 0.95, + ), + visualDensity: VisualDensity.comfortable, + padding: EdgeInsets.all(2), + splashRadius: 20, + dialogActionButtons: true, + dialogActionOnlyOkButton: true, + ), ), ), ); @@ -356,18 +410,1195 @@ void main() { expect(resultColor.value, Colors.red[300]!.value); }, ); + + patrolWidgetTest( + 'PAT1.4: Patrol test dialog - default labels, text buttons-no-icons', + (PatrolTester $) async { + Color resultColor = Colors.orange; + await $.pumpWidgetAndSettle( + TestPicker( + widget: Builder( + builder: (BuildContext context) { + return ElevatedButton( + onPressed: () async { + resultColor = await showColorPickerDialog( + context, + resultColor, + pickersEnabled: const { + ColorPickerType.both: false, + ColorPickerType.primary: true, + ColorPickerType.accent: true, + ColorPickerType.bw: false, + ColorPickerType.custom: false, + ColorPickerType.wheel: false, + }, + includeIndex850: true, + crossAxisAlignment: CrossAxisAlignment.start, + enableTonalPalette: true, + enableOpacity: true, + actionButtons: const ColorPickerActionButtons( + okButton: true, + closeButton: true, + okTooltip: 'OK NOW', + closeTooltip: 'CLOSE NOW', + toolIconsThemeData: IconThemeData( + color: Colors.red, + size: 20, + opacity: 0.88, + ), + visualDensity: VisualDensity.comfortable, + padding: EdgeInsets.all(2), + splashRadius: 20, + dialogActionButtons: true, + // dialogActionOnlyOkButton: false, + ), + copyPasteBehavior: const ColorPickerCopyPasteBehavior( + copyButton: true, + pasteButton: true, + editFieldCopyButton: true, + ), + ); + }, + child: const Text('Open'), + ); + }, + ), + ), + ); + + // Test primary color picker. + expect(find.text('Open'), findsOneWidget); + await $('Open').tap(); + + expect(find.text('Primary'), findsOneWidget); + await $('Primary').tap(); + await $(ColorIndicator).tap(); + // expect(resultColor.value, Colors.red.value); + await $(ColorIndicator).at(2).tap(); + // expect(resultColor.value, Colors.purple.value); + await $(ColorIndicator).at(20).tap(); + // expect(resultColor.value, Colors.purple[100]!.value); + + // Test accent color picker. + expect(find.text('Accent'), findsOneWidget); + await $('Accent').tap(); + await $(ColorIndicator).tap(); + // expect(resultColor.value, Colors.redAccent.value); + await $(ColorIndicator).at(5).tap(); + // expect(resultColor.value, Colors.blueAccent.value); + await $(ColorIndicator).at(18).tap(); + + expect(find.byType(TextButton), findsExactly(2)); + await $(TextButton).at(1).tap(); + expect(Color(resultColor.value), Color(Colors.blueAccent[400]!.value)); + const Color(0xff2979ff); + const Color(0xffff9800); + + // Open dialog again + await $('Open').tap(); + await $('Primary').tap(); + await $(ColorIndicator).tap(); + // expect(resultColor.value, Colors.red.value); + await $(ColorIndicator).at(2).tap(); + // expect(resultColor.value, Colors.purple.value); + expect(find.text('OK'), findsOneWidget); + await $('OK').tap(); + expect(resultColor.value, Colors.purple.value); + + // Open dialog again + await $('Open').tap(); + await $('Primary').tap(); + await $(ColorIndicator).tap(); + // expect(resultColor.value, Colors.red.value); + expect(find.text('Cancel'), findsOneWidget); + await $('Cancel').tap(); + expect(resultColor.value, Colors.purple.value); + }, + ); + + patrolWidgetTest( + 'PAT1.4-icons: Patrol test dialog - default labels, text buttons-icons', + (PatrolTester $) async { + Color resultColor = Colors.orange; + await $.pumpWidgetAndSettle( + TestPicker( + widget: Builder( + builder: (BuildContext context) { + return ElevatedButton( + onPressed: () async { + resultColor = await showColorPickerDialog( + context, + resultColor, + pickersEnabled: const { + ColorPickerType.both: false, + ColorPickerType.primary: true, + ColorPickerType.accent: true, + ColorPickerType.bw: false, + ColorPickerType.custom: false, + ColorPickerType.wheel: false, + }, + includeIndex850: true, + crossAxisAlignment: CrossAxisAlignment.start, + enableTonalPalette: true, + enableOpacity: true, + actionButtons: const ColorPickerActionButtons( + okButton: true, + closeButton: true, + okTooltip: 'OK NOW', + closeTooltip: 'CLOSE NOW', + toolIconsThemeData: IconThemeData( + color: Colors.red, + size: 20, + opacity: 0.88, + ), + visualDensity: VisualDensity.comfortable, + padding: EdgeInsets.all(2), + splashRadius: 20, + dialogActionIcons: true, + dialogActionButtons: true, + // dialogActionOnlyOkButton: false, + ), + copyPasteBehavior: const ColorPickerCopyPasteBehavior( + copyButton: true, + pasteButton: true, + editFieldCopyButton: true, + ), + ); + }, + child: const Text('Open'), + ); + }, + ), + ), + ); + + // Test primary color picker. + expect(find.text('Open'), findsOneWidget); + await $('Open').tap(); + + expect(find.text('Primary'), findsOneWidget); + await $('Primary').tap(); + await $(ColorIndicator).tap(); + // expect(resultColor.value, Colors.red.value); + await $(ColorIndicator).at(2).tap(); + // expect(resultColor.value, Colors.purple.value); + await $(ColorIndicator).at(20).tap(); + // expect(resultColor.value, Colors.purple[100]!.value); + + // Test accent color picker. + expect(find.text('Accent'), findsOneWidget); + await $('Accent').tap(); + await $(ColorIndicator).tap(); + // expect(resultColor.value, Colors.redAccent.value); + await $(ColorIndicator).at(5).tap(); + // expect(resultColor.value, Colors.blueAccent.value); + await $(ColorIndicator).at(18).tap(); + + expect(find.text('OK'), findsOneWidget); + await $('OK').tap(); + expect(Color(resultColor.value), Color(Colors.blueAccent[400]!.value)); + const Color(0xff2979ff); + const Color(0xffff9800); + + // Open dialog again + await $('Open').tap(); + await $('Primary').tap(); + await $(ColorIndicator).tap(); + // expect(resultColor.value, Colors.red.value); + await $(ColorIndicator).at(2).tap(); + // expect(resultColor.value, Colors.purple.value); + expect(find.text('OK'), findsOneWidget); + await $('OK').tap(); + expect(resultColor.value, Colors.purple.value); + + // Open dialog again + await $('Open').tap(); + await $('Primary').tap(); + await $(ColorIndicator).tap(); + // expect(resultColor.value, Colors.red.value); + expect(find.text('Cancel'), findsOneWidget); + await $('Cancel').tap(); + expect(resultColor.value, Colors.purple.value); + }, + ); + + patrolWidgetTest( + 'PAT1.5: Patrol test dialog - custom labels, filled buttons-no-icons', + (PatrolTester $) async { + Color resultColor = Colors.blue; + await $.pumpWidgetAndSettle( + TestPicker( + widget: Builder( + builder: (BuildContext context) { + return ElevatedButton( + onPressed: () async { + resultColor = await showColorPickerDialog( + context, + resultColor, + pickersEnabled: const { + ColorPickerType.both: false, + ColorPickerType.primary: true, + ColorPickerType.accent: true, + ColorPickerType.bw: false, + ColorPickerType.custom: false, + ColorPickerType.wheel: false, + }, + columnSpacing: 4, + toolbarSpacing: 0, + shadesSpacing: 0, + includeIndex850: true, + crossAxisAlignment: CrossAxisAlignment.start, + enableTonalPalette: true, + enableOpacity: true, + actionButtons: const ColorPickerActionButtons( + okButton: true, + closeButton: true, + okTooltip: 'OK NOW', + closeTooltip: 'CLOSE NOW', + toolIconsThemeData: IconThemeData( + color: Colors.red, + size: 20, + opacity: 0.88, + ), + visualDensity: VisualDensity.comfortable, + padding: EdgeInsets.all(2), + splashRadius: 20, + dialogActionButtons: true, + // dialogActionOnlyOkButton: false, + dialogCancelButtonLabel: 'CLOSE', + dialogOkButtonLabel: 'USE', + dialogOkButtonType: ColorPickerActionButtonType.filled, + dialogCancelButtonType: + ColorPickerActionButtonType.filled, + ), + copyPasteBehavior: const ColorPickerCopyPasteBehavior( + copyButton: true, + pasteButton: true, + editFieldCopyButton: true, + feedbackParseError: true, + parseShortHexCode: true, + ), + ); + }, + child: const Text('Open'), + ); + }, + ), + ), + ); + + // Test primary color picker. + expect(find.text('Open'), findsOneWidget); + await $('Open').tap(); + + expect(find.text('Primary'), findsOneWidget); + await $('Primary').tap(); + await $(ColorIndicator).tap(); + // expect(resultColor.value, Colors.red.value); + await $(ColorIndicator).at(2).tap(); + // expect(resultColor.value, Colors.purple.value); + expect(find.text('USE'), findsOneWidget); + await $('USE').tap(); + expect(Color(resultColor.value), Color(Colors.purple.value)); + + // Open dialog again + await $('Open').tap(); + await $('Primary').tap(); + await $(ColorIndicator).tap(); + // expect(resultColor.value, Colors.red.value); + expect(find.text('CLOSE'), findsOneWidget); + await $('CLOSE').tap(); + expect(resultColor.value, Colors.purple.value); + }, + ); + + patrolWidgetTest( + 'PAT1.5-icon: Patrol test dialog - custom labels, filled buttons-icons', + (PatrolTester $) async { + Color resultColor = Colors.blue; + await $.pumpWidgetAndSettle( + TestPicker( + widget: Builder( + builder: (BuildContext context) { + return ElevatedButton( + onPressed: () async { + resultColor = await showColorPickerDialog( + context, + resultColor, + pickersEnabled: const { + ColorPickerType.both: false, + ColorPickerType.primary: true, + ColorPickerType.accent: true, + ColorPickerType.bw: false, + ColorPickerType.custom: false, + ColorPickerType.wheel: false, + }, + columnSpacing: 4, + toolbarSpacing: 0, + shadesSpacing: 0, + includeIndex850: true, + crossAxisAlignment: CrossAxisAlignment.start, + enableTonalPalette: true, + enableOpacity: true, + actionButtons: const ColorPickerActionButtons( + okButton: true, + closeButton: true, + okTooltip: 'OK NOW', + closeTooltip: 'CLOSE NOW', + toolIconsThemeData: IconThemeData( + color: Colors.red, + size: 20, + opacity: 0.88, + ), + visualDensity: VisualDensity.comfortable, + padding: EdgeInsets.all(2), + splashRadius: 20, + dialogActionButtons: true, + // dialogActionOnlyOkButton: false, + dialogCancelButtonLabel: 'CLOSE', + dialogOkButtonLabel: 'USE', + dialogActionIcons: true, + dialogOkButtonType: ColorPickerActionButtonType.filled, + dialogCancelButtonType: + ColorPickerActionButtonType.filled, + ), + copyPasteBehavior: const ColorPickerCopyPasteBehavior( + copyButton: true, + pasteButton: true, + editFieldCopyButton: true, + feedbackParseError: true, + parseShortHexCode: true, + ), + ); + }, + child: const Text('Open'), + ); + }, + ), + ), + ); + + // Test primary color picker. + expect(find.text('Open'), findsOneWidget); + await $('Open').tap(); + + expect(find.text('Primary'), findsOneWidget); + await $('Primary').tap(); + await $(ColorIndicator).tap(); + // expect(resultColor.value, Colors.red.value); + await $(ColorIndicator).at(2).tap(); + // expect(resultColor.value, Colors.purple.value); + expect(find.text('USE'), findsOneWidget); + await $('USE').tap(); + expect(Color(resultColor.value), Color(Colors.purple.value)); + + // Open dialog again + await $('Open').tap(); + await $('Primary').tap(); + await $(ColorIndicator).tap(); + // expect(resultColor.value, Colors.red.value); + expect(find.text('CLOSE'), findsOneWidget); + await $('CLOSE').tap(); + expect(resultColor.value, Colors.purple.value); + }, + ); + + patrolWidgetTest( + 'PAT1.6: Patrol test dialog - custom labels, filledTonal-no-icons', + (PatrolTester $) async { + Color resultColor = Colors.blue; + await $.pumpWidgetAndSettle( + TestPicker( + platform: TargetPlatform.windows, + widget: Builder( + builder: (BuildContext context) { + return ElevatedButton( + onPressed: () async { + resultColor = await showColorPickerDialog( + context, + resultColor, + pickersEnabled: const { + ColorPickerType.both: false, + ColorPickerType.primary: true, + ColorPickerType.accent: true, + ColorPickerType.bw: false, + ColorPickerType.custom: false, + ColorPickerType.wheel: false, + }, + includeIndex850: true, + crossAxisAlignment: CrossAxisAlignment.start, + enableTonalPalette: true, + enableOpacity: true, + actionButtons: const ColorPickerActionButtons( + okButton: true, + closeButton: true, + closeIsLast: false, + okTooltip: 'OK NOW', + closeTooltip: 'CLOSE NOW', + toolIconsThemeData: IconThemeData( + color: Colors.red, + size: 20, + opacity: 0.88, + ), + visualDensity: VisualDensity.comfortable, + padding: EdgeInsets.all(2), + splashRadius: 20, + dialogActionButtons: true, + dialogCancelButtonLabel: 'CLOSER', + dialogOkButtonLabel: 'USE', + dialogOkButtonType: + ColorPickerActionButtonType.filledTonal, + dialogCancelButtonType: + ColorPickerActionButtonType.filledTonal, + dialogActionOrder: + ColorPickerActionButtonOrder.adaptive, + ), + copyPasteBehavior: const ColorPickerCopyPasteBehavior( + copyButton: true, + pasteButton: true, + editFieldCopyButton: true, + feedbackParseError: true, + parseShortHexCode: true, + ), + ); + }, + child: const Text('Open'), + ); + }, + ), + ), + ); + + // Test primary color picker. + expect(find.text('Open'), findsOneWidget); + await $('Open').tap(); + + expect(find.text('Primary'), findsOneWidget); + await $('Primary').tap(); + await $(ColorIndicator).tap(); + // expect(resultColor.value, Colors.red.value); + await $(ColorIndicator).at(2).tap(); + // expect(resultColor.value, Colors.purple.value); + expect(find.text('USE'), findsOneWidget); + await $('USE').tap(); + expect(Color(resultColor.value), Color(Colors.purple.value)); + + // Dialog is closed + expect(find.text('Open'), findsOneWidget); + // Open dialog again + await $('Open').tap(); + // Close via Cancel button + expect(find.text('CLOSER'), findsOneWidget); + await $('CLOSER').tap(); + + // Open dialog again + await $('Open').tap(); + await $('Primary').tap(); + await $(ColorIndicator).tap(); + // expect(resultColor.value, Colors.red.value); + // Find the ToolBar + expect(find.byType(ColorPickerToolbar), findsOneWidget); + // Find the ToolBar buttons, 4 of them configured. + expect($(ColorPickerToolbar).$(IconButton), findsNWidgets(4)); + // Close via toolbar, 4th button is OK, since close is NOT last + await $(ColorPickerToolbar).$(IconButton).at(3).tap(); + expect(resultColor.value, Colors.red.value); + + // Dialog is closed + expect(find.text('Open'), findsOneWidget); + // Open dialog again + await $('Open').tap(); + await $('Primary').tap(); + await $(ColorIndicator).at(3).tap(); + // expect(resultColor.value, Colors.deepPurple.value); + // Find the ToolBar buttons, 4 of them configured. + expect($(ColorPickerToolbar).$(IconButton), findsNWidgets(4)); + // Close via toolbar, 3rd button is Close, since close is NOT last + await $(ColorPickerToolbar).$(IconButton).at(2).tap(); + // Dialog is closed + expect(find.text('Open'), findsOneWidget); + // Expect no change in color + expect(resultColor.value, Colors.red.value); + }, + ); + + patrolWidgetTest( + 'PAT1.6-icon: Patrol test dialog - custom labels, filledTonal-icons', + (PatrolTester $) async { + Color resultColor = Colors.blue; + await $.pumpWidgetAndSettle( + TestPicker( + platform: TargetPlatform.windows, + widget: Builder( + builder: (BuildContext context) { + return ElevatedButton( + onPressed: () async { + resultColor = await showColorPickerDialog( + context, + resultColor, + pickersEnabled: const { + ColorPickerType.both: false, + ColorPickerType.primary: true, + ColorPickerType.accent: true, + ColorPickerType.bw: false, + ColorPickerType.custom: false, + ColorPickerType.wheel: false, + }, + includeIndex850: true, + crossAxisAlignment: CrossAxisAlignment.start, + enableTonalPalette: true, + enableOpacity: true, + actionButtons: const ColorPickerActionButtons( + okButton: true, + closeButton: true, + closeIsLast: false, + okTooltip: 'OK NOW', + closeTooltip: 'CLOSE NOW', + toolIconsThemeData: IconThemeData( + color: Colors.red, + size: 20, + opacity: 0.88, + ), + visualDensity: VisualDensity.comfortable, + padding: EdgeInsets.all(2), + splashRadius: 20, + dialogActionButtons: true, + dialogActionIcons: true, + dialogCancelButtonLabel: 'CLOSER', + dialogOkButtonLabel: 'USE', + dialogOkButtonType: + ColorPickerActionButtonType.filledTonal, + dialogCancelButtonType: + ColorPickerActionButtonType.filledTonal, + dialogActionOrder: + ColorPickerActionButtonOrder.adaptive, + ), + copyPasteBehavior: const ColorPickerCopyPasteBehavior( + copyButton: true, + pasteButton: true, + editFieldCopyButton: true, + feedbackParseError: true, + parseShortHexCode: true, + ), + ); + }, + child: const Text('Open'), + ); + }, + ), + ), + ); + + // Test primary color picker. + expect(find.text('Open'), findsOneWidget); + await $('Open').tap(); + + expect(find.text('Primary'), findsOneWidget); + await $('Primary').tap(); + await $(ColorIndicator).tap(); + // expect(resultColor.value, Colors.red.value); + await $(ColorIndicator).at(2).tap(); + // expect(resultColor.value, Colors.purple.value); + expect(find.text('USE'), findsOneWidget); + await $('USE').tap(); + expect(Color(resultColor.value), Color(Colors.purple.value)); + + // Dialog is closed + expect(find.text('Open'), findsOneWidget); + // Open dialog again + await $('Open').tap(); + // Close via Cancel button + expect(find.text('CLOSER'), findsOneWidget); + await $('CLOSER').tap(); + + // Open dialog again + await $('Open').tap(); + await $('Primary').tap(); + await $(ColorIndicator).tap(); + // expect(resultColor.value, Colors.red.value); + // Find the ToolBar + expect(find.byType(ColorPickerToolbar), findsOneWidget); + // Find the ToolBar buttons, 4 of them configured. + expect($(ColorPickerToolbar).$(IconButton), findsNWidgets(4)); + // Close via toolbar, 4th button is OK, since close is NOT last + await $(ColorPickerToolbar).$(IconButton).at(3).tap(); + expect(resultColor.value, Colors.red.value); + + // Dialog is closed + expect(find.text('Open'), findsOneWidget); + // Open dialog again + await $('Open').tap(); + await $('Primary').tap(); + await $(ColorIndicator).at(3).tap(); + // expect(resultColor.value, Colors.deepPurple.value); + // Find the ToolBar buttons, 4 of them configured. + expect($(ColorPickerToolbar).$(IconButton), findsNWidgets(4)); + // Close via toolbar, 3rd button is Close, since close is NOT last + await $(ColorPickerToolbar).$(IconButton).at(2).tap(); + // Dialog is closed + expect(find.text('Open'), findsOneWidget); + // Expect no change in color + expect(resultColor.value, Colors.red.value); + }, + ); + + patrolWidgetTest( + 'PAT1.7: Patrol test dialog - custom labels, elevated-no-icon, ' + 'with constraints.', + (PatrolTester $) async { + Color resultColor = Colors.blue; + await $.pumpWidgetAndSettle( + TestPicker( + widget: Builder( + builder: (BuildContext context) { + return ElevatedButton( + onPressed: () async { + resultColor = await showColorPickerDialog( + context, + resultColor, + constraints: const BoxConstraints( + minHeight: 480, minWidth: 320, maxWidth: 320), + pickersEnabled: const { + ColorPickerType.both: false, + ColorPickerType.primary: true, + ColorPickerType.accent: true, + ColorPickerType.bw: false, + ColorPickerType.custom: false, + ColorPickerType.wheel: false, + }, + includeIndex850: true, + crossAxisAlignment: CrossAxisAlignment.start, + enableTonalPalette: true, + enableOpacity: true, + actionButtons: const ColorPickerActionButtons( + okButton: true, + closeButton: true, + okTooltip: 'OK NOW', + closeTooltip: 'CLOSE NOW', + toolIconsThemeData: IconThemeData( + color: Colors.red, + size: 20, + opacity: 0.88, + ), + visualDensity: VisualDensity.comfortable, + padding: EdgeInsets.all(2), + splashRadius: 20, + dialogActionButtons: true, + // dialogActionOnlyOkButton: false, + dialogCancelButtonLabel: 'CLOSE', + dialogOkButtonLabel: 'Do', + dialogOkButtonType: + ColorPickerActionButtonType.elevated, + dialogCancelButtonType: + ColorPickerActionButtonType.elevated, + dialogActionOrder: + ColorPickerActionButtonOrder.okIsRight, + ), + copyPasteBehavior: const ColorPickerCopyPasteBehavior( + copyButton: true, + pasteButton: true, + editFieldCopyButton: true, + feedbackParseError: true, + parseShortHexCode: true, + ), + ); + }, + child: const Text('Open'), + ); + }, + ), + ), + ); + + // Test primary color picker. + expect(find.text('Open'), findsOneWidget); + await $('Open').tap(); + + expect(find.text('Primary'), findsOneWidget); + await $('Primary').tap(); + await $(ColorIndicator).tap(); + // expect(resultColor.value, Colors.red.value); + await $(ColorIndicator).at(6).tap(); + // expect(resultColor.value, Colors.lightBlue.value); + expect(find.text('Do'), findsOneWidget); + await $('Do').tap(); + expect(Color(resultColor.value), Color(Colors.lightBlue.value)); + + // Dialog is closed + expect(find.text('Open'), findsOneWidget); + // Open dialog again + await $('Open').tap(); + // Close via Cancel button + expect(find.text('CLOSE'), findsOneWidget); + await $('CLOSE').tap(); + + // Open dialog again + await $('Open').tap(); + await $('Primary').tap(); + await $(ColorIndicator).tap(); + // expect(resultColor.value, Colors.red.value); + // Find the ToolBar + expect(find.byType(ColorPickerToolbar), findsOneWidget); + // Find the ToolBar buttons, 4 of them configured. + expect($(ColorPickerToolbar).$(IconButton), findsNWidgets(4)); + // Close via toolbar, 4th button is Close, since close is last + await $(ColorPickerToolbar).$(IconButton).at(3).tap(); + expect(resultColor.value, Colors.lightBlue.value); + + // Dialog is closed + expect(find.text('Open'), findsOneWidget); + // Open dialog again + await $('Open').tap(); + await $('Primary').tap(); + await $(ColorIndicator).at(3).tap(); + // expect(resultColor.value, Colors.deepPurple.value); + // Find the ToolBar buttons, 4 of them configured. + expect($(ColorPickerToolbar).$(IconButton), findsNWidgets(4)); + // Close via toolbar, 3rd button is OK, since close is NOT last + await $(ColorPickerToolbar).$(IconButton).at(2).tap(); + // Dialog is closed + expect(find.text('Open'), findsOneWidget); + // Expect no change in color + expect(Color(resultColor.value), Color(Colors.deepPurple.value)); + // Color(0xff673ab7); + }, + ); + + patrolWidgetTest( + 'PAT1.7-icon: Patrol test dialog - custom labels, elevated-icons, ' + 'with constraints.', + (PatrolTester $) async { + Color resultColor = Colors.blue; + await $.pumpWidgetAndSettle( + TestPicker( + widget: Builder( + builder: (BuildContext context) { + return ElevatedButton( + onPressed: () async { + resultColor = await showColorPickerDialog( + context, + resultColor, + constraints: const BoxConstraints( + minHeight: 480, minWidth: 320, maxWidth: 320), + pickersEnabled: const { + ColorPickerType.both: false, + ColorPickerType.primary: true, + ColorPickerType.accent: true, + ColorPickerType.bw: false, + ColorPickerType.custom: false, + ColorPickerType.wheel: false, + }, + includeIndex850: true, + crossAxisAlignment: CrossAxisAlignment.start, + enableTonalPalette: true, + enableOpacity: true, + actionButtons: const ColorPickerActionButtons( + okButton: true, + closeButton: true, + okTooltip: 'OK NOW', + closeTooltip: 'CLOSE NOW', + toolIconsThemeData: IconThemeData( + color: Colors.red, + size: 20, + opacity: 0.88, + ), + visualDensity: VisualDensity.comfortable, + padding: EdgeInsets.all(2), + splashRadius: 20, + dialogActionButtons: true, + dialogActionIcons: true, + dialogCancelButtonLabel: 'CLOSE', + dialogOkButtonLabel: 'Do', + dialogOkButtonType: + ColorPickerActionButtonType.elevated, + dialogCancelButtonType: + ColorPickerActionButtonType.elevated, + dialogActionOrder: + ColorPickerActionButtonOrder.okIsRight, + ), + copyPasteBehavior: const ColorPickerCopyPasteBehavior( + copyButton: true, + pasteButton: true, + editFieldCopyButton: true, + feedbackParseError: true, + parseShortHexCode: true, + ), + ); + }, + child: const Text('Open'), + ); + }, + ), + ), + ); + + // Test primary color picker. + expect(find.text('Open'), findsOneWidget); + await $('Open').tap(); + + expect(find.text('Primary'), findsOneWidget); + await $('Primary').tap(); + await $(ColorIndicator).tap(); + // expect(resultColor.value, Colors.red.value); + await $(ColorIndicator).at(6).tap(); + // expect(resultColor.value, Colors.lightBlue.value); + expect(find.text('Do'), findsOneWidget); + await $('Do').tap(); + expect(Color(resultColor.value), Color(Colors.lightBlue.value)); + + // Dialog is closed + expect(find.text('Open'), findsOneWidget); + // Open dialog again + await $('Open').tap(); + // Close via Cancel button + expect(find.text('CLOSE'), findsOneWidget); + await $('CLOSE').tap(); + + // Open dialog again + await $('Open').tap(); + await $('Primary').tap(); + await $(ColorIndicator).tap(); + // expect(resultColor.value, Colors.red.value); + // Find the ToolBar + expect(find.byType(ColorPickerToolbar), findsOneWidget); + // Find the ToolBar buttons, 4 of them configured. + expect($(ColorPickerToolbar).$(IconButton), findsNWidgets(4)); + // Close via toolbar, 4th button is Close, since close is last + await $(ColorPickerToolbar).$(IconButton).at(3).tap(); + expect(resultColor.value, Colors.lightBlue.value); + + // Dialog is closed + expect(find.text('Open'), findsOneWidget); + // Open dialog again + await $('Open').tap(); + await $('Primary').tap(); + await $(ColorIndicator).at(3).tap(); + // expect(resultColor.value, Colors.deepPurple.value); + // Find the ToolBar buttons, 4 of them configured. + expect($(ColorPickerToolbar).$(IconButton), findsNWidgets(4)); + // Close via toolbar, 3rd button is OK, since close is NOT last + await $(ColorPickerToolbar).$(IconButton).at(2).tap(); + // Dialog is closed + expect(find.text('Open'), findsOneWidget); + // Expect no change in color + expect(Color(resultColor.value), Color(Colors.deepPurple.value)); + // Color(0xff673ab7); + }, + ); + + patrolWidgetTest( + 'PAT1.8: Patrol test dialog - custom labels, outlined-no-icon, ' + 'with constraints and custom transition.', + (PatrolTester $) async { + Color resultColor = Colors.blue; + await $.pumpWidgetAndSettle( + TestPicker( + widget: Builder( + builder: (BuildContext context) { + return ElevatedButton( + onPressed: () async { + resultColor = await showColorPickerDialog( + context, + resultColor, + constraints: const BoxConstraints( + minHeight: 480, minWidth: 320, maxWidth: 320), + pickersEnabled: const { + ColorPickerType.both: false, + ColorPickerType.primary: true, + ColorPickerType.accent: true, + ColorPickerType.bw: false, + ColorPickerType.custom: false, + ColorPickerType.wheel: true, + }, + includeIndex850: true, + crossAxisAlignment: CrossAxisAlignment.start, + enableTonalPalette: true, + enableOpacity: true, + actionButtons: const ColorPickerActionButtons( + okButton: true, + closeButton: true, + okTooltip: 'OK NOW', + closeTooltip: 'CLOSE NOW', + toolIconsThemeData: IconThemeData( + color: Colors.red, + size: 20, + opacity: 0.88, + ), + visualDensity: VisualDensity.comfortable, + padding: EdgeInsets.all(2), + splashRadius: 20, + dialogCancelButtonLabel: 'CLOSE', + dialogOkButtonLabel: 'OK', + dialogOkButtonType: + ColorPickerActionButtonType.outlined, + dialogCancelButtonType: + ColorPickerActionButtonType.outlined, + dialogActionOrder: + ColorPickerActionButtonOrder.okIsLeft, + ), + copyPasteBehavior: const ColorPickerCopyPasteBehavior( + copyButton: true, + pasteButton: true, + editFieldCopyButton: true, + feedbackParseError: true, + parseShortHexCode: true, + ), + transitionBuilder: (BuildContext context, + Animation a1, + Animation a2, + Widget widget) { + final double curvedValue = + Curves.easeInOutBack.transform(a1.value) - 1.0; + return Transform( + transform: Matrix4.translationValues( + 0.0, curvedValue * 200, 0.0), + child: Opacity( + opacity: a1.value, + child: widget, + ), + ); + }, + transitionDuration: const Duration(milliseconds: 400), + ); + }, + child: const Text('Open'), + ); + }, + ), + ), + ); + + // Test primary color picker. + expect(find.text('Open'), findsOneWidget); + await $('Open').tap(); + + expect(find.text('Primary'), findsOneWidget); + await $('Primary').tap(); + await $(ColorIndicator).tap(); + // expect(resultColor.value, Colors.red.value); + await $(ColorIndicator).at(6).tap(); + // expect(resultColor.value, Colors.lightBlue.value); + expect(find.text('OK'), findsOneWidget); + await $('OK').tap(); + expect(Color(resultColor.value), Color(Colors.lightBlue.value)); + + // Open dialog again + await $('Open').tap(); + await $('Primary').tap(); + await $(ColorIndicator).tap(); + // expect(resultColor.value, Colors.red.value); + // Find the ToolBar + expect(find.byType(ColorPickerToolbar), findsOneWidget); + // Find the ToolBar buttons, 4 of them configured. + expect($(ColorPickerToolbar).$(IconButton), findsNWidgets(4)); + // Close via toolbar, 4th button is Close, since close is last + await $(ColorPickerToolbar).$(IconButton).at(3).tap(); + expect(resultColor.value, Colors.lightBlue.value); + + // Dialog is closed + expect(find.text('Open'), findsOneWidget); + // Open dialog again + await $('Open').tap(); + // Close via Cancel button + expect(find.text('CLOSE'), findsOneWidget); + await $('CLOSE').tap(); + + // Open dialog again + await $('Open').tap(); + await $('Primary').tap(); + await $(ColorIndicator).at(3).tap(); + // expect(resultColor.value, Colors.deepPurple.value); + // Find the ToolBar buttons, 4 of them configured. + expect($(ColorPickerToolbar).$(IconButton), findsNWidgets(4)); + // Close via toolbar, 3rd button is OK, since close is NOT last + await $(ColorPickerToolbar).$(IconButton).at(2).tap(); + // Dialog is closed + expect(find.text('Open'), findsOneWidget); + // Expect no change in color + expect(Color(resultColor.value), Color(Colors.deepPurple.value)); + + // Open dialog again + await $('Open').tap(); + // Got to wheel picker + expect(find.text('Wheel'), findsOneWidget); + await $('Wheel').tap(); + // Tap the center of the wheel picker + expect(find.byType(ColorWheelPicker), findsOneWidget); + await $(ColorWheelPicker).tap(); + // Tap close button + expect(find.text('OK'), findsOneWidget); + await $('OK').tap(); + // We get a purple color from smack in the middle + expect(Color(resultColor.value), const Color(0xff574080)); + }, + ); + + patrolWidgetTest( + 'PAT1.8-icon: Patrol test dialog - custom labels, outlined-icon, ' + 'with constraints and custom transition.', + (PatrolTester $) async { + Color resultColor = Colors.blue; + await $.pumpWidgetAndSettle( + TestPicker( + widget: Builder( + builder: (BuildContext context) { + return ElevatedButton( + onPressed: () async { + resultColor = await showColorPickerDialog( + context, + resultColor, + constraints: const BoxConstraints( + minHeight: 480, minWidth: 320, maxWidth: 320), + pickersEnabled: const { + ColorPickerType.both: false, + ColorPickerType.primary: true, + ColorPickerType.accent: true, + ColorPickerType.bw: false, + ColorPickerType.custom: false, + ColorPickerType.wheel: true, + }, + includeIndex850: true, + crossAxisAlignment: CrossAxisAlignment.start, + enableTonalPalette: true, + enableOpacity: true, + actionButtons: const ColorPickerActionButtons( + okButton: true, + closeButton: true, + okTooltip: 'OK NOW', + closeTooltip: 'CLOSE NOW', + toolIconsThemeData: IconThemeData( + color: Colors.red, + size: 20, + opacity: 0.88, + ), + visualDensity: VisualDensity.comfortable, + padding: EdgeInsets.all(2), + splashRadius: 20, + dialogActionButtons: true, + dialogActionIcons: true, + dialogCancelButtonLabel: 'CLOSE', + dialogOkButtonLabel: 'OK', + dialogOkButtonType: + ColorPickerActionButtonType.outlined, + dialogCancelButtonType: + ColorPickerActionButtonType.outlined, + dialogActionOrder: + ColorPickerActionButtonOrder.okIsLeft, + ), + copyPasteBehavior: const ColorPickerCopyPasteBehavior( + copyButton: true, + pasteButton: true, + editFieldCopyButton: true, + feedbackParseError: true, + parseShortHexCode: true, + ), + transitionBuilder: (BuildContext context, + Animation a1, + Animation a2, + Widget widget) { + final double curvedValue = + Curves.easeInOutBack.transform(a1.value) - 1.0; + return Transform( + transform: Matrix4.translationValues( + 0.0, curvedValue * 200, 0.0), + child: Opacity( + opacity: a1.value, + child: widget, + ), + ); + }, + transitionDuration: const Duration(milliseconds: 400), + ); + }, + child: const Text('Open'), + ); + }, + ), + ), + ); + + // Test primary color picker. + expect(find.text('Open'), findsOneWidget); + await $('Open').tap(); + + expect(find.text('Primary'), findsOneWidget); + await $('Primary').tap(); + await $(ColorIndicator).tap(); + // expect(resultColor.value, Colors.red.value); + await $(ColorIndicator).at(6).tap(); + // expect(resultColor.value, Colors.lightBlue.value); + expect(find.text('OK'), findsOneWidget); + await $('OK').tap(); + expect(Color(resultColor.value), Color(Colors.lightBlue.value)); + + // Open dialog again + await $('Open').tap(); + await $('Primary').tap(); + await $(ColorIndicator).tap(); + // expect(resultColor.value, Colors.red.value); + // Find the ToolBar + expect(find.byType(ColorPickerToolbar), findsOneWidget); + // Find the ToolBar buttons, 4 of them configured. + expect($(ColorPickerToolbar).$(IconButton), findsNWidgets(4)); + // Close via toolbar, 4th button is Close, since close is last + await $(ColorPickerToolbar).$(IconButton).at(3).tap(); + expect(resultColor.value, Colors.lightBlue.value); + + // Dialog is closed + expect(find.text('Open'), findsOneWidget); + // Open dialog again + await $('Open').tap(); + // Close via Cancel button + expect(find.text('CLOSE'), findsOneWidget); + await $('CLOSE').tap(); + + // Open dialog again + await $('Open').tap(); + await $('Primary').tap(); + await $(ColorIndicator).at(3).tap(); + // expect(resultColor.value, Colors.deepPurple.value); + // Find the ToolBar buttons, 4 of them configured. + expect($(ColorPickerToolbar).$(IconButton), findsNWidgets(4)); + // Close via toolbar, 3rd button is OK, since close is NOT last + await $(ColorPickerToolbar).$(IconButton).at(2).tap(); + // Dialog is closed + expect(find.text('Open'), findsOneWidget); + // Expect no change in color + expect(Color(resultColor.value), Color(Colors.deepPurple.value)); + + // Open dialog again + await $('Open').tap(); + // Got to wheel picker + expect(find.text('Wheel'), findsOneWidget); + await $('Wheel').tap(); + // Tap the center of the wheel picker + expect(find.byType(ColorWheelPicker), findsOneWidget); + await $(ColorWheelPicker).tap(); + // Tap close button + expect(find.text('OK'), findsOneWidget); + await $('OK').tap(); + // We get a purple color from smack in the middle + expect(Color(resultColor.value), const Color(0xff574080)); + }, + ); + + ///--- }); } -class TestWidget extends StatelessWidget { - const TestWidget({super.key, required this.widget}); +class TestPicker extends StatelessWidget { + const TestPicker({super.key, required this.widget, this.platform}); final Widget widget; + final TargetPlatform? platform; @override Widget build(BuildContext context) { debugDefaultTargetPlatformOverride = null; return MaterialApp( title: 'TestWidget', + theme: ThemeData( + platform: platform, + ), home: Scaffold( appBar: AppBar( title: const Text('TestWidget'), diff --git a/test/color_picker_test.dart b/test/color_picker_test.dart index 8a6acfb..abf3921 100644 --- a/test/color_picker_test.dart +++ b/test/color_picker_test.dart @@ -40,6 +40,7 @@ void main() { ColorPickerType.accent: true, ColorPickerType.bw: false, ColorPickerType.custom: false, + ColorPickerType.customSecondary: false, ColorPickerType.wheel: false, } && widget.enableShadesSelection && @@ -97,10 +98,14 @@ void main() { ColorPickerType.bw: 'Black & White', ColorPickerType.both: 'Primary & Accent', ColorPickerType.custom: 'Custom', + ColorPickerType.customSecondary: 'Option', ColorPickerType.wheel: 'Wheel', } && widget.customColorSwatchesAndNames == + const , String>{} && + widget.customSecondaryColorSwatchesAndNames == const , String>{}; + expect(find.byWidgetPredicate(defaultPicker), findsOneWidget); }); // @@ -164,6 +169,14 @@ void main() { ColorTools.createAccentSwatch(const Color(0xFF03DAC6)): 'Guide Teal', }, + customSecondaryColorSwatchesAndNames: , String>{ + ColorTools.createPrimarySwatch(const Color(0xFF6200EE)): + 'Guide Purple', + ColorTools.createPrimarySwatch(const Color(0xFF3700B3)): + 'Guide Purple Variant', + ColorTools.createAccentSwatch(const Color(0xFF03DAC6)): + 'Guide Teal', + }, ), ), ); @@ -247,9 +260,13 @@ void main() { ColorPickerType.bw: 'Black & White', ColorPickerType.both: 'Primary & Accent', ColorPickerType.custom: 'Custom', + ColorPickerType.customSecondary: 'Option', ColorPickerType.wheel: 'Wheel', } && - widget.customColorSwatchesAndNames != , String>{}; + widget.customColorSwatchesAndNames != + , String>{} && + widget.customSecondaryColorSwatchesAndNames != + , String>{}; expect(find.byWidgetPredicate(customPicker, skipOffstage: false), findsOneWidget); }); @@ -314,6 +331,14 @@ void main() { ColorTools.createAccentSwatch(const Color(0xFF03DAC6)): 'Guide Teal', }, + customSecondaryColorSwatchesAndNames: , String>{ + ColorTools.createPrimarySwatch(const Color(0xFF6200EE)): + 'Guide Purple', + ColorTools.createPrimarySwatch(const Color(0xFF3700B3)): + 'Guide Purple Variant', + ColorTools.createAccentSwatch(const Color(0xFF03DAC6)): + 'Guide Teal', + }, ), ), ); @@ -397,9 +422,13 @@ void main() { ColorPickerType.bw: 'Black & White', ColorPickerType.both: 'Primary & Accent', ColorPickerType.custom: 'Custom', + ColorPickerType.customSecondary: 'Option', ColorPickerType.wheel: 'Wheel', } && - widget.customColorSwatchesAndNames != , String>{}; + widget.customColorSwatchesAndNames != + , String>{} && + widget.customSecondaryColorSwatchesAndNames != + , String>{}; expect(find.byWidgetPredicate(customPicker), findsOneWidget); }); @@ -492,6 +521,14 @@ void main() { ColorTools.createAccentSwatch(const Color(0xFF03DAC6)): 'Guide Teal', }, + customSecondaryColorSwatchesAndNames: , String>{ + ColorTools.createPrimarySwatch(const Color(0xFF6200EE)): + 'Guide Purple', + ColorTools.createPrimarySwatch(const Color(0xFF3700B3)): + 'Guide Purple Variant', + ColorTools.createAccentSwatch(const Color(0xFF03DAC6)): + 'Guide Teal', + }, ), ), ); @@ -596,9 +633,13 @@ void main() { ColorPickerType.bw: 'Black & White', ColorPickerType.both: 'Primary & Accent', ColorPickerType.custom: 'Custom', + ColorPickerType.customSecondary: 'Option', ColorPickerType.wheel: 'Wheel', } && - widget.customColorSwatchesAndNames != , String>{}; + widget.customColorSwatchesAndNames != + , String>{} && + widget.customSecondaryColorSwatchesAndNames != + , String>{}; expect(find.byWidgetPredicate(customPicker), findsOneWidget); }); @@ -687,6 +728,14 @@ void main() { ColorTools.createAccentSwatch(const Color(0xFF03DAC6)): 'Guide Teal', }, + customSecondaryColorSwatchesAndNames: , String>{ + ColorTools.createPrimarySwatch(const Color(0xFF6200EE)): + 'Guide Purple', + ColorTools.createPrimarySwatch(const Color(0xFF3700B3)): + 'Guide Purple Variant', + ColorTools.createAccentSwatch(const Color(0xFF03DAC6)): + 'Guide Teal', + }, ), ), ); @@ -791,9 +840,13 @@ void main() { ColorPickerType.bw: 'Black & White', ColorPickerType.both: 'Primary & Accent', ColorPickerType.custom: 'Custom', + ColorPickerType.customSecondary: 'Option', ColorPickerType.wheel: 'Wheel', } && - widget.customColorSwatchesAndNames != , String>{}; + widget.customColorSwatchesAndNames != + , String>{} && + widget.customSecondaryColorSwatchesAndNames != + , String>{}; expect(find.byWidgetPredicate(customPicker), findsOneWidget); }); @@ -882,6 +935,14 @@ void main() { ColorTools.createAccentSwatch(const Color(0xFF03DAC6)): 'Guide Teal', }, + customSecondaryColorSwatchesAndNames: , String>{ + ColorTools.createPrimarySwatch(const Color(0xFF6200EE)): + 'Guide Purple', + ColorTools.createPrimarySwatch(const Color(0xFF3700B3)): + 'Guide Purple Variant', + ColorTools.createAccentSwatch(const Color(0xFF03DAC6)): + 'Guide Teal', + }, ), ), ); @@ -986,9 +1047,13 @@ void main() { ColorPickerType.bw: 'Black & White', ColorPickerType.both: 'Primary & Accent', ColorPickerType.custom: 'Custom', + ColorPickerType.customSecondary: 'Option', ColorPickerType.wheel: 'Wheel', } && - widget.customColorSwatchesAndNames != , String>{}; + widget.customColorSwatchesAndNames != + , String>{} && + widget.customSecondaryColorSwatchesAndNames != + , String>{}; expect(find.byWidgetPredicate(customPicker), findsOneWidget); }); }); diff --git a/test/copy_paste_handler_test.dart b/test/copy_paste_handler_test.dart new file mode 100644 index 0000000..fbeaca6 --- /dev/null +++ b/test/copy_paste_handler_test.dart @@ -0,0 +1,51 @@ +import 'package:flex_color_picker/src/widgets/copy_paste_handler.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('CopyPasteHandler Widget Test', (WidgetTester tester) async { + Future mockCopyToClipboard() async { + await Clipboard.setData(const ClipboardData(text: 'Copied Text')); + } + + Future mockPasteFromClipboard() async { + final ClipboardData? data = await Clipboard.getData(Clipboard.kTextPlain); + expect(data?.text, 'Copied Text'); + } + + final FocusNode focusNode = FocusNode(); + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: CopyPasteHandler( + pasteFromClipboard: mockPasteFromClipboard, + copyToClipboard: mockCopyToClipboard, + useContextMenu: true, + useLongPress: true, + useSecondaryTapDown: true, + useSecondaryOnDesktopLongOnDevice: true, + useSecondaryOnDesktopLongOnDeviceAndWeb: true, + onCopyPasteMenuOpened: () {}, + focusNode: focusNode, + autoFocus: true, + child: Container(), + ), + ), + ), + ); + + expect(find.byType(CopyPasteHandler), findsOneWidget); + + // Test copy functionality + await tester.sendKeyEvent(LogicalKeyboardKey.meta); + await tester.sendKeyEvent(LogicalKeyboardKey.keyC); + await tester.pumpAndSettle(); + + // Test paste functionality + await tester.sendKeyEvent(LogicalKeyboardKey.meta); + await tester.sendKeyEvent(LogicalKeyboardKey.keyV); + await tester.pumpAndSettle(); + }); +} diff --git a/test/dry_intrisinic_test.dart b/test/dry_intrisinic_test.dart index 562751a..7c7436a 100644 --- a/test/dry_intrisinic_test.dart +++ b/test/dry_intrisinic_test.dart @@ -27,6 +27,102 @@ void main() { expect(widget, findsOneWidget); }); }); + + testWidgets('DryIntrinsicWidth computes dry layout correctly', + (WidgetTester tester) async { + // Build the DryIntrinsicWidth with a child widget + await tester.pumpWidget( + const MaterialApp( + home: Scaffold( + body: Center( + child: DryIntrinsicWidth( + child: SizedBox(width: 50, height: 100), + ), + ), + ), + ), + ); + + // Verify that the DryIntrinsicWidth is displayed + expect(find.byType(DryIntrinsicWidth), findsOneWidget); + + // Verify that the DryIntrinsicWidth has the correct size + final RenderBox dryIntrinsicWidth = + tester.renderObject(find.byType(DryIntrinsicWidth)); + expect(dryIntrinsicWidth.size, equals(const Size(50, 100))); + }); + + testWidgets('DryIntrinsicWidth computes zero dry layout correctly', + (WidgetTester tester) async { + // Build the DryIntrinsicWidth with a child widget + await tester.pumpWidget( + const MaterialApp( + home: Scaffold( + body: Center( + child: DryIntrinsicWidth( + child: null, + ), + ), + ), + ), + ); + + // Verify that the DryIntrinsicWidth is displayed + expect(find.byType(DryIntrinsicWidth), findsOneWidget); + + // Verify that the DryIntrinsicWidth has the correct size + final RenderBox dryIntrinsicWidth = + tester.renderObject(find.byType(DryIntrinsicWidth)); + expect(dryIntrinsicWidth.size, equals(Size.zero)); + }); + + testWidgets('DryIntrinsicHeight computes dry layout correctly', + (WidgetTester tester) async { + // Build the DryIntrinsicHeight with a child widget + await tester.pumpWidget( + const MaterialApp( + home: Scaffold( + body: Center( + child: DryIntrinsicHeight( + child: SizedBox(width: 100, height: 50), + ), + ), + ), + ), + ); + + // Verify that the DryIntrinsicHeight is displayed + expect(find.byType(DryIntrinsicHeight), findsOneWidget); + + // Verify that the DryIntrinsicHeight has the correct size + final RenderBox dryIntrinsicHeight = + tester.renderObject(find.byType(DryIntrinsicHeight)); + expect(dryIntrinsicHeight.size, equals(const Size(100, 50))); + }); + + testWidgets('DryIntrinsicHeight computes zero dry layout correctly', + (WidgetTester tester) async { + // Build the DryIntrinsicHeight with a child widget + await tester.pumpWidget( + const MaterialApp( + home: Scaffold( + body: Center( + child: DryIntrinsicHeight( + child: null, + ), + ), + ), + ), + ); + + // Verify that the DryIntrinsicHeight is displayed + expect(find.byType(DryIntrinsicHeight), findsOneWidget); + + // Verify that the DryIntrinsicHeight has the correct size + final RenderBox dryIntrinsicHeight = + tester.renderObject(find.byType(DryIntrinsicHeight)); + expect(dryIntrinsicHeight.size, equals(Size.zero)); + }); } class TestWidget extends StatelessWidget { diff --git a/test/opacity_slider_test.dart b/test/opacity_slider_test.dart new file mode 100644 index 0000000..ceac317 --- /dev/null +++ b/test/opacity_slider_test.dart @@ -0,0 +1,40 @@ +import 'package:flex_color_picker/src/widgets/opacity/opacity_slider.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('OpacitySlider test', (WidgetTester tester) async { + double opacity = 0.5; + + // Create a MaterialApp as a root widget for the test + await tester.pumpWidget(MaterialApp( + home: Scaffold( + body: Center( + child: OpacitySlider( + opacity: opacity, + color: Colors.red, + onChanged: (double value) { + debugPrint('onChanged: $value'); + opacity = value; + }, + onChangeStart: (double value) { + debugPrint('onChangeStart: $value'); + }, + onChangeEnd: (double value) { + debugPrint('onChangeEnd: $value'); + }, + ), + ), + ), + )); + + // Ensure the widget tree has finished building + await tester.pumpAndSettle(); + + // Verify that the initial opacity is correct + expect(opacity, 0.5); + + // Check if the slider is found by the test + expect(find.byType(OpacitySlider), findsOneWidget); + }); +} diff --git a/test/opacity_slider_thumb_test.dart b/test/opacity_slider_thumb_test.dart new file mode 100644 index 0000000..76b41d8 --- /dev/null +++ b/test/opacity_slider_thumb_test.dart @@ -0,0 +1,30 @@ +import 'package:flex_color_picker/src/widgets/opacity/opacity_slider_thumb.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('OpacitySliderThumb test', (WidgetTester tester) async { + // Create a MaterialApp as a root widget for the test + await tester.pumpWidget(MaterialApp( + home: Scaffold( + body: Center( + child: SliderTheme( + data: const SliderThemeData( + thumbShape: OpacitySliderThumb(color: Colors.red), + ), + child: Slider( + value: 0.5, + onChanged: (double value) {}, + ), + ), + ), + ), + )); + + // Ensure the widget tree has finished building + await tester.pumpAndSettle(); + + // Check if the slider is found by the test + expect(find.byType(Slider), findsOneWidget); + }); +} diff --git a/test/opacity_slider_track_test.dart b/test/opacity_slider_track_test.dart new file mode 100644 index 0000000..be73536 --- /dev/null +++ b/test/opacity_slider_track_test.dart @@ -0,0 +1,55 @@ +import 'dart:async'; +import 'dart:ui' as ui; + +import 'package:flex_color_picker/src/widgets/opacity/opacity_slider_track.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('OpacitySliderTrack test', (WidgetTester tester) async { + // Create an AssetImage for the test + const AssetImage assetImage = AssetImage('assets/opacity.png'); + late ui.Image image; + final Completer completer = Completer(); + + // Load the image using the tester's runAsync method + await tester.runAsync(() async { + final ByteData data = await rootBundle.load(assetImage.keyName); + final ui.Codec codec = + await ui.instantiateImageCodec(data.buffer.asUint8List()); + final ui.FrameInfo frameInfo = await codec.getNextFrame(); + image = frameInfo.image; + completer.complete(); + }); + + await completer.future; + + // Create a MaterialApp as a root widget for the test + await tester.pumpWidget(MaterialApp( + home: Scaffold( + body: Center( + child: SliderTheme( + data: SliderThemeData( + trackShape: OpacitySliderTrack( + color: Colors.red, + thumbRadius: 14, + image: image, + ), + ), + child: Slider( + value: 0.5, + onChanged: (double value) {}, + ), + ), + ), + ), + )); + + // Ensure the widget tree has finished building + await tester.pumpAndSettle(); + + // Check if the slider is found by the test + expect(find.byType(Slider), findsOneWidget); + }); +} diff --git a/test/show_color_picker_dialog_test.dart b/test/show_color_picker_dialog_test.dart new file mode 100644 index 0000000..7c023ee --- /dev/null +++ b/test/show_color_picker_dialog_test.dart @@ -0,0 +1,51 @@ +import 'package:flex_color_picker/flex_color_picker.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('Test showColorPickerDialog', (WidgetTester tester) async { + // Define a color to start with + Color color = Colors.red; + + // Create a MaterialApp as a root widget for the test + await tester.pumpWidget(MaterialApp( + home: Scaffold( + body: Builder( + builder: (BuildContext context) { + return ElevatedButton( + onPressed: () async { + color = await showColorPickerDialog( + context, + color, + pickersEnabled: const { + ColorPickerType.both: false, + ColorPickerType.primary: true, + ColorPickerType.accent: true, + ColorPickerType.bw: false, + ColorPickerType.custom: false, + ColorPickerType.wheel: false, + }, + ); + }, + child: const Text('Open Color Picker'), + ); + }, + ), + ), + )); + + // Tap the button to open the color picker dialog + await tester.tap(find.text('Open Color Picker')); + await tester.pumpAndSettle(); + + // Verify that the dialog is displayed + expect(find.byType(AlertDialog), findsOneWidget); + + // Tap on a color to select it + await tester.tap(find.byType(ColorIndicator).first); + await tester.pumpAndSettle(); + + // Verify that the color has been selected + expect(color, isNotNull); + }); +}