flex_seed_scheme 0.2.0-dev.2
flex_seed_scheme: ^0.2.0-dev.2 copied to clipboard
A more flexible version of Flutter's ColorScheme.fromSeed, use multiple seed colors, custom chroma and tone mapping.
example/lib/main.dart
import 'package:flex_seed_scheme/flex_seed_scheme.dart';
import 'package:flutter/material.dart';
// App breakpoints
const double phoneWidthBreakpoint = 550;
const double phoneHeightBreakpoint = 600;
// Define your seed colors.
const Color primarySeedColor = Color(0xFF6750A4);
const Color secondarySeedColor = Color(0xFF3871BB);
const Color tertiarySeedColor = Color(0xFF6CA450);
// Make a light ColorScheme from the seeds.
final ColorScheme schemeLight = SeedColorScheme.fromSeeds(
brightness: Brightness.light,
// Primary key color is required, like seed color ColorScheme.fromSeed.
primaryKey: primarySeedColor,
// You can add optional own seeds for secondary and tertiary key colors.
secondaryKey: secondarySeedColor,
tertiaryKey: tertiarySeedColor,
// Tone chroma config and tone mapping is optional, if you do not add it
// you get the config matching Flutter's Material 3 ColorScheme.fromSeed.
tones: FlexTones.vivid(Brightness.light),
);
// Make a dark ColorScheme from the seeds.
final ColorScheme schemeDark = SeedColorScheme.fromSeeds(
brightness: Brightness.dark,
primaryKey: primarySeedColor,
secondaryKey: secondarySeedColor,
tertiaryKey: tertiarySeedColor,
tones: FlexTones.vivid(Brightness.dark),
);
// Make a high contrast light ColorScheme from the seeds.
final ColorScheme schemeLightHc = SeedColorScheme.fromSeeds(
brightness: Brightness.light,
primaryKey: primarySeedColor,
secondaryKey: secondarySeedColor,
tertiaryKey: tertiarySeedColor,
tones: FlexTones.highContrast(Brightness.light),
);
// Make a ultra contrast dark ColorScheme from the seeds.
final ColorScheme schemeDarkHc = SeedColorScheme.fromSeeds(
brightness: Brightness.dark,
primaryKey: primarySeedColor,
secondaryKey: secondarySeedColor,
tertiaryKey: tertiarySeedColor,
tones: FlexTones.ultraContrast(Brightness.dark),
);
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
bool useMaterial3 = true;
ThemeMode themeMode = ThemeMode.light;
int counter = 0;
void _incrementCounter() {
setState(() {
counter++;
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'SeedColorScheme.fromSeeds Demo',
themeMode: themeMode,
//
// Make light and dark theme using the seeded color color schemes.
//
theme: ThemeData.from(
colorScheme: schemeLight,
useMaterial3: useMaterial3,
),
darkTheme: ThemeData.from(
colorScheme: schemeDark,
useMaterial3: useMaterial3,
),
//
// Define accessibility high contrast versions using same color base.
//
highContrastTheme: ThemeData.from(
colorScheme: schemeLightHc,
useMaterial3: useMaterial3,
),
highContrastDarkTheme: ThemeData.from(
colorScheme: schemeDarkHc,
useMaterial3: useMaterial3,
),
//
//
home: Scaffold(
appBar: AppBar(
title: const Text('SeedColorScheme.fromSeeds Demo'),
actions: <Widget>[
IconButton(
icon: useMaterial3
? const Icon(Icons.filter_3)
: const Icon(Icons.filter_2),
onPressed: () {
setState(() {
useMaterial3 = !useMaterial3;
});
},
tooltip: 'Switch to Material ${useMaterial3 ? 2 : 3}',
),
IconButton(
icon: themeMode == ThemeMode.dark
? const Icon(Icons.wb_sunny_outlined)
: const Icon(Icons.wb_sunny),
onPressed: () {
setState(() {
if (themeMode == ThemeMode.light) {
themeMode = ThemeMode.dark;
} else {
themeMode = ThemeMode.light;
}
});
},
tooltip: 'Toggle brightness',
),
],
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: ListView(
children: <Widget>[
const ShowColorSchemeColors(),
const SizedBox(height: 16),
const Text(
'You have pushed the button this many times:',
textAlign: TextAlign.center,
),
Text(
'$counter',
style: const TextStyle(fontSize: 30),
textAlign: TextAlign.center,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
),
);
}
}
/// Draw a number of boxes showing the colors of key theme color properties
/// in the ColorScheme of the inherited ThemeData and its color properties.
class ShowColorSchemeColors extends StatelessWidget {
const ShowColorSchemeColors({super.key, this.onBackgroundColor});
/// The color of the background the color widget are being drawn on.
///
/// Some of the theme colors may have semi transparent fill color. To compute
/// a legible text color for the sum when it shown on a background color, we
/// need to alpha merge it with background and we need the exact background
/// color it is drawn on for that. If not passed in from parent, it is
/// assumed to be drawn on card color, which usually is close enough.
final Color? onBackgroundColor;
// Return true if the color is light, meaning it needs dark text for contrast.
static bool _isLight(final Color color) =>
ThemeData.estimateBrightnessForColor(color) == Brightness.light;
// Return true if the color is dark, meaning it needs light text for contrast.
static bool _isDark(final Color color) =>
ThemeData.estimateBrightnessForColor(color) == Brightness.dark;
// On color used when a theme color property does not have a theme onColor.
static Color _onColor(final Color color, final Color bg) =>
_isLight(Color.alphaBlend(color, bg)) ? Colors.black : Colors.white;
@override
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
final ColorScheme colorScheme = theme.colorScheme;
final bool isDark = colorScheme.brightness == Brightness.dark;
final bool useMaterial3 = theme.useMaterial3;
final MediaQueryData media = MediaQuery.of(context);
final bool isPhone = media.size.width < phoneWidthBreakpoint ||
media.size.height < phoneHeightBreakpoint;
final double spacing = isPhone ? 3 : 6;
// Grab the card border from the theme card shape
ShapeBorder? border = theme.cardTheme.shape;
// If we had one, copy in a border side to it.
if (border is RoundedRectangleBorder) {
border = border.copyWith(
side: BorderSide(
color: theme.dividerColor,
width: 1,
),
);
// If
} else {
// If border was null, make one matching Card default, but with border
// side, if it was not null, we leave it as it was.
border ??= RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(useMaterial3 ? 12 : 4)),
side: BorderSide(
color: theme.dividerColor,
width: 1,
),
);
}
// Get effective background color.
final Color background =
onBackgroundColor ?? theme.cardTheme.color ?? theme.cardColor;
// Warning label for scaffold background when it uses to much blend.
final String surfaceTooHigh = isDark
? _isLight(theme.colorScheme.surface)
? '\nTOO HIGH'
: ''
: _isDark(theme.colorScheme.surface)
? '\nTOO HIGH'
: '';
// Warning label for scaffold background when it uses to much blend.
final String backTooHigh = isDark
? _isLight(theme.colorScheme.background)
? '\nTOO HIGH'
: ''
: _isDark(theme.colorScheme.background)
? '\nTOO HIGH'
: '';
// Wrap this widget branch in a custom theme where card has a border outline
// if it did not have one, but retains in ambient themed border radius.
return Theme(
data: Theme.of(context).copyWith(
cardTheme: CardTheme.of(context).copyWith(
elevation: 0,
shape: border,
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Text(
'ColorScheme Colors',
style: theme.textTheme.titleMedium,
),
),
Wrap(
alignment: WrapAlignment.start,
crossAxisAlignment: WrapCrossAlignment.center,
spacing: spacing,
runSpacing: spacing,
children: <Widget>[
ColorCard(
label: 'Primary',
color: colorScheme.primary,
textColor: colorScheme.onPrimary,
),
ColorCard(
label: 'on\nPrimary',
color: colorScheme.onPrimary,
textColor: colorScheme.primary,
),
ColorCard(
label: 'Primary\nContainer',
color: colorScheme.primaryContainer,
textColor: colorScheme.onPrimaryContainer,
),
ColorCard(
label: 'onPrimary\nContainer',
color: colorScheme.onPrimaryContainer,
textColor: colorScheme.primaryContainer,
),
ColorCard(
label: 'Secondary',
color: colorScheme.secondary,
textColor: colorScheme.onSecondary,
),
ColorCard(
label: 'on\nSecondary',
color: colorScheme.onSecondary,
textColor: colorScheme.secondary,
),
ColorCard(
label: 'Secondary\nContainer',
color: colorScheme.secondaryContainer,
textColor: colorScheme.onSecondaryContainer,
),
ColorCard(
label: 'on\nSecondary\nContainer',
color: colorScheme.onSecondaryContainer,
textColor: colorScheme.secondaryContainer,
),
ColorCard(
label: 'Tertiary',
color: colorScheme.tertiary,
textColor: colorScheme.onTertiary,
),
ColorCard(
label: 'on\nTertiary',
color: colorScheme.onTertiary,
textColor: colorScheme.tertiary,
),
ColorCard(
label: 'Tertiary\nContainer',
color: colorScheme.tertiaryContainer,
textColor: colorScheme.onTertiaryContainer,
),
ColorCard(
label: 'on\nTertiary\nContainer',
color: colorScheme.onTertiaryContainer,
textColor: colorScheme.tertiaryContainer,
),
ColorCard(
label: 'Error',
color: colorScheme.error,
textColor: colorScheme.onError,
),
ColorCard(
label: 'on\nError',
color: colorScheme.onError,
textColor: colorScheme.error,
),
ColorCard(
label: 'Error\nContainer',
color: colorScheme.errorContainer,
textColor: colorScheme.onErrorContainer,
),
ColorCard(
label: 'onError\nContainer',
color: colorScheme.onErrorContainer,
textColor: colorScheme.errorContainer,
),
ColorCard(
label: 'Background$backTooHigh',
color: colorScheme.background,
textColor: colorScheme.onBackground,
),
ColorCard(
label: 'on\nBackground',
color: colorScheme.onBackground,
textColor: colorScheme.background,
),
ColorCard(
label: 'Surface$surfaceTooHigh',
color: colorScheme.surface,
textColor: colorScheme.onSurface,
),
ColorCard(
label: 'on\nSurface',
color: colorScheme.onSurface,
textColor: colorScheme.surface,
),
ColorCard(
label: 'Surface\nVariant',
color: colorScheme.surfaceVariant,
textColor: colorScheme.onSurfaceVariant,
),
ColorCard(
label: 'onSurface\nVariant',
color: colorScheme.onSurfaceVariant,
textColor: colorScheme.surfaceVariant,
),
ColorCard(
label: 'Outline',
color: colorScheme.outline,
textColor: colorScheme.background,
),
ColorCard(
label: 'Shadow',
color: colorScheme.shadow,
textColor: _onColor(colorScheme.shadow, background),
),
ColorCard(
label: 'Inverse\nSurface',
color: colorScheme.inverseSurface,
textColor: colorScheme.onInverseSurface,
),
ColorCard(
label: 'onInverse\nSurface',
color: colorScheme.onInverseSurface,
textColor: colorScheme.inverseSurface,
),
ColorCard(
label: 'Inverse\nPrimary',
color: colorScheme.inversePrimary,
textColor: colorScheme.inverseSurface,
),
ColorCard(
label: 'Surface\nTint',
color: colorScheme.surfaceTint,
textColor: colorScheme.onPrimary,
),
],
),
],
),
);
}
}
/// A [SizedBox] with a [Card] and string text in it. Used in this demo to
/// display theme color boxes.
///
/// Can specify label text color and background color.
class ColorCard extends StatelessWidget {
const ColorCard({
super.key,
required this.label,
required this.color,
required this.textColor,
this.size,
});
final String label;
final Color color;
final Color textColor;
final Size? size;
@override
Widget build(BuildContext context) {
final MediaQueryData media = MediaQuery.of(context);
final bool isPhone = media.size.width < phoneWidthBreakpoint ||
media.size.height < phoneHeightBreakpoint;
final double fontSize = isPhone ? 10 : 11;
final Size effectiveSize =
size ?? (isPhone ? const Size(74, 54) : const Size(86, 58));
return SizedBox(
width: effectiveSize.width,
height: effectiveSize.height,
child: Card(
margin: EdgeInsets.zero,
clipBehavior: Clip.antiAlias,
color: color,
child: Center(
child: Text(
label,
style: TextStyle(color: textColor, fontSize: fontSize),
textAlign: TextAlign.center,
),
),
),
);
}
}