showPickerDialog method Null safety

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 = const EdgeInsets.symmetric(horizontal: 16),
  7. EdgeInsetsGeometry buttonPadding = const EdgeInsets.all(16),
  8. Color? backgroundColor,
  9. double? elevation,
  10. String? semanticLabel,
  11. EdgeInsets insetPadding = const EdgeInsets.symmetric(horizontal: 40, vertical: 24),
  12. Clip clipBehavior = Clip.none,
  13. ShapeBorder? shape,
  14. bool barrierDismissible = true,
  15. Color barrierColor = Colors.black12,
  16. bool useSafeArea = true,
  17. @Deprecated('This property is no longer set here and has no function if assigned here. ' 'From version 2.1.0 it must be defined via same property in configuration ' 'class ColorPickerActionButtons(useRootNavigator).') bool useRootNavigator = true,
  18. RouteSettings? routeSettings,
  19. 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. 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.headline6] 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 `EdgeInsets.symmetric(horizontal: 16)`.
  final EdgeInsetsGeometry actionsPadding =
      const EdgeInsets.symmetric(horizontal: 16),

  /// 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 `EdgeInsets.all(16)`.
  final EdgeInsetsGeometry buttonPadding = const EdgeInsets.all(16),

  /// 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 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,

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

  /// 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,

  /// 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,

  /// Usage of [useRootNavigator] here is deprecated.
  ///
  /// 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,
  /// 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 the [ColorPicker] widget itself,
  /// as well as by built-in dialogs.
  ///
  /// The default behavior has not been changed, the setting still defaults
  /// to using dialogs that use the root navigator, but now the pop
  /// functions work as intended.
  ///
  /// If you for some reason have used none root navigators for the built-in
  /// dialogs in previous version, you need to set
  /// `ColorPickerActionButtons(useRootNavigator: false)` in
  /// `ColorPicker(actionButtons)` or `showColorPickerDialog(actionButtons)`.
  @Deprecated(
    'This property is no longer set here and has no function if assigned here. '
    'From version 2.1.0 it must be defined via same property in configuration '
    'class ColorPickerActionButtons(useRootNavigator).',
  )
      bool useRootNavigator = true,

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

  /// 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 Widget _okButtonContent = Wrap(
    alignment: WrapAlignment.center,
    crossAxisAlignment: WrapCrossAlignment.center,
    children: <Widget>[
      if (actionButtons.dialogActionIcons)
        Padding(
          padding: const EdgeInsetsDirectional.only(end: 4),
          child: Icon(actionButtons.okIcon),
        ),
      Text(_okButtonLabel),
    ],
  );
  Widget _okButton;
  switch (actionButtons.dialogOkButtonType) {
    case ColorPickerActionButtonType.text:
      _okButton = TextButton(
        onPressed: () {
          Navigator.of(context, rootNavigator: actionButtons.useRootNavigator)
              .pop(true);
        },
        child: _okButtonContent,
      );
      break;
    case ColorPickerActionButtonType.outlined:
      _okButton = OutlinedButton(
        onPressed: () {
          Navigator.of(context, rootNavigator: actionButtons.useRootNavigator)
              .pop(true);
        },
        child: _okButtonContent,
      );
      break;
    case ColorPickerActionButtonType.elevated:
      _okButton = ElevatedButton(
        onPressed: () {
          Navigator.of(context, rootNavigator: actionButtons.useRootNavigator)
              .pop(true);
        },
        child: _okButtonContent,
      );
      break;
  }

  // Make the dialog OK button.
  final String _cancelButtonLabel =
      actionButtons.dialogCancelButtonLabel ?? translate.cancelButtonLabel;
  final Widget _cancelButtonContent = Wrap(
    alignment: WrapAlignment.center,
    crossAxisAlignment: WrapCrossAlignment.center,
    children: <Widget>[
      if (actionButtons.dialogActionIcons)
        Padding(
          padding: const EdgeInsetsDirectional.only(end: 4),
          child: Icon(actionButtons.closeIcon),
        ),
      Text(_cancelButtonLabel),
    ],
  );
  Widget _cancelButton;
  switch (actionButtons.dialogCancelButtonType) {
    case ColorPickerActionButtonType.text:
      _cancelButton = TextButton(
        onPressed: () {
          Navigator.of(context, rootNavigator: actionButtons.useRootNavigator)
              .pop(false);
        },
        child: _cancelButtonContent,
      );
      break;
    case ColorPickerActionButtonType.outlined:
      _cancelButton = OutlinedButton(
        onPressed: () {
          Navigator.of(context, rootNavigator: actionButtons.useRootNavigator)
              .pop(false);
        },
        child: _cancelButtonContent,
      );
      break;
    case ColorPickerActionButtonType.elevated:
      _cancelButton = ElevatedButton(
        onPressed: () {
          Navigator.of(context, rootNavigator: actionButtons.useRootNavigator)
              .pop(false);
        },
        child: _cancelButtonContent,
      );
      break;
  }

  // False if dialog cancelled, true if color selected
  bool _colorWasSelected = false;

  await showDialog<bool>(
      context: context,
      barrierDismissible: barrierDismissible,
      barrierColor: barrierColor,
      useSafeArea: useSafeArea,
      useRootNavigator: actionButtons.useRootNavigator,
      routeSettings: routeSettings,
      builder: (BuildContext context) {
        return AlertDialog(
          title: title,
          titlePadding: titlePadding,
          titleTextStyle: titleTextStyle,
          content: constraints == null
              ? this
              : ConstrainedBox(
                  constraints: constraints,
                  child: this,
                ),
          contentPadding: contentPadding,
          actions: actionButtons.dialogActionButtons
              ? <Widget>[
                  _cancelButton,
                  _okButton,
                ]
              : null,
          actionsPadding: actionsPadding,
          buttonPadding: buttonPadding,
          backgroundColor: backgroundColor,
          elevation: elevation,
          semanticLabel: semanticLabel,
          insetPadding: insetPadding,
          clipBehavior: clipBehavior,
          shape: shape,
          scrollable: true,
        );
      }).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;
}