showPickerDialog method

Future<bool> showPickerDialog(
  1. BuildContext context, {
  2. Widget? title,
  3. EdgeInsetsGeometry titlePadding = EdgeInsets.zero,
  4. TextStyle? titleTextStyle,
  5. EdgeInsetsGeometry contentPadding = EdgeInsets.zero,
  6. EdgeInsetsGeometry? actionsPadding,
  7. EdgeInsetsGeometry? buttonPadding,
  8. Color? backgroundColor,
  9. double? elevation,
  10. Color? shadowColor,
  11. Color? surfaceTintColor,
  12. String? semanticLabel,
  13. EdgeInsets insetPadding = const EdgeInsets.symmetric(horizontal: 40, vertical: 24),
  14. Clip clipBehavior = Clip.none,
  15. ShapeBorder? shape,
  16. Color barrierColor = Colors.black12,
  17. bool barrierDismissible = true,
  18. String? barrierLabel,
  19. bool useSafeArea = true,
  20. RouteSettings? routeSettings,
  21. Offset? anchorPoint,
  22. RouteTransitionsBuilder? transitionBuilder,
  23. Duration transitionDuration = const Duration(milliseconds: 200),
  24. BoxConstraints? constraints,
})

Show the defined ColorPicker in a custom alert dialog.

The showPickerDialog method is a convenience function to show the ColorPicker widget in a modal dialog. It re-implements the standard showDialog function with opinionated Cancel and OK buttons.

If a transitionBuilder is provided the showPickerDialog instead uses a showGeneralDialog implementation to show the ColorPicker, this allows for customization of the show animation.

It also by default uses a lighter barrier color. This is useful if the color picker is used to dynamically change color of a widget or entire application theme, since we can then better see the impact of the color choice behind the modal dialog when the barrier is made almost fully transparent.

Returns a Future bool, that resolves to true if the dialog is closed with OK action button, and to false if the cancel action was selected. Clicking outside the dialog also closes it and returns false.

The actual color selected in the dialog is handled via the onChange callbacks of the ColorPicker instance.

Implementation

Future<bool> showPickerDialog(
  /// The dialog requires a BuildContext.
  BuildContext context, {
  /// Title of the color picker dialog, often omitted in favor of using a
  /// [title] and/or [heading] already defined in the [ColorPicker].
  Widget? title,

  /// Padding around the dialog title, if a title is used.
  /// Defaults to `EdgeInsets.zero`, since the title is normally omitted
  /// and provided via the `heading` property of the `ColorPicker` instead.
  final EdgeInsetsGeometry titlePadding = EdgeInsets.zero,

  /// Style for the text in the [title] of this [AlertDialog].
  ///
  /// If null, [DialogTheme.titleTextStyle] is used. If that's null,
  /// defaults to [TextTheme.titleLarge] of [ThemeData.textTheme].
  final TextStyle? titleTextStyle,

  /// Padding around the content in the dialog.
  ///
  /// Defaults to `EdgeInsets.zero`, as the content padding is expected to
  /// be a part of the `ColorPicker`.
  final EdgeInsetsGeometry contentPadding = EdgeInsets.zero,

  /// Padding around the Cancel and OK action buttons at the bottom of
  /// the dialog.
  ///
  /// Typically used to provide padding to the button bar between the button
  /// bar and the edges of the dialog.
  ///
  /// Defaults to null and follows ambient [AlertDialog] themed actions
  /// padding or [AlertDialog] default if not defined.
  ///
  /// Versions before FlexColorPicker 3.0.0 defaulted to
  /// `EdgeInsets.symmetric(horizontal: 16) use it for same padding as in
  /// previous versions.
  final EdgeInsetsGeometry? actionsPadding,

  /// The padding that surrounds each bottom action button.
  ///
  /// This is different from [actionsPadding], which defines the padding
  /// between the entire button bar and the edges of the dialog.
  ///
  /// Defaults to null and follows ambient [AlertDialog] themed buttons
  /// padding or [AlertDialog] default if not defined.
  ///
  /// Versions before FlexColorPicker 3.0.0 defaulted to `EdgeInsets.all(16),
  /// use it for same button padding as in previous versions.
  final EdgeInsetsGeometry? buttonPadding,

  /// The background color of the surface of this Dialog.
  ///
  /// This sets the Material.color on this Dialog's Material.
  /// If null, ThemeData.dialogBackgroundColor is used.
  ///
  /// NOTE: The ColorPicker is designed to fit on background color with
  /// brightness that follow active theme mode. Putting e.g. white as
  /// background in dark theme mode, will not produce usable results.
  final Color? backgroundColor,

  /// The z-coordinate of this Dialog.
  ///
  /// If null then DialogTheme.elevation is used, and if that's null then the
  /// dialog's elevation is 24.0. The z-coordinate at which to place this
  /// material relative to its parent.
  ///
  /// This controls the size of the shadow below the material and the opacity
  /// of the elevation overlay color if it is applied. If this is non-zero,
  /// the contents of the material are clipped, because the widget
  /// conceptually defines an independent printed piece of material.
  /// Changing this value will cause the shadow and the elevation
  /// overlay to animate over Material.animationDuration.
  ///
  /// Defaults to 0.
  final double? elevation,

  /// The color used to paint a drop shadow under the dialog's Material,
  /// which reflects the dialog's elevation.
  final Color? shadowColor,

  /// The color used as a surface tint overlay on the dialog's background
  /// color, which reflects the dialog's elevation.
  final Color? surfaceTintColor,

  /// The semantic label of the dialog used by accessibility frameworks to
  /// announce screen transitions when the dialog is opened and closed.
  ///
  /// In iOS, if this label is not provided, a semantic label will be inferred
  /// from the [title] if it is not null.
  ///
  /// In Android, if this label is not provided, the dialog will use the
  /// [MaterialLocalizations.alertDialogLabel] as its label.
  ///
  /// See also:
  ///
  ///  * [SemanticsConfiguration.namesRoute], for a description of how this
  ///    value is used.
  final String? semanticLabel,

  /// The amount of padding added to `MediaQueryData.viewInsets` on the
  /// outside of the `ColorPicker` dialog.
  ///
  /// Defines the minimum space between the screen's edges and the dialog.
  /// Defaults to `EdgeInsets.symmetric(horizontal: 40, vertical: 24)`.
  final EdgeInsets insetPadding =
      const EdgeInsets.symmetric(horizontal: 40, vertical: 24),

  /// Controls how the contents of the dialog are clipped (or not) to the
  /// given shape.
  ///
  /// See the enum `Clip` for details of all possible options and their
  /// common use cases.
  ///
  /// Defaults to Clip.none, and must not be null.
  final Clip clipBehavior = Clip.none,

  /// The shape of this dialog's border.
  ///
  /// Defines the dialog's Material.shape.
  ///
  /// The default shape is a RoundedRectangleBorder with a radius of 4.0.
  final ShapeBorder? shape,

  /// The background transparency color of the dialog barrier.
  ///
  /// Defaults to [Colors.black12] which is considerably lighter than the
  /// standard [Colors.black54] and allows us to see the impact of selected
  /// color on app behind the dialog. If this is not desired, set it back to
  /// [Colors.black54] when you call [showPickerDialog], or make it even more
  /// transparent.
  ///
  /// You can also make the barrier completely transparent.
  Color barrierColor = Colors.black12,

  /// If true, the dialog can be closed by clicking outside it.
  ///
  /// Defaults to true.
  bool barrierDismissible = true,

  /// The `barrierLabel` argument is the semantic label used for a dismissible
  /// barrier. This argument defaults to `null`.
  String? barrierLabel,

  /// The `useSafeArea` argument is used to indicate if the dialog should only
  /// display in 'safe' areas of the screen not used by the operating system
  /// (see [SafeArea] for more details).
  ///
  /// Default to `true` by default, which means the dialog will not overlap
  /// operating system areas. If it is set to `false` the dialog will only
  /// be constrained by the screen size.
  bool useSafeArea = true,

  /// The `routeSettings` argument is passed to [showGeneralDialog],
  /// see [RouteSettings] for details.
  RouteSettings? routeSettings,

  /// Offset anchorPoint for the dialog.
  Offset? anchorPoint,

  /// The [transitionBuilder] argument is used to define how the route
  /// arrives on and leaves off the screen.
  ///
  /// If this transition is not specified, the default Material platform
  /// transition builder for [showDialog] is used.
  RouteTransitionsBuilder? transitionBuilder,

  /// The [transitionDuration] argument is used to determine how long it takes
  /// for the route to arrive on or leave off the screen.
  ///
  /// It only has any effect when a custom `transitionBuilder`is used.
  ///
  /// This argument defaults to 200 milliseconds.
  Duration transitionDuration = const Duration(milliseconds: 200),

  /// You can provide BoxConstraints to constrain the size of the dialog.
  ///
  /// You might want to do this at least for the height, otherwise
  /// the dialog height might jump up and down jarringly if its size changes
  /// when user changes the picker type with the selector.ยจ
  ///
  /// Normally you would not change the picker's content element sizes after
  /// you have determined what works in your implementation. You can usually
  /// figure out a good dialog box size that works well for your use case,
  /// for all active pickers, instead of allowing the color picker dialog
  /// to auto size itself, which it will do if no constraints are defined.
  BoxConstraints? constraints,
}) async {
  assert(debugCheckHasMaterialLocalizations(context),
      'A context with Material localizations is required');
  // Get the Material localizations.
  final MaterialLocalizations translate = MaterialLocalizations.of(context);

  // Make the dialog OK button.
  final String okButtonLabel =
      actionButtons.dialogOkButtonLabel ?? translate.okButtonLabel;
  final Text okButtonContent = Text(okButtonLabel);
  Widget okButton;
  switch (actionButtons.dialogOkButtonType) {
    case ColorPickerActionButtonType.text:
      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 = 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 = 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 = Text(cancelButtonLabel);
  Widget cancelButton;
  switch (actionButtons.dialogCancelButtonType) {
    case ColorPickerActionButtonType.text:
      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 = 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 = 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
  bool colorWasSelected = false;

  // Determine OK-Cancel button order.
  final TargetPlatform platform = Theme.of(context).platform;
  final bool okIsLeft = (platform == TargetPlatform.windows &&
          actionButtons.dialogActionOrder ==
              ColorPickerActionButtonOrder.adaptive) ||
      actionButtons.dialogActionOrder ==
          ColorPickerActionButtonOrder.okIsLeft;

  // Put or [ColorPicker] instance `this` in an `AlertDialog` using all
  // to it assigned and above defined properties.
  Widget dialog = AlertDialog(
    title: title,
    titlePadding: titlePadding,
    titleTextStyle: titleTextStyle,
    content: constraints == null
        ? this
        : ConstrainedBox(
            constraints: constraints,
            child: this,
          ),
    contentPadding: contentPadding,
    actions: actionButtons.dialogActionButtons
        ? <Widget>[
            if (okIsLeft) ...<Widget>[
              okButton,
              if (!actionButtons.dialogActionOnlyOkButton) cancelButton,
            ] else ...<Widget>[
              if (!actionButtons.dialogActionOnlyOkButton) cancelButton,
              okButton,
            ]
          ]
        : null,
    actionsPadding: actionsPadding,
    buttonPadding: buttonPadding,
    backgroundColor: backgroundColor,
    elevation: elevation,
    shadowColor: shadowColor,
    surfaceTintColor: surfaceTintColor,
    semanticLabel: semanticLabel,
    insetPadding: insetPadding,
    clipBehavior: clipBehavior,
    shape: shape,
    scrollable: true,
  );

  // No `transitionBuilder` give, then use
  // the platform and Material2/3 dependent default MaterialPage route
  // transition via `showDialog`, as in all versions before 3.0.0.
  if (transitionBuilder == null) {
    await showDialog<bool>(
        context: context,
        barrierDismissible: barrierDismissible,
        barrierColor: barrierColor,
        barrierLabel: barrierLabel,
        useSafeArea: useSafeArea,
        useRootNavigator: actionButtons.useRootNavigator,
        routeSettings: routeSettings,
        anchorPoint: anchorPoint,
        builder: (BuildContext context) {
          return dialog;
        }).then((bool? value) {
      // If the dialog return value was null, then we got here by a
      // barrier dismiss, then we set the return value to false.
      colorWasSelected = value ?? false;
    });
  }
  // If a `transitionBuilder` is given, we use `showGeneralDialog` using the
  // given `transitionBuilder` and a custom `pageBuilder`, conditionally
  // wrapping `SafeArea` around or AlertDialog widget and capturing the
  // current theme that we and wrapping it around the page builder.
  else {
    final CapturedThemes themes = InheritedTheme.capture(
      from: context,
      to: Navigator.of(
        context,
        rootNavigator: actionButtons.useRootNavigator,
      ).context,
    );
    if (useSafeArea) {
      dialog = SafeArea(child: dialog);
    }
    await showGeneralDialog<bool>(
      context: context,
      barrierDismissible: barrierDismissible,
      barrierColor: barrierColor,
      barrierLabel: barrierLabel ??
          MaterialLocalizations.of(context).modalBarrierDismissLabel,
      useRootNavigator: actionButtons.useRootNavigator,
      routeSettings: routeSettings,
      anchorPoint: anchorPoint,
      transitionBuilder: transitionBuilder,
      transitionDuration: transitionDuration,
      pageBuilder: (BuildContext context, Animation<double> animation1,
          Animation<double> animation2) {
        return themes.wrap(Builder(
          builder: (BuildContext context) => dialog,
        ));
      },
    ).then((bool? value) {
      // If the dialog return value was null, then we got here by a
      // barrier dismiss, then we set the return value to false.
      colorWasSelected = value ?? false;
    });
  }
  return colorWasSelected;
}