toApproximateMaterialTheme method
Converts this FThemeData to a Material ThemeData on a best-effort basis.
It does not take into account any platform-specific styling. If you need to do so, consider generating and customizing this method using the CLI:
dart run forui snippet create material-mapping
This method enables interoperability between Forui and Material Design widgets by mapping Forui's theme properties to their closest Material equivalents. Use this when you need to:
- Use Material widgets within a Forui application
- Apply your Forui theme consistently to both Forui and Material widgets
- Create a gradual migration path from Material Design to Forui
Note that this conversion is approximate. Some styling properties may not map perfectly between the two design systems, and the resulting Material theme might not capture all the nuances of your Forui theme.
// Apply a Forui theme to Material widgets
MaterialApp(
theme: FThemes.neutral.light.toApproximateMaterialTheme(),
// ...
)
Implementation
ThemeData toApproximateMaterialTheme() {
// Material requires height to be 1, certain widgets will overflow without it.
// TextBaseline.alphabetic is required as TextField requires it.
final textTheme = TextTheme(
displayLarge: typography.xl4.copyWith(height: 1, textBaseline: typography.xl4.textBaseline ?? .alphabetic),
displayMedium: typography.xl3.copyWith(height: 1, textBaseline: typography.xl3.textBaseline ?? .alphabetic),
displaySmall: typography.xl2.copyWith(height: 1, textBaseline: typography.xl2.textBaseline ?? .alphabetic),
headlineLarge: typography.xl3.copyWith(height: 1, textBaseline: typography.xl3.textBaseline ?? .alphabetic),
headlineMedium: typography.xl2.copyWith(height: 1, textBaseline: typography.xl2.textBaseline ?? .alphabetic),
headlineSmall: typography.xl.copyWith(height: 1, textBaseline: typography.xl.textBaseline ?? .alphabetic),
titleLarge: typography.lg.copyWith(height: 1, textBaseline: typography.lg.textBaseline ?? .alphabetic),
titleMedium: typography.base.copyWith(height: 1, textBaseline: typography.base.textBaseline ?? .alphabetic),
titleSmall: typography.sm.copyWith(height: 1, textBaseline: typography.sm.textBaseline ?? .alphabetic),
labelLarge: typography.base.copyWith(height: 1, textBaseline: typography.base.textBaseline ?? .alphabetic),
labelMedium: typography.sm.copyWith(height: 1, textBaseline: typography.sm.textBaseline ?? .alphabetic),
labelSmall: typography.xs.copyWith(height: 1, textBaseline: typography.xs.textBaseline ?? .alphabetic),
bodyLarge: typography.base.copyWith(height: 1, textBaseline: typography.base.textBaseline ?? .alphabetic),
bodyMedium: typography.sm.copyWith(height: 1, textBaseline: typography.sm.textBaseline ?? .alphabetic),
bodySmall: typography.xs.copyWith(height: 1, textBaseline: typography.xs.textBaseline ?? .alphabetic),
)..apply(fontFamily: typography.defaultFontFamily, bodyColor: colors.foreground, displayColor: colors.foreground);
return ThemeData(
colorScheme: ColorScheme(
brightness: colors.brightness,
primary: colors.primary,
onPrimary: colors.primaryForeground,
secondary: colors.secondary,
onSecondary: colors.secondaryForeground,
error: colors.error,
onError: colors.errorForeground,
surface: colors.background,
onSurface: colors.foreground,
secondaryContainer: colors.secondary,
onSecondaryContainer: colors.secondaryForeground,
),
fontFamily: typography.defaultFontFamily,
typography: Typography(
black: textTheme,
white: textTheme,
englishLike: textTheme,
dense: textTheme,
tall: textTheme,
),
textTheme: textTheme,
splashFactory: NoSplash.splashFactory,
useMaterial3: true,
// Navigation Bar
navigationBarTheme: NavigationBarThemeData(
indicatorShape: RoundedRectangleBorder(borderRadius: style.borderRadius),
),
// Navigation Drawer
navigationDrawerTheme: NavigationDrawerThemeData(
indicatorShape: RoundedRectangleBorder(borderRadius: style.borderRadius),
),
// Navigation Rail
navigationRailTheme: NavigationRailThemeData(
indicatorShape: RoundedRectangleBorder(borderRadius: style.borderRadius),
),
// Card
cardTheme: CardThemeData(
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: style.borderRadius,
side: BorderSide(width: style.borderWidth, color: colors.border),
),
),
// Chip
chipTheme: ChipThemeData(shape: RoundedRectangleBorder(borderRadius: style.borderRadius)),
// Input
inputDecorationTheme: InputDecorationTheme(
border: WidgetStateInputBorder.resolveWith((states) => textFieldStyle.border.resolve(toVariants(states))),
labelStyle: textFieldStyle.descriptionTextStyle.base,
floatingLabelStyle: textFieldStyle.labelTextStyle.base,
hintStyle: textFieldStyle.hintTextStyle.base,
errorStyle: textFieldStyle.errorTextStyle.base,
helperStyle: textFieldStyle.descriptionTextStyle.base,
counterStyle: textFieldStyle.counterTextStyle.base,
contentPadding: textFieldStyle.contentPadding,
),
// Date Picker
datePickerTheme: DatePickerThemeData(
shape: RoundedRectangleBorder(borderRadius: style.borderRadius),
dayShape: .all(RoundedRectangleBorder(borderRadius: style.borderRadius)),
rangePickerShape: RoundedRectangleBorder(borderRadius: style.borderRadius),
),
// Time Picker
timePickerTheme: TimePickerThemeData(
hourMinuteTextColor: colors.secondaryForeground,
hourMinuteColor: colors.secondary,
hourMinuteShape: RoundedRectangleBorder(borderRadius: style.borderRadius),
dayPeriodTextColor: colors.foreground,
dayPeriodColor: colors.secondary,
dayPeriodBorderSide: BorderSide(color: colors.border),
dayPeriodShape: RoundedRectangleBorder(borderRadius: style.borderRadius),
dialBackgroundColor: colors.secondary,
shape: RoundedRectangleBorder(borderRadius: style.borderRadius),
),
// Slider
sliderTheme: SliderThemeData(
activeTrackColor: sliderStyles.base.activeColor.base,
inactiveTrackColor: sliderStyles.base.inactiveColor.base,
disabledActiveTrackColor: sliderStyles.base.activeColor.resolve({FSliderVariant.disabled}),
disabledInactiveTrackColor: sliderStyles.base.inactiveColor.resolve({FSliderVariant.disabled}),
activeTickMarkColor: sliderStyles.base.markStyle.tickColor.base,
inactiveTickMarkColor: sliderStyles.base.markStyle.tickColor.base,
disabledActiveTickMarkColor: sliderStyles.base.markStyle.tickColor.resolve({FSliderVariant.disabled}),
disabledInactiveTickMarkColor: sliderStyles.base.markStyle.tickColor.resolve({FSliderVariant.disabled}),
thumbColor: sliderStyles.base.thumbStyle.borderColor.base,
disabledThumbColor: sliderStyles.base.thumbStyle.borderColor.resolve({FSliderVariant.disabled}),
valueIndicatorColor: sliderStyles.base.tooltipStyle.decoration.color,
valueIndicatorTextStyle: sliderStyles.base.tooltipStyle.textStyle,
),
// Switch
switchTheme: SwitchThemeData(
thumbColor: .resolveWith((states) => switchStyle.thumbColor.resolve(toVariants(states))),
trackColor: .resolveWith((states) => switchStyle.trackColor.resolve(toVariants(states))),
trackOutlineColor: .resolveWith((states) => switchStyle.trackColor.resolve(toVariants(states))),
),
// Buttons
elevatedButtonTheme: ElevatedButtonThemeData(
style: ButtonStyle(
textStyle: .resolveWith(
(states) => buttonStyles
.resolve({FButtonVariant.secondary})
.base
.contentStyle
.textStyle
.resolve(toVariants(states)),
),
backgroundColor: .resolveWith(
(states) =>
buttonStyles.resolve({FButtonVariant.secondary}).base.decoration.resolve(toVariants(states)).color ??
colors.secondary,
),
foregroundColor: .resolveWith(
(states) =>
buttonStyles
.resolve({FButtonVariant.secondary})
.base
.contentStyle
.textStyle
.resolve(toVariants(states))
.color ??
colors.secondaryForeground,
),
padding: .all(buttonStyles.resolve({FButtonVariant.secondary}).base.contentStyle.padding),
shape: .all(RoundedRectangleBorder(borderRadius: style.borderRadius)),
),
),
filledButtonTheme: FilledButtonThemeData(
style: ButtonStyle(
textStyle: .resolveWith(
(states) => buttonStyles.base.base.contentStyle.textStyle.resolve(toVariants(states)),
),
backgroundColor: .resolveWith(
(states) => buttonStyles.base.base.decoration.resolve(toVariants(states)).color ?? colors.secondary,
),
foregroundColor: .resolveWith(
(states) =>
buttonStyles
.resolve({FButtonVariant.secondary})
.base
.contentStyle
.textStyle
.resolve(toVariants(states))
.color ??
colors.secondaryForeground,
),
padding: .all(buttonStyles.base.base.contentStyle.padding),
shape: .all(RoundedRectangleBorder(borderRadius: style.borderRadius)),
),
),
outlinedButtonTheme: OutlinedButtonThemeData(
style: ButtonStyle(
textStyle: .resolveWith(
(states) =>
buttonStyles.resolve({FButtonVariant.outline}).base.contentStyle.textStyle.resolve(toVariants(states)),
),
backgroundColor: .resolveWith(
(states) =>
buttonStyles.resolve({FButtonVariant.outline}).base.decoration.resolve(toVariants(states)).color ??
Colors.transparent,
),
foregroundColor: .resolveWith(
(states) =>
buttonStyles
.resolve({FButtonVariant.outline})
.base
.contentStyle
.textStyle
.resolve(toVariants(states))
.color ??
Colors.transparent,
),
padding: .all(buttonStyles.resolve({FButtonVariant.outline}).base.contentStyle.padding),
side: .resolveWith((states) {
final border = buttonStyles
.resolve({FButtonVariant.outline})
.base
.decoration
.resolve(toVariants(states))
.border;
return BorderSide(
color:
border?.top.color ??
switch (states) {
_ when states.contains(WidgetState.disabled) => colors.disable(colors.border),
_ when states.contains(WidgetState.hovered) => colors.hover(colors.border),
_ => colors.border,
},
width: border?.top.width ?? style.borderWidth,
);
}),
shape: .resolveWith(
(states) => RoundedRectangleBorder(
borderRadius:
buttonStyles
.resolve({FButtonVariant.outline})
.base
.decoration
.resolve(toVariants(states))
.borderRadius ??
style.borderRadius,
),
),
),
),
textButtonTheme: TextButtonThemeData(
style: ButtonStyle(
textStyle: .resolveWith(
(states) =>
buttonStyles.resolve({FButtonVariant.ghost}).base.contentStyle.textStyle.resolve(toVariants(states)),
),
backgroundColor: .resolveWith(
(states) =>
buttonStyles.resolve({FButtonVariant.ghost}).base.decoration.resolve(toVariants(states)).color ??
Colors.transparent,
),
foregroundColor: .resolveWith(
(states) =>
buttonStyles
.resolve({FButtonVariant.ghost})
.base
.contentStyle
.textStyle
.resolve(toVariants(states))
.color ??
colors.secondaryForeground,
),
shape: .resolveWith(
(states) => RoundedRectangleBorder(
borderRadius:
buttonStyles
.resolve({FButtonVariant.ghost})
.base
.decoration
.resolve(toVariants(states))
.borderRadius ??
style.borderRadius,
),
),
),
),
floatingActionButtonTheme: FloatingActionButtonThemeData(
backgroundColor: buttonStyles.base.base.decoration.base.color,
foregroundColor: buttonStyles.base.base.contentStyle.textStyle.base.color,
hoverColor: buttonStyles.base.base.decoration.resolve({FTappableVariant.hovered}).color,
disabledElevation: 0,
shape: RoundedRectangleBorder(
borderRadius: buttonStyles.base.base.decoration.base.borderRadius ?? style.borderRadius,
),
),
iconButtonTheme: IconButtonThemeData(
style: ButtonStyle(
backgroundColor: .resolveWith(
(states) =>
buttonStyles.resolve({FButtonVariant.ghost}).base.decoration.resolve(toVariants(states)).color ??
Colors.transparent,
),
foregroundColor: .resolveWith(
(states) =>
buttonStyles
.resolve({FButtonVariant.ghost})
.base
.contentStyle
.textStyle
.resolve(toVariants(states))
.color ??
colors.secondaryForeground,
),
shape: .resolveWith(
(states) => RoundedRectangleBorder(
borderRadius:
buttonStyles
.resolve({FButtonVariant.ghost})
.base
.decoration
.resolve(toVariants(states))
.borderRadius ??
style.borderRadius,
),
),
),
),
segmentedButtonTheme: SegmentedButtonThemeData(
style: ButtonStyle(
textStyle: .resolveWith(
(states) =>
buttonStyles.resolve({FButtonVariant.ghost}).base.contentStyle.textStyle.resolve(toVariants(states)),
),
backgroundColor: .resolveWith(
(states) =>
buttonStyles.resolve({FButtonVariant.ghost}).base.decoration.resolve(toVariants(states)).color ??
Colors.transparent,
),
foregroundColor: .resolveWith(
(states) =>
buttonStyles
.resolve({FButtonVariant.ghost})
.base
.contentStyle
.textStyle
.resolve(toVariants(states))
.color ??
colors.secondaryForeground,
),
shape: .resolveWith(
(states) => RoundedRectangleBorder(
borderRadius:
buttonStyles
.resolve({FButtonVariant.ghost})
.base
.decoration
.resolve(toVariants(states))
.borderRadius ??
style.borderRadius,
),
),
),
),
/// Dialog
dialogTheme: DialogThemeData(shape: RoundedRectangleBorder(borderRadius: style.borderRadius)),
/// Bottom Sheet
bottomSheetTheme: BottomSheetThemeData(shape: RoundedRectangleBorder(borderRadius: style.borderRadius)),
/// Snack Bar
snackBarTheme: SnackBarThemeData(shape: RoundedRectangleBorder(borderRadius: style.borderRadius)),
/// List Tile
listTileTheme: ListTileThemeData(shape: RoundedRectangleBorder(borderRadius: style.borderRadius)),
/// Divider
dividerTheme: DividerThemeData(
color: dividerStyles.resolve({}).color,
thickness: dividerStyles.resolve({}).width,
),
iconTheme: IconThemeData(color: colors.primary, size: 20),
);
}