toTheme property

ThemeData toTheme

Returns the ThemeData object defined by FlexColorScheme properties and its theming logic.

After you have defined your scheme with FlexColorScheme or one of its factories FlexColorScheme.light, FlexColorScheme.dark, use toTheme to get the resulting ThemeData object defined by your FlexColorScheme definition.

The returned ThemeData contains some opinionated modifications and dark theme corrections, compared to what you get if you would just use the standard ThemeData.from a ColorScheme. You can always override these with your own theme modifications by using the ThemeData.copyWith method on the resulting theme.

The differences from the standard ThemeData.from factory are:

  • ScaffoldBackground has its own color property in FlexColorScheme and can if so desired differ from the ColorScheme.background color. In the branding implementation, the scaffoldBackground typically gets no primary branding applied, only in the heavy choice is there a small amount. Whereas background in a FlexColorScheme receives the most color branding of the surface colors. This fits well for where the background color is used on Material in Widgets, but it does not go so well together with scaffoldBackground, which is the reason why it has its own color value in this implementation.

  • The default dialogBackgroundColor uses the ColorScheme.surface color instead of the ColorScheme.background. The background color needed the strongest branding when branding is used, but this did not look so good on dialogs, so its color choice was changed to surface instead, that gets lighter branding in FlexColorScheme when it is used. With standard Material surface colors the background and surface colors are the same, so there is no difference when using the default background and surface colors.

  • The indicatorColor is same as effectiveTabColor which uses a function with logic to determine its color bases on if a TabBarTheme was selected that should work on current app bar background color, or on surface/background colors.

  • For toggleableActiveColor the ColorScheme.secondary color is used. The Flutter default just uses the default ThemeData colors and not the actual colors you define in the ColorScheme you create your theme from. Perhaps an oversight in Flutter? See issue: https:///github.com/flutter/flutter/issues/65782.

  • Flutter themes created with ThemeData.from does not define any color scheme related color for the primaryColorDark color, this method does. See issue: https:///github.com/flutter/flutter/issues/65782. The ThemeData.from leaves this color at ThemeData factory default, this may not match your scheme. Widgets seldom use this color, so the issue is rarely seen.

  • Flutter themes created with ThemeData.from does not define any color scheme based color for the primaryColorLight color, this method does. See issue: https:///github.com/flutter/flutter/issues/65782. The ThemeData.from leaves this color at ThemeData factory default this may not match your scheme. Widgets seldom use this color, so the issue is rarely seen.

  • Flutter themes created with ThemeData.from does not define any color scheme based color for the secondaryHeaderColor color, this method does. See issue: https:///github.com/flutter/flutter/issues/65782. ThemeData.from leaves this color at ThemeData factory default this may not match your scheme. Widgets seldom use this color, so the issue is rarely seen.

  • Background color for AppBarTheme can use a custom color theme in both light and dark themes, that is not dependent on theme primary or surface color. In the versions prior to Flutter 2.0.0 doing this was difficult to do. As presented in https://github.com/flutter/flutter/issues/50606 A new feature in Flutter 2.0.0 implemented via: https://github.com/flutter/flutter/pull/71184 makes this easy and better. FlexColorScheme's implementation has been changed to use this new AppBarTheme feature starting with version 2.0.0-nullsafety.2.

  • The AppBarTheme elevation defaults to 0, an iOs style influenced opinionated choice. It can easily be adjusted directly in the FlexColorScheme definition with property value appBarElevation without creating a sub theme or using copyWith. For the main less used constructor of FlexColorScheme it is required and cannot be null. The FlexColorScheme light and dark factories can be null but it will default to 0 if null.

  • The bottomAppBarColor uses color scheme background color to match the background color of the drawer, bottom navigation bar, possible side menu and system navigation bar on android (if theming of it is used). This is a slight change from the ColorScheme default that uses surface color.

  • The BottomAppBarTheme elevation defaults to appBarElevation or 0 if it is null, an iOs style influenced opinionated choice. It can easily be adjusted directly in the FlexColorScheme definition with property value bottomAppBarElevation without creating a sub theme or using copyWith.

  • In TextSelectionThemeData, the standard for selectionColor is colorScheme.primary with opacity value 0.4 for dark-mode and 0.12 for light mode. Here primary with 0.5 for dark-mode and 0.3 for light mode is used. The standard for selectionHandleColor is colorScheme.primary, here we use the slightly darker shade primaryColorDark instead, which does not have a proper color scheme color value in Flutter standard ColorScheme based themes.

  • A predefined slightly opinionated InputDecorationTheme is used. It sets filled to true and fill color to color scheme primary color with opacity 0.035 in light mode and opacity 0.06 in dark-mode.

  • The property fixTextFieldOutlineLabel is set to true by default, it looks better. The only reason why it is not the default in Flutter, is for default backwards legacy design compatibility.

  • For ThemeData.buttonTheme the entire color scheme is passed to its colorScheme property and it uses textTheme set to ButtonTextTheme.primary, plus minor changes to padding and tap target size. These modifications make the old buttons almost match the default design and look of their corresponding newer buttons. Thus the RaisedButton looks very similar to ElevatedButton, OutlineButton to OutlinedButton and FlatButton to TextButton. There are some differences in margins and looks, especially in dark-mode, but they are very similar.

  • The default theme for Chips contain a design bug that makes the selected ChoiceChip() widget look disabled in dark-mode, regardless if was created with ThemeData or ThemeData.from factory. See issue: https:///github.com/flutter/flutter/issues/65663 The ChipThemeData modification used here fixes the issue.

  • For TabBarTheme, the Flutter standard selected tab and indicator color is onSurface in dark mode and on Primary in light mode, which is designed to fit an AppBar colored TabBar. This is kept, and the default via FlexTabBarStyle.forAppBar style, with a minor modification. If AppBar is "light", then black87 is used, not black, it is the same as the textTheme on AppBar in light app bar brightness. If the FlexTabBarStyle.forBackground style was used, the selected color is always color scheme primary color, which works well on surface, background and scaffold background colors.

    The unselected TabBar color when FlexTabBarStyle.forBackground style is used, is always the onSurface color with 60% opacity. This is also the color if the AppBar background color brightness is light AND its color is white, surface or background colored. Otherwise when the style FlexTabBarStyle.forAppBar is used, the unselected tab bar color is the selected tab color with 70% opacity. This opacity value is the same as Flutter default for the default theme that is also designed for AppBar usage.

  • The BottomNavigationBarThemeData uses color scheme primary color for the selected item in both light and dark theme. Flutter SDK defaults to secondary color in dark mode. Using only primary color is a design used on iOS by default for the bottom navigation bar. We agree and think it looks better as the default choice for apps also in dark mode.

  • Default tooltip theme in Flutter is currently a bit flawed on desktop and web, because it defaults to using a very small font (10 dp). See issue: https:///github.com/flutter/flutter/issues/71429

    The default theming also does not handle multiline tooltips very well. The here used TooltipThemeData theme design corrects both these issues. It uses 12 dp font on desktop and web instead of 10 dp, and some padding instead of a height constraint to ensure that multiline tooltips look nice too.

    FlexColorScheme also includes a new boolean property tooltipsMatchBackground, that can be toggled to not used Flutter's Material default that has a theme mode inverted background. Tooltips using light background in light theme and dark in dark, are commonly used on the Windows desktop platform. You can tie the extra property to used platform to make an automatic platform adaptation of the tooltip style if you like, or give users a preference toggle the tooltip style to their liking.

  • The property transparentStatusBar is set to true by default and used to make to the AppBar one-toned on Android device like on iOS. Set it to false to restore default Android design.

    It would be nice if we could also make the system navigation button area on Android transparent via a theme, but it does not work. The style is doable, but requires modifying Android config files, not possible from Flutter only (as per current information). Related issue: https:///github.com/flutter/flutter/issues/69999.

    FlexColorScheme offers a static helper themedSystemNavigationBar that allows us to easily create an annotated region for the system navigation bar that uses the active color scheme and theme mode to make it at least use a correctly colored theme colored background for the active theme. See example 5 for a demo on how to use this.

Implementation

ThemeData get toTheme {
  // Use passed in sub-theme config data or a default one if none given.
  final FlexSubThemesData subTheme =
      subThemesData ?? const FlexSubThemesData();

  // Get the effective standard Flutter ColorScheme from the provided
  // brightness and provided or computed or default colors.
  final ColorScheme colorScheme = toScheme;

  // A convenience bool to check if this theme is for light or dark mode
  final bool isDark = colorScheme.brightness == Brightness.dark;

  // Use passed in target platform, else actual host platform.
  final TargetPlatform effectivePlatform = platform ?? defaultTargetPlatform;

  // TODO(rydmike): Remove when default in Flutter, still 2014 in Flutter 2.5.
  // Used Typography deviates from the Flutter standard that _still_ uses the
  // old Typography.material2014 in favor of the newer Typography.material2018
  // as default, if one is not provided, we want the Material 2 correct 2018
  // version to be the default.
  final Typography effectiveTypography =
      typography ?? Typography.material2018(platform: effectivePlatform);

  // We need the text themes locally for the theming, so we must form them
  // fully using the same process that the ThemeData() factory uses.
  TextTheme defTextTheme =
      isDark ? effectiveTypography.white : effectiveTypography.black;

  final bool primaryIsDark =
      ThemeData.estimateBrightnessForColor(colorScheme.primary) ==
          Brightness.dark;
  TextTheme defPrimaryTextTheme =
      primaryIsDark ? effectiveTypography.white : effectiveTypography.black;

  if (fontFamily != null) {
    // ThemeData uses this to apply a font from fontFamily. It works OK, but
    // it resets all typography and it uses regular style and weight
    // for all styles in the text theme. Consider defining the text theme
    // explicitly via textTheme and primaryTextTheme with the custom
    // font applied, at least if you want to use custom fonts and keep the
    // standard  typography, or supply your own complete typography with your
    // custom text theme.
    defTextTheme = defTextTheme.apply(fontFamily: fontFamily);
    defPrimaryTextTheme = defPrimaryTextTheme.apply(fontFamily: fontFamily);
  }

  // TODO(rydmike): Use SDK Material3 and new Flutter APIs when available.
  // TODO(rydmike): Use SDK Material3 TextTheme when available.
  // We are using sub themes and blend colors on text themes. If surfaces and
  // background are not set to use blends, the effect will be slightly
  // different, a bit less colorful, but only very marginally.
  if (useSubThemes && subTheme.blendTextTheme) {
    // Use the on color for surface or background that gives us better
    // contrast. Finding the right one by comparing red values seemed
    // to work well enough.
    final Color _onColor = isDark
        ? (colorScheme.onBackground.red < colorScheme.onSurface.red)
            ? colorScheme.onSurface
            : colorScheme.onBackground
        : (colorScheme.onBackground.red > colorScheme.onSurface.red)
            ? colorScheme.onSurface
            : colorScheme.onBackground;

    // Calculate colors for the different TextStyles, these are just best
    // approximations, color blend strength is a bit inline with opacities on
    // the 2018 typography, but that might not match what is used for color
    // strength on colored text in Material 3. I could not find definitions
    // for that in the Material 3 guide yet. They might also be just flat
    // one color tone for all sizes. That would be simpler, but event that
    // color is not know yet.
    final Color _head = isDark
        ? _onColor.blend(colorScheme.primary, 40)
        : _onColor.blend(colorScheme.primary, 50);
    final Color _medium = isDark
        ? _onColor.blend(colorScheme.primary, 22)
        : _onColor.blend(colorScheme.primary, 40);
    final Color _small = isDark
        ? _onColor.blend(colorScheme.primary, 20)
        : _onColor.blend(colorScheme.primary, 30);
    // Apply the computed colors. Most fonts have no opacity when using this
    // type of styling, they are computed with a color matching their
    // background. This does not work so well if you need to put text on
    // a completely different colored container than the background color.
    // Which is why this feature can be opted out of.
    // M3 has separate colored text for different colored containers.
    // Can't match that with M2 themes.
    defTextTheme = defTextTheme.copyWith(
      headline1: defTextTheme.headline1!.copyWith(color: _head),
      headline2: defTextTheme.headline2!.copyWith(color: _head),
      headline3: defTextTheme.headline3!.copyWith(color: _head),
      headline4: defTextTheme.headline4!.copyWith(color: _head),
      headline5: defTextTheme.headline5!.copyWith(color: _medium),
      headline6: defTextTheme.headline6!.copyWith(color: _medium),
      subtitle1: defTextTheme.subtitle1!.copyWith(color: _medium),
      subtitle2: defTextTheme.subtitle2!.copyWith(color: _small),
      bodyText1: defTextTheme.bodyText1!.copyWith(color: _medium),
      bodyText2: defTextTheme.bodyText2!.copyWith(color: _medium),
      button: defTextTheme.button!.copyWith(color: _medium),
      // Caption in English2018 has heading level opacity in Material2.
      // I noticed it still needs some, eg ListTile uses the color from
      // caption, with its opacity, to make the subtitles more muted, this
      // is an important design effect that we get automatically if we give
      // it some opacity, just not going to give it as much since we also
      // have colors and it is imo a bit too low contrast in M2.
      caption: defTextTheme.caption!
          .copyWith(color: _medium.withAlpha(isDark ? 0xCC : 0xBF)), //80,75%
      overline: defTextTheme.overline!.copyWith(color: _small),
    );
    // Equivalent color blend calculations for primary text theme.
    final Color _headP = primaryIsDark
        ? colorScheme.onPrimary.blend(colorScheme.primary, 16)
        : colorScheme.onPrimary.blend(colorScheme.primary, 10);
    final Color _mediumP = primaryIsDark
        ? colorScheme.onPrimary.blend(colorScheme.primary, 8)
        : colorScheme.onPrimary.blend(colorScheme.primary, 5);
    final Color _smallP = primaryIsDark
        ? colorScheme.onPrimary.blend(colorScheme.primary, 7)
        : colorScheme.onPrimary.blend(colorScheme.primary, 4);
    defPrimaryTextTheme = defPrimaryTextTheme.copyWith(
      headline1: defPrimaryTextTheme.headline1!.copyWith(color: _headP),
      headline2: defPrimaryTextTheme.headline2!.copyWith(color: _headP),
      headline3: defPrimaryTextTheme.headline3!.copyWith(color: _headP),
      headline4: defPrimaryTextTheme.headline4!.copyWith(color: _headP),
      headline5: defPrimaryTextTheme.headline5!.copyWith(color: _mediumP),
      headline6: defPrimaryTextTheme.headline6!.copyWith(color: _mediumP),
      subtitle1: defPrimaryTextTheme.subtitle1!.copyWith(color: _mediumP),
      subtitle2: defPrimaryTextTheme.subtitle2!.copyWith(color: _smallP),
      bodyText1: defPrimaryTextTheme.bodyText1!.copyWith(color: _mediumP),
      bodyText2: defPrimaryTextTheme.bodyText2!.copyWith(color: _mediumP),
      button: defPrimaryTextTheme.button!.copyWith(color: _mediumP),
      caption: defPrimaryTextTheme.caption!.copyWith(
          color: _mediumP.withAlpha(primaryIsDark ? 0xD8 : 0xCC)), //85,70%)
      overline: defPrimaryTextTheme.overline!.copyWith(color: _smallP),
    );
  }
  // Use M3 text theme when sub themes enabled, and M3 text themes opt-in.
  if (useSubThemes && subTheme.useTextTheme) {
    defTextTheme = defTextTheme.merge(m3TextTheme);
    defPrimaryTextTheme = defPrimaryTextTheme.merge(m3TextTheme);
  }
  // Make our final complete TextTheme, by also merging in the two TextThemes
  // passed in via the constructor, adding any custom text theme definitions.
  final TextTheme effectiveTextTheme = defTextTheme.merge(textTheme);
  final TextTheme effectivePrimaryTextTheme =
      defPrimaryTextTheme.merge(primaryTextTheme);

  // When working with color scheme based colors, there is no longer a
  // Material primary swatch that we can use to create some of the old
  // Theme colors from. Those colors are still needed by some Flutter Widgets.
  // To be able to make these colors we compute a complete material like
  // color swatch from the provided primary color, using the primary color
  // as the MaterialColor's mid [500] index color.
  final MaterialColor primarySwatch =
      createPrimarySwatch(colorScheme.primary);
  // We now have a swatch of the primary color provided via a color scheme,
  // we can use it to define some of the traditional theme colors in a way
  // that relates to the color scheme's primary color, like ThemeData factory
  // does when you create a theme from a swatch. This gives us some missing
  // critical shades of the primary color to work with.
  final Color primaryColorDark =
      isDark ? primarySwatch[700]! : primarySwatch[800]!;
  final Color primaryColorLight = primarySwatch[100]!;
  final Color secondaryHeaderColor = primarySwatch[50]!;

  // AppBar background color: If a custom color for the AppBar was
  // passed in, we use that. If not, we use the surface color in dark mode and
  // primary color in light mode. Which is the same logic that the
  // Flutter SDK ThemeData.from factory sets the AppBar background color to,
  // but via a convoluted ThemeData.primaryColor modification, causing
  // primaryColor to be almost black in dark mode, which is weird, but since
  // it was only used in the AppBar in the past, it kind of works, but now it
  // still lingers there being almost black and not primary colored, which
  // makes no sense at all. It will eventually be cleaned away when
  // `primaryColor` is deprecated though.
  final Color effectiveAppBarColor = appBarBackground ??
      (isDark ? colorScheme.surface : colorScheme.primary);
  final Brightness appBarBrightness =
      ThemeData.estimateBrightnessForColor(effectiveAppBarColor);
  Color appBarForeground =
      appBarBrightness == Brightness.dark ? Colors.white : Colors.black;
  // Icons are slightly transparent in light mode! This follows SDK standard.
  Color appBarIconColor =
      appBarBrightness == Brightness.dark ? Colors.white : Colors.black87;
  // If we are using subThemes, use blend for foreground color.
  if (useSubThemes && subTheme.blendTextTheme) {
    if (appBarBrightness == Brightness.dark) {
      appBarForeground =
          FlexColor.lightSurface.blend(effectiveAppBarColor, 12);
    } else {
      appBarForeground =
          FlexColor.darkSurface.blend(effectiveAppBarColor, 12);
    }
    appBarIconColor = appBarForeground;
  }
  // Selected TabBar color is based on FlexTabBarStyle tabBarStyle.
  // The `flutterDefault` sets values corresponding to SDK Default behavior,
  // it can be used, but is not as useful as the `forAppBar` version which
  // is the default here.
  Color selectedTabColor() {
    switch (tabBarStyle) {
      case FlexTabBarStyle.flutterDefault:
        return isDark ? Colors.white : colorScheme.onPrimary;
      case FlexTabBarStyle.forBackground:
        return colorScheme.primary;
      case FlexTabBarStyle.forAppBar:
        return appBarBrightness == Brightness.light
            ? Colors.black87
            : Colors.white;
      case FlexTabBarStyle.universal:
        return isDark
            ? colorScheme.primary.blendAlpha(Colors.white, 0xE6) // 90%
            : colorScheme.primary.blendAlpha(Colors.white, 0xB2); // 50%
    }
  }

  // Unselected TabBar color is based on FexTabBarStyle tabBarStyle.
  // The `flutterDefault` sets values corresponding to SDK Default behavior.
  Color unselectedTabColor() {
    switch (tabBarStyle) {
      case FlexTabBarStyle.flutterDefault:
        return selectedTabColor().withAlpha(0xB2); // 70%
      case FlexTabBarStyle.forBackground:
        return useSubThemes
            ? colorScheme.onSurface
                .blendAlpha(colorScheme.primary,
                    kUnselectedBackgroundPrimaryAlphaBlend)
                .withAlpha(kUnselectedAlphaBlend)
            : colorScheme.onSurface.withAlpha(0x99); // 60%
      case FlexTabBarStyle.forAppBar:
        return (appBarBrightness == Brightness.light &&
                (effectiveAppBarColor == Colors.white ||
                    effectiveAppBarColor == colorScheme.surface ||
                    effectiveAppBarColor == colorScheme.background))
            ? colorScheme.onSurface.withAlpha(0x99) // 60%
            : selectedTabColor().withAlpha(0xB2); // 70% alpha
      case FlexTabBarStyle.universal:
        return isDark
            ? colorScheme.primary
                .blendAlpha(Colors.white, 0xE6) // 90%
                .withAlpha(0xB2) // 70% alpha
            : colorScheme.primary
                .blendAlpha(Colors.white, 0x7F)
                .withAlpha(0x7F); // 50%a
    }
  }

  // The current default theme for Material themed Tooltips are not ideal
  // choices for desktop https://material.io/components/tooltips#specs.
  // See issue: https://github.com/flutter/flutter/issues/71429
  // The font size of 10 dp is too small for desktops with pixel density
  // 1.0 so here we use 12 dp on desktop/Web and 14 dp on devices.
  double tooltipFontSize() {
    switch (effectivePlatform) {
      case TargetPlatform.macOS:
      case TargetPlatform.linux:
      case TargetPlatform.windows:
        return 12;
      case TargetPlatform.iOS:
      case TargetPlatform.fuchsia:
      case TargetPlatform.android:
        return 14;
    }
  }

  // This padding on tooltips fixes that default tooltips do not work well if
  // multi-row tooltips are used with the default fixed sized behavior.
  EdgeInsets tooltipPadding() {
    switch (effectivePlatform) {
      case TargetPlatform.macOS:
      case TargetPlatform.linux:
      case TargetPlatform.windows:
        return const EdgeInsets.fromLTRB(8, 3, 8, 4);
      case TargetPlatform.iOS:
      case TargetPlatform.fuchsia:
      case TargetPlatform.android:
        return const EdgeInsets.symmetric(horizontal: 16, vertical: 8);
    }
  }

  // Same as in ThemeData.from, but defined for use in the tooltip sub-theme.
  // If our onSurface is primary tinted it has an effect on this divider too.
  final Color dividerColor = colorScheme.onSurface.withAlpha(0x1E); // 12%

  // Make the effective input decoration theme, by using FCS v4 sub themes
  // if opted in, otherwise use pre-v4 version as before. This decoration
  // theme is also passed into the TimePickerTheme, so we get the same
  // style used there too.
  final InputDecorationTheme effectiveInputDecorationTheme = useSubThemes
      ? FlexSubThemes.inputDecorationTheme(
          colorScheme: colorScheme,
          baseSchemeColor: subTheme.inputDecoratorSchemeColor,
          radius: subTheme.inputDecorationRadius ??
              subTheme.defaultRadius ??
              kButtonRadius,
          borderType: subTheme.inputDecoratorBorderType,
          filled: subTheme.inputDecoratorIsFilled,
          fillColor: subTheme.inputDecoratorFillColor,
          focusedBorderWidth: subTheme.thickBorderWidth,
          unfocusedBorderWidth: subTheme.thinBorderWidth,
          unfocusedHasBorder: subTheme.inputDecoratorUnfocusedHasBorder,
        )
      // Default one is also a bit opinionated, this is the default from
      // all previous versions before version 4.0.0.
      : InputDecorationTheme(
          // Extend filled property to previous always filled ones, defaults
          // to filled as before, but can now also be unfilled even if not
          // opted in on sub themes, by setting the property for it
          // FlexSubThemesData.
          filled: subTheme.inputDecoratorIsFilled,
          fillColor: isDark
              ? colorScheme.primary.withAlpha(0x0F) // 6%
              : colorScheme.primary.withAlpha(0x09), // 3.5%
        );

  // Use themed interaction effects on hover, focus, highlight and splash?
  final bool themedEffects = useSubThemes && subTheme.interactionEffects;

  // Return the ThemeData object defined by the FlexColorScheme
  // properties and the designed opinionated theme design choices.
  return ThemeData(
    // These properties we just pass along these to the standard ThemeData
    // factory. They are included in FlexColorScheme so we do not have to
    // apply them via ThemeData copyWith separately for cases when we want
    // to use them in a FlexColorSchemes, which might often be the case. Some
    // of the values may be null and get defaults via the ThemeData() factory.
    fontFamily: fontFamily,
    visualDensity: visualDensity,
    // TextTheme properties use the same logic as in ThemeData, allowing us
    // to optionally define them. AccentTextTheme is omitted since it has
    // been deprecated in Flutter 2.5.0.
    textTheme: effectiveTextTheme,
    primaryTextTheme: effectivePrimaryTextTheme,
    // Pass along custom typography and platform.
    typography: effectiveTypography,
    platform: effectivePlatform,
    // Most definitions below are very close to the ones used by the Flutter
    // factory ThemeData.from() for creating a theme from a color scheme and
    // text theme.
    brightness: colorScheme.brightness,
    primaryColor: colorScheme.primary,
    primaryColorBrightness:
        ThemeData.estimateBrightnessForColor(colorScheme.primary),
    canvasColor: colorScheme.background,

    // Flutter standard for scaffoldBackgroundColor is colorScheme.background.
    // Here it is replaced with a separate color for the scaffold background,
    // so we can use a configuration with a separate scaffold background
    // color from scheme background and surface. Flutter's ThemeData.from
    // a ColorScheme cannot do this. The good old ThemeData factory can of
    // course, but color scheme based themes in Flutter cannot specify it
    // separately. We need to be able to do so/ in order to make elegantly
    // nuanced primary color branded themes.
    scaffoldBackgroundColor: scaffoldBackground ?? colorScheme.background,
    // Card, divider and background colors are same as in ThemeData.from.
    cardColor: colorScheme.surface,
    dividerColor: dividerColor,
    backgroundColor: colorScheme.background,
    // Disabled color uses a different style when using themed interaction
    // effects, if not opted in same as before v4.0.0, use ThemeData default.
    disabledColor: themedEffects
        ? colorScheme.primary
            .blendAlpha(colorScheme.onSurface, kDisabledAlphaBlend)
            .withAlpha(kDisabledBackgroundAlpha)
        : isDark
            ? Colors.white38
            : Colors.black38,

    // Same as ThemeData SDK.
    // hintColor is used only by DropdownButton and InputDecorator in SDK.
    hintColor: isDark ? Colors.white60 : Colors.black.withAlpha(0x99), // 60%

    // Special theming on hover, focus, highlight and splash, if opting in on
    // themedEffects, otherwise use ThemeData defaults by passing in null
    // and letting it assign its values.
    hoverColor: themedEffects
        ? colorScheme.primary
            .blendAlpha(Colors.white, kHoverAlphaBlend)
            .withAlpha(kHoverAlpha)
        : null,
    focusColor: themedEffects
        ? colorScheme.primary
            .blendAlpha(Colors.white, kFocusAlphaBlend)
            .withAlpha(kFocusAlpha)
        : null,
    highlightColor: themedEffects
        ? colorScheme.primary
            .blendAlpha(Colors.white, kHighlightAlphaBlend)
            .withAlpha(kHighlightAlpha)
        : null,
    splashColor: themedEffects
        ? colorScheme.primary
            .blendAlpha(Colors.white, kSplashAlphaBlend)
            .withAlpha(kSplashAlpha)
        : null,

    // Flutter standard dialogBackgroundColor for color scheme based themes
    // uses colorScheme.background.
    // The FlexColorScheme.from() factory constructor uses passed in dialog
    // background color that is same as surface color if not defined, but
    // may also be any other other color.
    // If using surface blends that are not equal for all Material surface
    // backgrounds colors. There will be no elevation overlay color in dark
    // mode, even if so configured.
    // Use dialogs with background color that equals theme
    // colorScheme.surface to ensure it gets elevation overlay color applied
    // in dark mode. See issue:
    // https://github.com/flutter/flutter/issues/90353
    // The dialogBackgroundColor in ThemeData is going to be deprecated.
    dialogBackgroundColor: dialogBackground ?? colorScheme.background,

    // Define errorColor via color scheme error color.
    errorColor: colorScheme.error,

    // Use TabBar style dependent function for selected Tab as indicatorColor
    // if no color scheme selection for it is made.
    indicatorColor: subTheme.tabBarIndicatorSchemeColor == null
        ? selectedTabColor()
        : FlexSubThemes.schemeColor(
            subTheme.tabBarIndicatorSchemeColor!, colorScheme),

    // Elevation overlay on dark material elevation is used on dark themes
    // on surfaces when so requested, applyElevationOverlayColor defaults
    // to true in FlexColorScheme themes, but you can turn it off.
    // Flutter ThemeData.from ColorScheme based
    // themes also uses this by default, but older ThemeData factories do not.
    applyElevationOverlayColor: isDark && applyElevationOverlayColor,

    // Pass the from FlexColorScheme defined colorScheme to ThemeData
    // colorScheme. Newer standard Flutter sub themes use the colorScheme
    // for their theming and all sub themes will eventually be converted to
    // be based on the defined color scheme colors. FlexColorScheme passes
    // the scheme it has created to the colorScheme property in ThemeData.
    // More info here: https://flutter.dev/go/material-theme-system-updates
    colorScheme: colorScheme,

    // ----------------------------------------------------------------------
    // The theme settings below are corrective additions to the Flutter
    // standard Theme.from(colorScheme) factory. They are needed because it
    // omits some definitions that will not be aligned with the ColorScheme
    // theme if they are not added to it manually.
    // This as per the state in master channel December 15, 2020.
    // This document again relates to the on going transition:
    // https://flutter.dev/go/material-theme-system-updates
    // This issue explains and demos some of the current gaps:
    // https://github.com/flutter/flutter/issues/65782
    // Some of the gaps will probably be solved as Flutter's theme
    // migration progresses. We monitor the development and will remove no
    // longer needed corrections or remove totally deprecated
    // ThemeData properties when it is appropriate and timely to do so.
    // ----------------------------------------------------------------------

    // This color is important, if it is not set we get a teal color for it
    // in dark mode, and not actually the secondary color that we want for
    // our color scheme based theme. The Flutter color scheme based theme
    // does not include this, in our opinion for correct application of the
    // color scheme based theme, it should really do the same as below.
    // See issue: https://github.com/flutter/flutter/issues/65782
    toggleableActiveColor: colorScheme.secondary,

    // The primary dark color no longer exists in ColorScheme themes, but
    // it still needs to be set to match the ColorScheme theme, otherwise we
    // get a default dark blue theme color for it coming from default
    // primarySwatch. This will not look good if your theme uses any primary
    // color that is not a blue hue. To fix this we use the [700] value from
    // the calculated primary swatch for dark mode and [800] for light mode.
    // This property is used by `CircleAvatar` and `Slider`.
    // See issue: https://github.com/flutter/flutter/issues/65782
    primaryColorDark: primaryColorDark,

    // The light primary color no longer exists in ColorScheme themes, but it
    // still needs to be set to match the ColorScheme theme, otherwise we
    // get a default blue color for it coming from the default primarySwatch.
    // We use the [100] value from the calculated primary swatch.
    // This property is used by `CircleAvatar` and `Slider`.
    // See issue: https://github.com/flutter/flutter/issues/65782
    primaryColorLight: primaryColorLight,

    // Define a secondary header color, this property is only used in Flutter
    // SDK by `PaginatedDataTable`. It gets a super light [50] hue of the
    // primary color from default theme.light factory. Here we use the [50]
    // value from the calculated primary swatch.
    // See issue: https://github.com/flutter/flutter/issues/65782
    secondaryHeaderColor: secondaryHeaderColor,

    // The app bar theme allows us to use a custom colored appbar theme
    // in both light and dark themes that is not dependent on theme primary
    // or surface color, and still gets a correct working text and icon theme.
    // In the versions prior to Flutter 2.0.0 doing this was very difficult,
    // as presented in https://github.com/flutter/flutter/issues/50606 doing
    // this was a tricky. A new feature in Flutter 2.0.0 implemented via:
    // https://github.com/flutter/flutter/pull/71184 makes this setup easy.
    // The FlexColorScheme implementation below has been changed to
    // use these new AppBarTheme features in version 2.0.0.
    appBarTheme: AppBarTheme(
      backgroundColor: effectiveAppBarColor,
      foregroundColor: appBarForeground,
      iconTheme: IconThemeData(color: appBarIconColor),
      actionsIconTheme: IconThemeData(color: appBarIconColor),
      elevation: appBarElevation,
      systemOverlayStyle: SystemUiOverlayStyle(
        // AppBar overlay style.
        statusBarColor: transparentStatusBar
            ? Colors.transparent
            // This is the actual scrim color used by Android by default,
            // here we just re-apply if false or if it had been removed
            // earlier, using `null` does not restore it, we need to re-apply
            // the used scrim color by Android to restore if it has been
            // removed earlier.
            : const Color(0x40000000),
        statusBarBrightness: appBarBrightness,
        statusBarIconBrightness: appBarBrightness == Brightness.dark
            ? Brightness.light
            : Brightness.dark,
        // The systemNavigationBarColor used by default AppBar in SDK:
        systemNavigationBarColor: const Color(0xFF000000),
        // Would be nice if this worked instead of above, but it does not:
        // systemNavigationBarColor: isDark ? Colors.black : Colors.white;,
        //
        // The systemNavigationBarIconBrightness used by the AppBar in SDK:
        systemNavigationBarIconBrightness: Brightness.dark,
        // Would be nice if this worked instead of above, but it does not:
        // systemNavigationBarIconBrightness:
        //     isDark ? Brightness.light : Brightness.dark,
        //
        // The systemNavigationBarDividerColor used by default AppBar in SDK:
        systemNavigationBarDividerColor: null,
      ),
    ),

    // The bottom app bar uses color scheme background color to match the
    // background color of the drawer, bottom navigation bar, possible side
    // menu and system navigation bar on android (if theming of it is used).
    // This is a slight change from the ColorScheme default that uses
    // surface color.
    bottomAppBarColor: colorScheme.background,
    bottomAppBarTheme: BottomAppBarTheme(
      color: colorScheme.background,
      elevation: bottomAppBarElevation,
    ),

    // In TextSelectionThemeData, the standard for selectionColor is
    // colorScheme.primary with opacity value 0.4 for dark and 0.12 light
    // mode. Here we use primary with 0.5 for dark mode and 0.3 for light
    // mode. The standard selectionHandleColor is colorScheme.primary,
    // here we use the slightly darker shade primaryColorDark instead.
    textSelectionTheme: TextSelectionThemeData(
      selectionColor: isDark
          ? colorScheme.primary.withAlpha(0xB2) // 50%
          : colorScheme.primary.withAlpha(0x4C), // 30%
      selectionHandleColor: primaryColorDark,
    ),

    // Define the TabBar theme that will fit nicely in an AppBar
    // (default) or on background color for use eg in a Scaffold, the choice
    // depends on tabBarStyle `FlexTabBarStyle`, that defaults to
    // `FlexTabBarStyle.forAppBar`. Using different theme styles for intended
    // target usage avoids this challenge:
    // https://github.com/flutter/flutter/pull/68171#pullrequestreview-517753917
    // That still has some issue related to it, is using default is used, we
    // pass in `null` and let ThemeData use default sub-theme for TabBarTheme.
    tabBarTheme: TabBarTheme(
      indicatorSize: TabBarIndicatorSize.tab,
      labelStyle: effectiveTextTheme.bodyText1,
      labelColor: selectedTabColor(),
      unselectedLabelStyle: effectiveTextTheme.bodyText1,
      unselectedLabelColor: unselectedTabColor(),
    ),

    // Set colors for for icons in opted in sub themes.
    iconTheme: useSubThemes
        ? IconThemeData(color: effectiveTextTheme.headline6!.color)
        : null,
    primaryIconTheme: useSubThemes
        ? IconThemeData(color: effectivePrimaryTextTheme.headline6!.color)
        : null,

    // Opinionated theming of Tooltips, the default theme for Material
    // themed Tooltips are not ideal design choices on desktop and web
    // https://material.io/components/tooltips#specs.
    // The theming below is an opinionated alternative design, with an option
    // to also invert the tooltip background color.
    // See issue: https://github.com/flutter/flutter/issues/71429
    tooltipTheme: TooltipThemeData(
      // Do not use the min height, the custom padding handles it instead.
      padding: tooltipPadding(),
      margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
      textStyle: effectiveTextTheme.bodyText2!.copyWith(
        inherit: false,
        color: tooltipsMatchBackground
            ? isDark
                ? Colors.white
                : Colors.black
            : isDark
                ? Colors.black
                : Colors.white,
        fontSize: tooltipFontSize(),
      ),
      decoration: useSubThemes
          // Opted in on sub-themes, we make a more fitting tooltip too.
          ? tooltipsMatchBackground
              ? BoxDecoration(
                  color: isDark
                      ? FlexColor.darkSurface
                          .blendAlpha(colorScheme.primary, 0x28) // 16%
                          .withAlpha(0xF2) // 95%
                      : FlexColor.lightSurface
                          .blendAlpha(colorScheme.primary, 0x0A) // 4%
                          .withAlpha(0xF2), // 95%
                  borderRadius: const BorderRadius.all(Radius.circular(8)),
                  border: Border.all(color: dividerColor),
                )
              : BoxDecoration(
                  color: isDark
                      ? FlexColor.lightSurface
                          .blendAlpha(colorScheme.primary, 0x63) // 39%
                          .withAlpha(0xF2) // 95%
                      : FlexColor.darkSurface
                          .blendAlpha(colorScheme.primary, 0x72) // 45%
                          .withAlpha(0xF2), // 95%
                  borderRadius: const BorderRadius.all(Radius.circular(8)),
                  border: Border.all(color: dividerColor),
                )
          // Not opted in on sub themes, use the before v4 style.
          : tooltipsMatchBackground
              ? BoxDecoration(
                  color: isDark
                      ? const Color(0xED444444)
                      : const Color(0xF0FCFCFC),
                  borderRadius: const BorderRadius.all(Radius.circular(4)),
                  border: Border.all(color: dividerColor),
                )
              : null,
    ),

    textButtonTheme: useSubThemes
        ? FlexSubThemes.textButtonTheme(
            colorScheme: colorScheme,
            radius: subTheme.textButtonRadius ?? subTheme.defaultRadius,
            padding: subTheme.buttonPadding,
            minButtonSize: subTheme.buttonMinSize,
          )
        : null,
    elevatedButtonTheme: useSubThemes
        ? FlexSubThemes.elevatedButtonTheme(
            colorScheme: colorScheme,
            radius: subTheme.elevatedButtonRadius ?? subTheme.defaultRadius,
            elevation: subTheme.elevatedButtonElevation,
            padding: subTheme.buttonPadding,
            minButtonSize: subTheme.buttonMinSize,
          )
        : null,
    outlinedButtonTheme: useSubThemes
        ? FlexSubThemes.outlinedButtonTheme(
            colorScheme: colorScheme,
            radius: subTheme.outlinedButtonRadius ?? subTheme.defaultRadius,
            pressedOutlineWidth: subTheme.thickBorderWidth,
            outlineWidth: subTheme.thinBorderWidth,
            padding: subTheme.buttonPadding,
            minButtonSize: subTheme.buttonMinSize,
          )
        : null,
    // Since the old buttons have been deprecated in Flutter 2.0.0
    // they are no longer presented or used in the code in FlexColorScheme.
    // The button theming below still makes the old buttons almost
    // look like the defaults for the new ElevatedButton, TextButton and
    // OutlinedButton.
    // This buttonTheme setup, makes the old legacy Material buttons
    // [RaisedButton], [OutlineButton] and [FlatButton] very similar in
    // style to the default color scheme based style used for the
    // newer Material buttons [ElevatedButton], [OutlinedButton] and
    // [TextButton]. There are some differences in margin
    // and outline color and the elevation behavior on the raised button.
    // A useSubThemes was added in version 4.0.0 to also there still support
    // the old buttons. Be aware that the theme will be removed from
    // FlexColorScheme when it becomes deprecated in Flutter SDK or the
    // buttons that already are deprecated, and that use this ButtonThemeData
    // are completely removed.
    buttonTheme: useSubThemes
        ? FlexSubThemes.buttonTheme(
            colorScheme: colorScheme,
            radius: subTheme.textButtonRadius ?? subTheme.defaultRadius,
            padding: subTheme.buttonPadding,
            minButtonSize: subTheme.buttonMinSize,
          )
        : ButtonThemeData(
            colorScheme: colorScheme,
            textTheme: ButtonTextTheme.primary,
            materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
            padding: const EdgeInsets.symmetric(horizontal: 16),
          ),
    // Toggle buttons have limited theming capability and cannot match new
    // buttons fully, this is an approximation.
    toggleButtonsTheme: useSubThemes
        ? FlexSubThemes.toggleButtonsTheme(
            colorScheme: colorScheme,
            borderWidth: subTheme.thinBorderWidth,
            radius: subTheme.toggleButtonsRadius ?? subTheme.defaultRadius,
            minButtonSize: subTheme.buttonMinSize,
            visualDensity: visualDensity,
          )
        : null,
    inputDecorationTheme: effectiveInputDecorationTheme,
    floatingActionButtonTheme: useSubThemes
        ? FlexSubThemes.floatingActionButtonTheme(
            radius: subTheme.dialogRadius ?? subTheme.defaultRadius,
            useShape: subTheme.fabUseShape,
          )
        : null,

    // The default chip theme in Flutter does not work correctly with dark
    // themes. See issue: https://github.com/flutter/flutter/issues/65663
    // The chip theme below fixes it by using the colorScheme.primary color.
    chipTheme: useSubThemes
        ? FlexSubThemes.chipTheme(
            colorScheme: colorScheme,
            baseSchemeColor: subTheme.chipSchemeColor,
            labelStyle: effectiveTextTheme.button!,
            radius: subTheme.chipRadius ?? subTheme.defaultRadius,
          )
        : ChipThemeData.fromDefaults(
            secondaryColor: colorScheme.primary,
            brightness: colorScheme.brightness,
            labelStyle: effectiveTextTheme.bodyText1!,
          ),
    cardTheme: useSubThemes
        ? FlexSubThemes.cardTheme(
            radius: subTheme.cardRadius ?? subTheme.defaultRadius,
            elevation: subTheme.cardElevation,
          )
        : null,
    popupMenuTheme: useSubThemes
        ? FlexSubThemes.popupMenuTheme(
            radius: subTheme.popupMenuRadius ??
                ((subTheme.defaultRadius ?? 16) > 10
                    ? 10
                    : subTheme.defaultRadius),
            elevation: subTheme.popupMenuElevation,
            color: subTheme.popupMenuOpacity == null
                ? null
                : colorScheme.surface.withOpacity(subTheme.popupMenuOpacity!),
          )
        : null,
    dialogTheme: useSubThemes
        ? FlexSubThemes.dialogTheme(
            radius: subTheme.dialogRadius ?? subTheme.defaultRadius,
            elevation: subTheme.dialogElevation,
            backgroundColor: dialogBackground ?? colorScheme.background,
          )
        : null,
    timePickerTheme: useSubThemes
        ? FlexSubThemes.timePickerTheme(
            backgroundColor: dialogBackground ?? colorScheme.background,
            radius: subTheme.timePickerDialogRadius ?? subTheme.defaultRadius,
            elementRadius: subTheme.cardRadius ?? subTheme.defaultRadius,
            inputDecorationTheme: effectiveInputDecorationTheme)
        : null,
    snackBarTheme: useSubThemes
        ? FlexSubThemes.snackBarTheme(
            elevation: subTheme.snackBarElevation,
            backgroundColor: isDark
                ? colorScheme.onSurface
                    .blendAlpha(colorScheme.primary, 0x63) // 39%
                    .withAlpha(0xF2) // 95%
                : colorScheme.onSurface
                    .blendAlpha(colorScheme.primary, 0x72) // 45%
                    .withAlpha(0xED) // 93%
            )
        : null,
    bottomSheetTheme: useSubThemes
        ? FlexSubThemes.bottomSheetTheme(
            radius: subTheme.bottomSheetRadius ?? subTheme.defaultRadius,
            elevation: subTheme.bottomSheetElevation,
            modalElevation: subTheme.bottomSheetModalElevation,
          )
        : null,
    // Opinionated default theming for the bottom navigation bar: Use primary
    // color for selected item. Flutter defaults to using secondary color in
    // dark mode, we want primary in dark mode too, like it is in light
    // mode. Primary color is an iOS influenced style for the bottom nav.
    // Above was default in version < 4, version 4 can also use sub-theme.
    bottomNavigationBarTheme: useSubThemes
        ? FlexSubThemes.bottomNavigationBar(
            colorScheme: colorScheme,
            baseSchemeColor: subTheme.bottomNavigationBarSchemeColor,
            backgroundSchemeColor:
                subTheme.bottomNavigationBarBackgroundSchemeColor,
            elevation: subTheme.bottomNavigationBarElevation,
            opacity: subTheme.bottomNavigationBarOpacity,
            unselectedAlphaBlend: kUnselectedBackgroundPrimaryAlphaBlend,
            unselectedAlpha: kUnselectedAlphaBlend,
          )
        : BottomNavigationBarThemeData(
            selectedIconTheme: IconThemeData(
              color: colorScheme.primary,
            ),
            selectedItemColor: colorScheme.primary,
          ),
    // Opinionated sub theme for Material 3 based Navigation Bar
    navigationBarTheme: useSubThemes
        ? FlexSubThemes.navigationBarTheme(
            colorScheme: colorScheme,
            labelTextStyle: subTheme.navigationBarIsStyled
                ? effectiveTextTheme.overline
                : null,
            selectedLabelSize: subTheme.navigationBarSelectedLabelSize,
            unselectedLabelSize: subTheme.navigationBarUnselectedLabelSize,
            textSchemeColor: subTheme.navigationBarTextSchemeColor ??
                (subTheme.navigationBarIsStyled ? SchemeColor.primary : null),
            mutedUnselectedText: subTheme.navigationBarMutedUnselectedText ??
                subTheme.navigationBarIsStyled,
            selectedIconSize: subTheme.navigationBarSelectedIconSize,
            unselectedIconSize: subTheme.navigationBarUnselectedIconSize,
            iconSchemeColor: subTheme.navigationBarIconSchemeColor ??
                (subTheme.navigationBarIsStyled ? SchemeColor.primary : null),
            mutedUnselectedIcon: subTheme.navigationBarMutedUnselectedIcon ??
                subTheme.navigationBarIsStyled,
            highlightSchemeColor: subTheme
                    .navigationBarHighlightSchemeColor ??
                (subTheme.navigationBarIsStyled ? SchemeColor.primary : null),
            indicatorAlpha: kNavigationBarIndicatorAlpha,
            backgroundSchemeColor:
                subTheme.navigationBarBackgroundSchemeColor ??
                    (subTheme.navigationBarIsStyled
                        ? SchemeColor.background
                        : null),
            opacity: subTheme.navigationBarOpacity,
            height: subTheme.navigationBarHeight ??
                (subTheme.navigationBarIsStyled
                    ? kNavigationBarHeight
                    : null),
            labelBehavior: subTheme.navigationBarLabelBehavior,
            unselectedAlphaBlend: kUnselectedBackgroundPrimaryAlphaBlend,
            unselectedAlpha: kUnselectedAlphaBlend,
          )
        : null,
  );
}