content: import 'package:flutter/material.dart';
  
  import 'flex_scheme_color.dart';
  import 'flex_scheme_data.dart';
  
  /// Enum used to define the order, from left to right, of the theme mode
  /// selection buttons on the [FlexThemeModeSwitch].
  enum FlexThemeModeButtonOrder {
    /// The theme mode buttons are in order: Light, Dark, System.
    lightDarkSystem,
  
    /// The theme mode buttons are in order: Light, System, Dark.
    lightSystemDark,
  
    /// The theme mode buttons are in order: Dark, System, Light.
    darkSystemLight,
  
    /// The theme mode buttons are in order: Dark, Light, System.
    darkLightSystem,
  }
  
  /// A 3-way Light, Dark and System theme-mode switch widget.
  ///
  /// Pass in current selected [themeMode] value and the [onThemeModeChanged]
  /// callback will return the selected theme-mode.
  ///
  /// Requires a [FlexSchemeData] object via [flexSchemeData]. You
  /// should pass it one that has the same value as the one currently used by
  /// your `FlexColorScheme` based theme. Doing so, the option toggle buttons
  /// will contain the scheme colors for both the light and dark theme
  /// included in the option toggle button for each theme mode.
  ///
  /// They scheme colors for each button will be in the order:
  /// ```text
  /// | Primary           | Secondary       |
  /// | Primary container | Tertiary        |
  /// ```
  /// In the option button. In the system choice the 2nd column will instead
  /// contain the dark mode primary and secondary colors, thus combining both
  /// theme mode's color scheme.
  ///
  /// All other properties are optional, but it has extensive customization
  /// options in order create a theme mode switch widget with a customized look,
  /// see API reference for more info.
  class FlexThemeModeSwitch extends StatelessWidget {
    /// Default constructor.
    const FlexThemeModeSwitch({
      super.key,
      required this.themeMode,
      required this.onThemeModeChanged,
      required this.flexSchemeData,
      this.title,
      this.hasTitle = true,
      this.labelLight = 'LIGHT',
      this.labelDark = 'DARK',
      this.labelSystem = 'SYSTEM',
      this.showSystemMode = true,
      this.buttonOrder = FlexThemeModeButtonOrder.lightDarkSystem,
      this.selectedLabelStyle,
      this.unselectedLabelStyle,
      this.labelAbove = true,
      this.backgroundLight,
      this.backgroundDark,
      this.backgroundSystem,
      this.selectedBorder,
      this.unselectedBorder,
      this.selectedElevation = 0,
      this.unselectedElevation = 0,
      this.optionButtonPadding,
      this.optionButtonMargin,
      this.optionButtonBorderRadius = 5,
      this.height = 24,
      this.width = 24,
      this.borderRadius = 4,
      this.padding,
      this.hoverColor,
      this.focusColor,
    })  : assert(selectedElevation >= 0.0, 'Selected elevation must be >= 0.0'),
          assert(
              unselectedElevation >= 0.0, 'Unselected elevation must be >= 0.0');
  
    /// The current themeMode option button to be marked as selected.
    final ThemeMode themeMode;
  
    /// The new theme mode that was selected using the 3 option buttons.
    final ValueChanged<ThemeMode> onThemeModeChanged;
  
    /// The FlexSchemeData used to colorize the four colors in each theme mode
    /// option button.
    final FlexSchemeData flexSchemeData;
  
    /// A leading title widget for the theme mode switch.
    /// Defaults to `Text('Theme mode')` with style subtitle1, if it is null.
    final Widget? title;
  
    /// To use the widget without any leading title set to false;
    ///
    /// Defaults to true.
    final bool hasTitle;
  
    /// Option label for theme mode light.
    ///
    /// Defaults to 'LIGHT', assign null to omit the label.
    final String labelLight;
  
    /// Option label for theme mode dark.
    ///
    /// Defaults to 'DARK', assign null to omit the label.
    final String labelDark;
  
    /// Option label for theme mode system.
    ///
    /// Defaults to 'SYSTEM', assign null to omit the label.
    final String labelSystem;
  
    /// Include a theme option button for selecting system theme mode.
    ///
    /// Defaults to true.
    ///
    /// If set to false, only a light and dark-mode options are available.
    final bool showSystemMode;
  
    /// Set the order of the system mode selection buttons.
    ///
    /// Order follows the enum label from left to right. If [showSystemMode] is
    /// false, the system button is left out in the resulting order.
    ///
    /// Defaults to [FlexThemeModeButtonOrder.lightDarkSystem].
    final FlexThemeModeButtonOrder buttonOrder;
  
    /// Optional text style for the theme mode selected label.
    ///
    /// If null, default to Theme.of(context).textTheme.caption).
    final TextStyle? selectedLabelStyle;
  
    /// Optional text style for the theme mode unselected label.
    ///
    /// If null, default to Theme.of(context).textTheme.caption).
    final TextStyle? unselectedLabelStyle;
  
    /// Label is positioned above the option button.
    ///
    /// If true, the label will be above the option button, if false the
    /// label will be below the option button. Defaults to true.
    final bool labelAbove;
  
    /// Background color for the light theme option button.
    ///
    /// If null, defaults to `Colors.white`.
    final Color? backgroundLight;
  
    /// Background color for the dark theme option button.
    ///
    /// If null, defaults to `Colors.grey[850]`.
    final Color? backgroundDark;
  
    /// Background color for the system theme option button.
    ///
    /// If null, defaults to `Colors.grey[500]`.
    final Color? backgroundSystem;
  
    /// Border side for the selected option state.
    ///
    /// If null, defaults to
    /// `BorderSide(color: Theme.of(context).colorScheme.primary, width: 4)`.
    final BorderSide? selectedBorder;
  
    /// Border side for the unselected option state.
    ///
    /// If null, defaults to `BorderSide(color: Theme.of(context).dividerColor)`.
    final BorderSide? unselectedBorder;
  
    /// The elevation of the option button when selected.
    ///
    /// Defaults to 0 dp.
    final double selectedElevation;
  
    /// The elevation of the option button when unselected.
    ///
    /// Defaults to 0 dp.
    final double unselectedElevation;
  
    /// Padding around the option button.
    ///
    /// If null, defaults to `const EdgeInsetsDirectional.only(start: 6)`.
    final EdgeInsetsGeometry? optionButtonPadding;
  
    /// The margin inside the option button, before the scheme color boxes.
    ///
    /// If null, defaults to `const EdgeInsets.all(4)`.
    final EdgeInsetsGeometry? optionButtonMargin;
  
    /// The circular borderRadius of the option button.
    ///
    /// Defaults to 5 dp.
    final double optionButtonBorderRadius;
  
    /// The height of an individual scheme color box.
    ///
    /// Defaults to 24 dp.
    final double height;
  
    /// The width of an individual scheme color box.
    ///
    /// Defaults to 24 dp.
    final double width;
  
    /// The circular borderRadius of an individual scheme color box.
    ///
    /// Defaults to 3 dp.
    final double borderRadius;
  
    /// Padding around an individual scheme color box.
    ///
    /// If null, default to `const EdgeInsets.all(3)`
    final EdgeInsetsGeometry? padding;
  
    /// The InkWell hover color for the option buttons.
    ///
    /// If null, defaults to `Color(0x50BCBCBC)` in light mode and to
    /// `Color(0x99555555)` dark mode.
    final Color? hoverColor;
  
    /// The InkWell focus color for the option button.
    ///
    /// If not defined, defaults to Theme.of(context).focusColor, via InkWell
    /// default behavior.
    final Color? focusColor;
  
    @override
    Widget build(BuildContext context) {
      final FlexThemeModeOptionButton lightButton = FlexThemeModeOptionButton(
        flexSchemeColor: flexSchemeData.light,
        backgroundColor: backgroundLight ?? Colors.white,
        label: labelLight,
        labelStyle: themeMode == ThemeMode.light
            ? selectedLabelStyle
            : unselectedLabelStyle,
        labelAbove: labelAbove,
        selected: themeMode == ThemeMode.light,
        onSelect: () {
          onThemeModeChanged(ThemeMode.light);
        },
        selectedBorder: selectedBorder,
        unselectedBorder: unselectedBorder,
        elevation: themeMode == ThemeMode.light
            ? selectedElevation
            : unselectedElevation,
        optionButtonPadding: optionButtonPadding,
        optionButtonMargin: optionButtonMargin,
        optionButtonBorderRadius: optionButtonBorderRadius,
        height: height,
        width: width,
        borderRadius: borderRadius,
        padding: padding,
        hoverColor: hoverColor,
        focusColor: focusColor,
      );
  
      final FlexThemeModeOptionButton darkButton = FlexThemeModeOptionButton(
        flexSchemeColor: flexSchemeData.dark,
        backgroundColor: backgroundDark ?? Colors.grey[850]!,
        label: labelDark,
        labelStyle: themeMode == ThemeMode.dark
            ? selectedLabelStyle
            : unselectedLabelStyle,
        labelAbove: labelAbove,
        selected: themeMode == ThemeMode.dark,
        onSelect: () {
          onThemeModeChanged(ThemeMode.dark);
        },
        selectedBorder: selectedBorder,
        unselectedBorder: unselectedBorder,
        elevation:
            themeMode == ThemeMode.dark ? selectedElevation : unselectedElevation,
        optionButtonPadding: optionButtonPadding,
        optionButtonMargin: optionButtonMargin,
        optionButtonBorderRadius: optionButtonBorderRadius,
        height: height,
        width: width,
        borderRadius: borderRadius,
        padding: padding,
        hoverColor: hoverColor,
        focusColor: focusColor,
      );
  
      // Option button for system theme mode. For this case we show
      // the primary and variant color of light and dark theme, ie not all
      // four colors of either mode and we place them on a different
      // background to indicate that we do not actually know if it will
      // be light or dark, as that is controlled by the host system.
      final FlexThemeModeOptionButton systemButton = FlexThemeModeOptionButton(
        flexSchemeColor: FlexSchemeColor(
          primary: flexSchemeData.light.primary,
          primaryContainer: flexSchemeData.dark.primary,
          secondary: flexSchemeData.light.secondary,
          secondaryContainer: flexSchemeData.dark.secondary,
        ),
        backgroundColor: backgroundSystem ?? Colors.grey[500]!,
        label: labelSystem,
        labelStyle: themeMode == ThemeMode.system
            ? selectedLabelStyle
            : unselectedLabelStyle,
        labelAbove: labelAbove,
        selected: themeMode == ThemeMode.system,
        onSelect: () {
          onThemeModeChanged(ThemeMode.system);
        },
        selectedBorder: selectedBorder,
        unselectedBorder: unselectedBorder,
        elevation: themeMode == ThemeMode.system
            ? selectedElevation
            : unselectedElevation,
        optionButtonPadding: optionButtonPadding,
        optionButtonMargin: optionButtonMargin,
        optionButtonBorderRadius: optionButtonBorderRadius,
        height: height,
        width: width,
        borderRadius: borderRadius,
        padding: padding,
        hoverColor: hoverColor,
        focusColor: focusColor,
      );
  
      return Row(
        children: <Widget>[
          if (hasTitle)
            Expanded(
              child: title ??
                  Text(
                    'Theme mode',
                    style: Theme.of(context).textTheme.titleMedium,
                  ),
            ),
          if (buttonOrder ==
              FlexThemeModeButtonOrder.lightDarkSystem) ...<Widget>[
            lightButton,
            darkButton,
            if (showSystemMode) systemButton,
          ],
          if (buttonOrder ==
              FlexThemeModeButtonOrder.darkLightSystem) ...<Widget>[
            darkButton,
            lightButton,
            if (showSystemMode) systemButton,
          ],
          if (buttonOrder ==
              FlexThemeModeButtonOrder.lightSystemDark) ...<Widget>[
            lightButton,
            if (showSystemMode) systemButton,
            darkButton,
          ],
          if (buttonOrder ==
              FlexThemeModeButtonOrder.darkSystemLight) ...<Widget>[
            darkButton,
            if (showSystemMode) systemButton,
            lightButton,
          ],
        ],
      );
    }
  }
  
  /// Stateless widget that draws a box with the 4 colors, primary,
  /// primary variant secondary and secondary variant in the properties of
  /// passed in [flexSchemeColor].
  ///
  /// The widget has a required [selected] bool property for selected and
  /// not selected status. A VoidCallback provides [onSelect] provides
  /// select action callback info.
  ///
  /// The [FlexThemeModeOptionButton] offers a large number of customization
  /// options, see API reference for more info. This widget is typically used
  /// via the [FlexThemeModeSwitch] widget.
  class FlexThemeModeOptionButton extends StatefulWidget {
    /// Default constructor.
    const FlexThemeModeOptionButton({
      super.key,
      required this.flexSchemeColor,
      this.backgroundColor,
      this.label,
      this.semanticLabel,
      this.labelStyle,
      this.labelAbove = true,
      required this.selected,
      this.onSelect,
      this.selectedBorder,
      this.unselectedBorder,
      this.elevation = 0,
      this.optionButtonPadding,
      this.optionButtonMargin,
      this.optionButtonBorderRadius = 5,
      this.height = 24,
      this.width = 24,
      this.borderRadius = 4,
      this.padding,
      this.hoverColor,
      this.focusColor,
      this.setFocusOnTap,
    }) : assert(elevation >= 0.0, 'Elevation must be >= 0.0');
  
    /// The scheme colors used to colorize the option button's four colors.
    final FlexSchemeColor flexSchemeColor;
  
    /// The background color of the option button.
    ///
    /// Defaults to Material default background color if null.
    final Color? backgroundColor;
  
    /// Optional text label for the button.
    ///
    /// If not defined, the label is omitted.
    final String? label;
  
    /// Optional semantic label for the button.
    ///
    /// If null, then defaults to [label], if it is also null, it defaults
    /// to "Theme mode option button".
    final String? semanticLabel;
  
    /// Optional text style for the [label].
    ///
    /// If null, default to Theme.of(context).textTheme.caption).
    final TextStyle? labelStyle;
  
    /// Label is positioned above the option button.
    ///
    /// If true, the label will be above the option button, if false the
    /// label will be below the option button. Defaults to true.
    final bool labelAbove;
  
    /// The button is selected.
    final bool selected;
  
    /// The button was clicked and selected.
    ///
    /// The optional callback allows for the button to be used eg as a trailing
    /// widget in a ListTile. Keep it null to not have any callback and use the
    /// click event in the parent instead.
    final VoidCallback? onSelect;
  
    /// Border side for the selected option state.
    ///
    /// If null, defaults to
    /// `BorderSide(color: Theme.of(context).colorScheme.primary, width: 4)`.
    final BorderSide? selectedBorder;
  
    /// Border side for the unselected option state.
    ///
    /// If null, defaults to `BorderSide(color: Theme.of(context).dividerColor)`.
    final BorderSide? unselectedBorder;
  
    /// The elevation of the option button.
    ///
    /// Defaults to 0 dp.
    final double elevation;
  
    /// Padding around the option button.
    ///
    /// If null, defaults to `const EdgeInsetsDirectional.only(start: 6)`.
    final EdgeInsetsGeometry? optionButtonPadding;
  
    /// The margin inside the option button, before the scheme color boxes.
    ///
    /// If null, defaults to `const EdgeInsets.all(2)`.
    final EdgeInsetsGeometry? optionButtonMargin;
  
    /// The circular borderRadius of the option button,
    ///
    /// Defaults to 5 dp.
    final double optionButtonBorderRadius;
  
    /// The height of an individual scheme color box.
    ///
    /// Defaults to 24 dp.
    final double height;
  
    /// The width of an individual scheme color box.
    ///
    /// Defaults to 24 dp.
    final double width;
  
    /// The circular borderRadius of an individual scheme color box.
    ///
    /// Defaults to 4 dp.
    final double borderRadius;
  
    /// Padding around an individual scheme color box.
    ///
    /// If null, default to `const EdgeInsets.all(3)`
    final EdgeInsetsGeometry? padding;
  
    /// The InkWell hover color for the option button.
    ///
    /// If null, defaults to `Color(0x50BCBCBC)` in light mode and to
    /// `Color(0x99555555)` in dark-mode.
    ///
    /// If using custom theming that should be used, pass in
    /// Theme.of(context).hoverColor.
    final Color? hoverColor;
  
    /// The InkWell focus color for the option button.
    ///
    /// If not defined, defaults to Theme.of(context).focusColor, via InkWell
    /// default behavior.
    final Color? focusColor;
  
    /// If true, sets focus to the button when it is tapped.
    ///
    /// If not defined, default to false.
    final bool? setFocusOnTap;
  
    @override
    State<FlexThemeModeOptionButton> createState() =>
        _FlexThemeModeOptionButtonState();
  }
  
  class _FlexThemeModeOptionButtonState extends State<FlexThemeModeOptionButton> {
    late final FocusNode _focusNode;
    // Paddings needed for the size of the buttons so we can make a SizedBox
    // with the Ink in a Sized transparent Material on top of the buttons.
    double padX = 0;
    double padY = 0;
    // Default padding around an individual scheme color box.
    static const EdgeInsets _padding = EdgeInsets.all(3);
    // The default margin inside the option button, before the scheme color boxes.
    static const EdgeInsets _optionButtonMargin = EdgeInsets.all(4);
  
    void updatePaddings() {
      final EdgeInsetsGeometry padding = widget.padding ?? _padding;
      if (padding is EdgeInsets) {
        padX = (padding.left + padding.right) * 2;
        padY = (padding.top + padding.bottom) * 2;
      } else if (padding is EdgeInsetsDirectional) {
        padX = (padding.start + padding.end) * 2;
        padY = (padding.top + padding.bottom) * 2;
      }
      final EdgeInsetsGeometry optionButtonMargin =
          widget.optionButtonMargin ?? _optionButtonMargin;
      if (optionButtonMargin is EdgeInsets) {
        padX += optionButtonMargin.left + optionButtonMargin.right;
        padY += optionButtonMargin.top + optionButtonMargin.bottom;
      } else if (optionButtonMargin is EdgeInsetsDirectional) {
        padX += optionButtonMargin.start + optionButtonMargin.end;
        padY += optionButtonMargin.top + optionButtonMargin.bottom;
      }
    }
  
    @override
    void initState() {
      super.initState();
      _focusNode = FocusNode();
      updatePaddings();
    }
  
    @override
    void dispose() {
      _focusNode.dispose();
      super.dispose();
    }
  
    @override
    void didUpdateWidget(covariant FlexThemeModeOptionButton oldWidget) {
      super.didUpdateWidget(oldWidget);
      if (widget.padding != oldWidget.padding ||
          widget.optionButtonMargin != oldWidget.optionButtonMargin) {
        updatePaddings();
      }
    }
  
    @override
    Widget build(BuildContext context) {
      final ThemeData theme = Theme.of(context);
      final Color effectiveHoverColor = widget.hoverColor ??
          (theme.brightness == Brightness.light
              ? const Color(0x50BCBCBC)
              : const Color(0x99555555));
  
      return Semantics(
        label: widget.semanticLabel ?? widget.label ?? 'Theme mode option button',
        selected: widget.selected,
        button: true,
        enabled: true,
        child: Padding(
          padding: widget.optionButtonPadding ??
              const EdgeInsetsDirectional.only(start: 6),
          child: Row(
            children: <Widget>[
              Column(
                children: <Widget>[
                  if (widget.label != null && widget.labelAbove)
                    Text(
                      widget.label!,
                      style: widget.labelStyle ?? theme.textTheme.bodySmall,
                      semanticsLabel: '', // Is set on button instead
                    ),
                  Material(
                    elevation: widget.elevation,
                    color: widget.backgroundColor,
                    clipBehavior: Clip.antiAlias,
                    shape: RoundedRectangleBorder(
                      borderRadius: BorderRadius.all(
                        Radius.circular(widget.optionButtonBorderRadius),
                      ),
                      side: widget.selected
                          ? widget.selectedBorder ??
                              BorderSide(
                                color: theme.colorScheme.primary,
                                width: 4,
                              )
                          : widget.unselectedBorder ??
                              BorderSide(
                                color: theme.dividerColor,
                              ),
                    ),
                    child: Stack(
                      // alignment: AlignmentDirectional.center,
                      children: <Widget>[
                        Padding(
                          padding:
                              widget.optionButtonMargin ?? _optionButtonMargin,
                          child: Column(
                            children: <Widget>[
                              Row(
                                children: <Widget>[
                                  _SchemeColorBox(
                                    color: widget.flexSchemeColor.primary,
                                    height: widget.height,
                                    width: widget.width,
                                    borderRadius: widget.borderRadius,
                                    padding: widget.padding ?? _padding,
                                  ),
                                  _SchemeColorBox(
                                    color: widget.flexSchemeColor.secondary,
                                    height: widget.height,
                                    width: widget.width,
                                    borderRadius: widget.borderRadius,
                                    padding: widget.padding ?? _padding,
                                  ),
                                ],
                              ),
                              Row(
                                children: <Widget>[
                                  _SchemeColorBox(
                                    color:
                                        widget.flexSchemeColor.primaryContainer,
                                    height: widget.height,
                                    width: widget.width,
                                    borderRadius: widget.borderRadius,
                                    padding: widget.padding ?? _padding,
                                  ),
                                  _SchemeColorBox(
                                    color: widget.flexSchemeColor.tertiary,
                                    height: widget.height,
                                    width: widget.width,
                                    borderRadius: widget.borderRadius,
                                    padding: widget.padding ?? _padding,
                                  ),
                                ],
                              ),
                            ],
                          ),
                        ),
                        Material(
                          type: MaterialType.transparency,
                          child: InkWell(
                            focusNode: _focusNode,
                            hoverColor: effectiveHoverColor,
                            focusColor: widget.focusColor,
                            onTap: () {
                              if (widget.setFocusOnTap ?? false) {
                                _focusNode.requestFocus();
                              }
                              widget.onSelect?.call();
                            },
                            child: SizedBox(
                              width: widget.width * 2 + padX,
                              height: widget.height * 2 + padY,
                            ),
                          ),
                        )
                      ],
                    ),
                  ),
                  // ),
                  if (widget.label != null && !widget.labelAbove)
                    Text(
                      widget.label!,
                      style: widget.labelStyle ?? theme.textTheme.bodySmall,
                      semanticsLabel: '', // Is set on button instead
                    ),
                ],
              ),
            ],
          ),
        ),
      );
    }
  }
  
  /// Draws a box with rounded corners with given background [color].
  ///
  /// Have defaults for standard use case, but may be modified via parent.
  class _SchemeColorBox extends StatelessWidget {
    /// Default constructor.
    const _SchemeColorBox({
      required this.color,
      this.height = 24,
      this.width = 24,
      this.borderRadius = 4,
      this.padding = const EdgeInsets.all(3),
    });
  
    /// The background color used to draw an individual scheme color box.
    ///
    /// Required, cannot be null.
    final Color color;
  
    /// The height of an individual scheme color box.
    ///
    /// Defaults to 24 dp.
    final double height;
  
    /// The width of an individual scheme color box.
    ///
    /// Defaults to 24 dp.
    final double width;
  
    /// The circular borderRadius of an individual scheme color box.
    ///
    /// Defaults to 4 dp.
    final double borderRadius;
  
    /// Padding around an individual scheme color box.
    ///
    /// Defaults to `const EdgeInsets.all(3)`.
    final EdgeInsetsGeometry padding;
  
    @override
    Widget build(BuildContext context) {
      return Padding(
        padding: padding,
        child: Container(
          height: height,
          width: width,
          decoration: BoxDecoration(
            color: color,
            borderRadius: BorderRadius.all(Radius.circular(borderRadius)),
          ),
        ),
      );
    }
  }
  