-
-
Notifications
You must be signed in to change notification settings - Fork 42
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feature request: make OK button more intuitive #74
Comments
Hi @Piotr12, There are two options you can use to currently style the OK/Close buttons. 1. Wrap with desired button themeYou wrap it with a theme where the type of Text/Elevated/Outlined button you decide to use for "OK" has a more prominent style, and you can of course set labels to whatever you like. This can look like this: Screen.Recording.2024-01-26.at.18.06.38.movThe above is a modified version of the default example in the repo Code exampleimport 'package:flex_color_picker/flex_color_picker.dart';
import 'package:flutter/material.dart';
import 'demo/utils/app_scroll_behavior.dart';
void main() => runApp(const ColorPickerDemo());
class ColorPickerDemo extends StatefulWidget {
const ColorPickerDemo({super.key});
@override
State<ColorPickerDemo> createState() => _ColorPickerDemoState();
}
class _ColorPickerDemoState extends State<ColorPickerDemo> {
late ThemeMode themeMode;
@override
void initState() {
super.initState();
themeMode = ThemeMode.light;
}
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
scrollBehavior: AppScrollBehavior(),
title: 'ColorPicker',
theme: ThemeData(useMaterial3: true),
darkTheme: ThemeData(useMaterial3: true, brightness: Brightness.dark),
themeMode: themeMode,
home: ColorPickerPage(
themeMode: (ThemeMode mode) {
setState(() {
themeMode = mode;
});
},
),
);
}
}
class ColorPickerPage extends StatefulWidget {
const ColorPickerPage({super.key, required this.themeMode});
final ValueChanged<ThemeMode> themeMode;
@override
State<ColorPickerPage> createState() => _ColorPickerPageState();
}
class _ColorPickerPageState extends State<ColorPickerPage> {
late Color screenPickerColor; // Color for picker shown in Card on the screen.
late Color dialogPickerColor; // Color for picker in dialog using onChanged
late Color dialogSelectColor; // Color for picker using color select dialog.
late bool isDark;
// Define some custom colors for the custom picker segment.
// The 'guide' color values are from
// https://material.io/design/color/the-color-system.html#color-theme-creation
static const Color guidePrimary = Color(0xFF6200EE);
static const Color guidePrimaryVariant = Color(0xFF3700B3);
static const Color guideSecondary = Color(0xFF03DAC6);
static const Color guideSecondaryVariant = Color(0xFF018786);
static const Color guideError = Color(0xFFB00020);
static const Color guideErrorDark = Color(0xFFCF6679);
static const Color blueBlues = Color(0xFF174378);
// Make a custom ColorSwatch to name map from the above custom colors.
final Map<ColorSwatch<Object>, String> colorsNameMap =
<ColorSwatch<Object>, String>{
ColorTools.createPrimarySwatch(guidePrimary): 'Guide Purple',
ColorTools.createPrimarySwatch(guidePrimaryVariant): 'Guide Purple Variant',
ColorTools.createAccentSwatch(guideSecondary): 'Guide Teal',
ColorTools.createAccentSwatch(guideSecondaryVariant): 'Guide Teal Variant',
ColorTools.createPrimarySwatch(guideError): 'Guide Error',
ColorTools.createPrimarySwatch(guideErrorDark): 'Guide Error Dark',
ColorTools.createPrimarySwatch(blueBlues): 'Blue blues',
};
@override
void initState() {
screenPickerColor = Colors.blue;
dialogPickerColor = Colors.red;
dialogSelectColor = const Color(0xFFA239CA);
isDark = false;
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: const Text('ColorPicker Demo'),
),
body: ListView(
padding: const EdgeInsets.fromLTRB(8, 0, 8, 0),
children: <Widget>[
const SizedBox(height: 16),
// Pick color in a dialog.
ListTile(
title: const Text('Click this color to modify it in a dialog. '
'The color is modified while dialog is open, but returns '
'to previous value if dialog is cancelled'),
subtitle: Text(
// ignore: lines_longer_than_80_chars
'${ColorTools.materialNameAndCode(dialogPickerColor, colorSwatchNameMap: colorsNameMap)} '
'aka ${ColorTools.nameThatColor(dialogPickerColor)}',
),
trailing: Theme(
data: Theme.of(context).copyWith(
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.pinkAccent,
foregroundColor: Colors.white,
padding: const EdgeInsets.all(20),
elevation: 0,
),
),
),
child: Builder(builder: (BuildContext context) {
return ColorIndicator(
width: 44,
height: 44,
borderRadius: 4,
color: dialogPickerColor,
onSelectFocus: false,
onSelect: () async {
// Store current color before we open the dialog.
final Color colorBeforeDialog = dialogPickerColor;
// Wait for the picker to close, if dialog was dismissed,
// then restore the color we had before it was opened.
if (!(await colorPickerDialog(context))) {
setState(() {
dialogPickerColor = colorBeforeDialog;
});
}
},
);
}),
),
),
ListTile(
title: const Text('Click to select a new color from a dialog '
'that uses custom open/close animation. The color is only '
'modified after dialog is closed with OK'),
subtitle: Text(
// ignore: lines_longer_than_80_chars
'${ColorTools.materialNameAndCode(dialogSelectColor, colorSwatchNameMap: colorsNameMap)} '
'aka ${ColorTools.nameThatColor(dialogSelectColor)}',
),
trailing: Theme(
data: Theme.of(context).copyWith(
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.pinkAccent,
foregroundColor: Colors.white,
padding: const EdgeInsets.all(20),
textStyle: const TextStyle(fontSize: 20),
elevation: 0,
),
),
),
child: Builder(builder: (BuildContext context) {
return ColorIndicator(
width: 40,
height: 40,
borderRadius: 0,
color: dialogSelectColor,
elevation: 1,
onSelectFocus: false,
onSelect: () async {
// Wait for the dialog to return color selection result.
final Color newColor = await showColorPickerDialog(
// The dialog needs a context, we pass it in.
context,
// We use the dialogSelectColor, as its starting color.
dialogSelectColor,
title: Text('ColorPicker',
style: Theme.of(context).textTheme.titleLarge),
width: 40,
height: 40,
spacing: 0,
runSpacing: 0,
borderRadius: 0,
wheelDiameter: 165,
enableOpacity: true,
showColorCode: true,
colorCodeHasColor: true,
pickersEnabled: <ColorPickerType, bool>{
ColorPickerType.wheel: true,
},
copyPasteBehavior: const ColorPickerCopyPasteBehavior(
copyButton: true,
pasteButton: true,
longPressMenu: true,
),
actionButtons: const ColorPickerActionButtons(
useRootNavigator: true,
okButton: true,
closeButton: true,
dialogActionButtons: true,
dialogCancelButtonType:
ColorPickerActionButtonType.text,
dialogOkButtonType:
ColorPickerActionButtonType.elevated,
dialogOkButtonLabel: 'SELECT',
),
transitionBuilder: (BuildContext context,
Animation<double> a1,
Animation<double> 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),
constraints: const BoxConstraints(
minHeight: 480, minWidth: 320, maxWidth: 320),
);
// We update the dialogSelectColor, to the returned result
// color. If the dialog was dismissed it actually returns
// the color we started with. The extra update for that
// below does not really matter, but if you want you can
// check if they are equal and skip the update below.
setState(() {
dialogSelectColor = newColor;
});
});
}),
),
),
// Show the selected color.
ListTile(
title: const Text('Select color below to change this color'),
subtitle:
Text('${ColorTools.materialNameAndCode(screenPickerColor)} '
'aka ${ColorTools.nameThatColor(screenPickerColor)}'),
trailing: ColorIndicator(
width: 44,
height: 44,
borderRadius: 22,
color: screenPickerColor,
),
),
// Show the color picker in sized box in a raised card.
SizedBox(
width: double.infinity,
child: Padding(
padding: const EdgeInsets.all(6),
child: Card(
elevation: 2,
child: ColorPicker(
// Use the screenPickerColor as start color.
color: screenPickerColor,
// Update the screenPickerColor using the callback.
onColorChanged: (Color color) =>
setState(() => screenPickerColor = color),
width: 44,
height: 44,
borderRadius: 22,
heading: Text(
'Select color',
style: Theme.of(context).textTheme.headlineSmall,
),
subheading: Text(
'Select color shade',
style: Theme.of(context).textTheme.titleMedium,
),
),
),
),
),
// Theme mode toggle
SwitchListTile(
title: const Text('Turn ON for dark mode'),
subtitle: const Text('Turn OFF for light mode'),
value: isDark,
onChanged: (bool value) {
setState(() {
isDark = value;
widget.themeMode(isDark ? ThemeMode.dark : ThemeMode.light);
});
},
)
],
),
);
}
Future<bool> colorPickerDialog(BuildContext context) async {
return ColorPicker(
color: dialogPickerColor,
onColorChanged: (Color color) =>
setState(() => dialogPickerColor = color),
width: 40,
height: 40,
borderRadius: 4,
spacing: 5,
runSpacing: 5,
wheelDiameter: 155,
heading: Text(
'Select color',
style: Theme.of(context).textTheme.titleMedium,
),
subheading: Text(
'Select color shade',
style: Theme.of(context).textTheme.titleMedium,
),
wheelSubheading: Text(
'Selected color and its shades',
style: Theme.of(context).textTheme.titleMedium,
),
showMaterialName: true,
showColorName: true,
showColorCode: true,
copyPasteBehavior: const ColorPickerCopyPasteBehavior(
longPressMenu: true,
),
actionButtons: const ColorPickerActionButtons(
useRootNavigator: false,
dialogActionButtons: true,
dialogCancelButtonType: ColorPickerActionButtonType.text,
dialogOkButtonType: ColorPickerActionButtonType.elevated,
dialogOkButtonLabel: 'PICK COLOR',
),
materialNameTextStyle: Theme.of(context).textTheme.bodySmall,
colorNameTextStyle: Theme.of(context).textTheme.bodySmall,
colorCodeTextStyle: Theme.of(context).textTheme.bodyMedium,
colorCodePrefixStyle: Theme.of(context).textTheme.bodySmall,
selectedPickerTypeColor: Theme.of(context).colorScheme.primary,
pickersEnabled: const <ColorPickerType, bool>{
ColorPickerType.both: false,
ColorPickerType.primary: true,
ColorPickerType.accent: true,
ColorPickerType.bw: false,
ColorPickerType.custom: true,
ColorPickerType.wheel: true,
},
enableTonalPalette: true, // Enable tonal palette
customColorSwatchesAndNames: colorsNameMap,
).showPickerDialog(
context,
actionsPadding: const EdgeInsets.all(16),
constraints:
const BoxConstraints(minHeight: 480, minWidth: 300, maxWidth: 320),
);
}
}
2. Make your own dialog wrapperYou can make your own dialog wrapper of the 3. Do not use any bottom OK/Cancel dialog buttonsI kind of prefer the compact options where you just have close and select in the header. OK button that follows the currently selected color?Upon reading your proposal closer, I'm beginning to suspect that you would like to see a feature flag that if set makes the dialog "OK" button color follow the currently selected color? Then you can set its label to PICK, SELECT, CHOOSE, USE or whatever. Agreed then it also needs to adjust text contrast color while it does that. This would be like what the optional color value input/indicator does below: Screen.Recording.2024-01-26.at.18.34.27.movAnd check marks also do that when you select colors. Yes this is doable, not that tricky even. It would however only work well visually when the OK button style is set to use Is this what you had in mind? Feel free to elaborate on the feature request. I can certainly add this as a feature to next minor feature release. |
thanks for detailed answer.
this is exactly what I look for and |
Sorry to say, but this colored "OK" button cannot be done within the currently used Best I can do in next release (v.3.4.0) is recommend using the "filled" button for OK as prominent one if so needed, and not having any cancel button (also new in 3.4.0 to not have a bottom cancel button when bottom dialog buttons are used), only close in upper corner and tapping outside dialog as close: It is possible to build this, but then I need to add own bottom OK / Cancel buttons in the Dialog and having them as an option that are used if you opt for the selected color following OK button. Doable, I might return to this in version 4.0.0. When I am doing a lot of other planned changes. Keeping this feature request issue open as reminder. |
I have noticed some of my app users have difficulty noticing they need to confirm color selection by clicking the OK button. Seriously, some close the dialog and are surprised the color was not changed.
Question: Would it be ok to add a bool parameter in the ColorPickerActionButtons (updateOKButtonLikeCrazyToShowUsersWhatITDoes is the working title) that would modify the background color of the OK Button so it makes folks notice "here is what to click next" ?
If yes, I would be happy to make a PR with that, but before I start googling how to 1) modify, 2)test flutter packages locally I decided to ask not to get a "it is not welcome" response later.
PS. Font Color for the OK button shall be changed as well based on the grayscale representation of the color currently picked to avoid white font on almost-white background scenario. (https://support.ptc.com/help/mathcad/r9.0/en/index.html#page/PTC_Mathcad_Help/example_grayscale_and_color_in_images.html)
The text was updated successfully, but these errors were encountered: