make ColorScheme configurable

pull/2115/head
Qun Cheng 1 year ago committed by Leigha Jarett
parent 4287e7caed
commit b0023ead5d

@ -4,6 +4,10 @@
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_colorpicker/flutter_colorpicker.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:material_3_demo/home.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
const Widget divider = SizedBox(height: 10); const Widget divider = SizedBox(height: 10);
@ -14,20 +18,19 @@ const Widget divider = SizedBox(height: 10);
const double narrowScreenWidthThreshold = 400; const double narrowScreenWidthThreshold = 400;
class ColorPalettesScreen extends StatelessWidget { class ColorPalettesScreen extends StatelessWidget {
const ColorPalettesScreen({super.key}); const ColorPalettesScreen({
super.key,
required this.handleColorRoleChange,
required this.lightColorScheme,
required this.darkColorScheme,
});
final ConfigColorSchemeCallback handleColorRoleChange;
final ColorScheme lightColorScheme;
final ColorScheme darkColorScheme;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Color selectedColor = Theme.of(context).primaryColor;
ThemeData lightTheme = ThemeData(
colorSchemeSeed: selectedColor,
brightness: Brightness.light,
);
ThemeData darkTheme = ThemeData(
colorSchemeSeed: selectedColor,
brightness: Brightness.dark,
);
Widget schemeLabel(String brightness) { Widget schemeLabel(String brightness) {
return Padding( return Padding(
padding: const EdgeInsets.symmetric(vertical: 15), padding: const EdgeInsets.symmetric(vertical: 15),
@ -38,11 +41,13 @@ class ColorPalettesScreen extends StatelessWidget {
); );
} }
Widget schemeView(ThemeData theme) { Widget schemeView(Brightness brightness) {
return Padding( return Padding(
padding: const EdgeInsets.symmetric(horizontal: 15), padding: const EdgeInsets.symmetric(horizontal: 15),
child: ColorSchemeView( child: ColorSchemeView(
colorScheme: theme.colorScheme, handleColorRoleChange: handleColorRoleChange,
colorScheme: brightness == Brightness.light ? lightColorScheme : darkColorScheme,
brightness: brightness,
), ),
); );
} }
@ -83,11 +88,11 @@ class ColorPalettesScreen extends StatelessWidget {
dynamicColorNotice(), dynamicColorNotice(),
divider, divider,
schemeLabel('Light ColorScheme'), schemeLabel('Light ColorScheme'),
schemeView(lightTheme), schemeView(Brightness.light),
divider, divider,
divider, divider,
schemeLabel('Dark ColorScheme'), schemeLabel('Dark ColorScheme'),
schemeView(darkTheme), schemeView(Brightness.dark),
], ],
), ),
); );
@ -104,7 +109,7 @@ class ColorPalettesScreen extends StatelessWidget {
child: Column( child: Column(
children: [ children: [
schemeLabel('Light ColorScheme'), schemeLabel('Light ColorScheme'),
schemeView(lightTheme), schemeView(Brightness.light),
], ],
), ),
), ),
@ -112,7 +117,7 @@ class ColorPalettesScreen extends StatelessWidget {
child: Column( child: Column(
children: [ children: [
schemeLabel('Dark ColorScheme'), schemeLabel('Dark ColorScheme'),
schemeView(darkTheme), schemeView(Brightness.dark),
], ],
), ),
), ),
@ -128,10 +133,38 @@ class ColorPalettesScreen extends StatelessWidget {
} }
} }
class ColorSchemeView extends StatelessWidget { class ColorSchemeView extends StatefulWidget {
const ColorSchemeView({super.key, required this.colorScheme}); const ColorSchemeView({
super.key,
required this.handleColorRoleChange,
required this.colorScheme,
required this.brightness,
});
final ConfigColorSchemeCallback handleColorRoleChange;
final ColorScheme colorScheme; final ColorScheme colorScheme;
final Brightness brightness;
@override
State<ColorSchemeView> createState() => _ColorSchemeViewState();
}
class _ColorSchemeViewState extends State<ColorSchemeView> {
late ColorScheme _colorScheme;
@override
void initState() {
_colorScheme = widget.colorScheme;
super.initState();
}
@override
void didUpdateWidget(covariant ColorSchemeView oldWidget) {
if (oldWidget.colorScheme != widget.colorScheme) {
_colorScheme = widget.colorScheme;
}
super.didUpdateWidget(oldWidget);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -139,185 +172,395 @@ class ColorSchemeView extends StatelessWidget {
children: [ children: [
ColorGroup( ColorGroup(
children: [ children: [
ColorChip( EditableColorChip(
label: 'primary', label: 'primary',
color: colorScheme.primary, color: _colorScheme.primary,
onColor: colorScheme.onPrimary, onColor: _colorScheme.onPrimary,
updateColorScheme: (color) {
_colorScheme = _colorScheme.copyWith(primary: color);
widget.handleColorRoleChange(
widget.brightness,
primary: color,
);
},
), ),
ColorChip( EditableColorChip(
label: 'onPrimary', label: 'onPrimary',
color: colorScheme.onPrimary, color: _colorScheme.onPrimary,
onColor: colorScheme.primary, onColor: _colorScheme.primary,
updateColorScheme: (color) {
_colorScheme = _colorScheme.copyWith(onPrimary: color);
widget.handleColorRoleChange(
widget.brightness,
onPrimary: color,
);
},
), ),
ColorChip( EditableColorChip(
label: 'primaryContainer', label: 'primaryContainer',
color: colorScheme.primaryContainer, color: _colorScheme.primaryContainer,
onColor: colorScheme.onPrimaryContainer, onColor: _colorScheme.onPrimaryContainer,
updateColorScheme: (color) {
_colorScheme = _colorScheme.copyWith(primaryContainer: color);
widget.handleColorRoleChange(
widget.brightness,
primaryContainer: color,
);
},
), ),
ColorChip( EditableColorChip(
label: 'onPrimaryContainer', label: 'onPrimaryContainer',
color: colorScheme.onPrimaryContainer, color: _colorScheme.onPrimaryContainer,
onColor: colorScheme.primaryContainer, onColor: _colorScheme.primaryContainer,
updateColorScheme: (color) {
_colorScheme = _colorScheme.copyWith(onPrimaryContainer: color);
widget.handleColorRoleChange(
widget.brightness,
onPrimaryContainer: color,
);
},
), ),
], ],
), ),
divider, divider,
ColorGroup( ColorGroup(
children: [ children: [
ColorChip( EditableColorChip(
label: 'secondary', label: 'secondary',
color: colorScheme.secondary, color: _colorScheme.secondary,
onColor: colorScheme.onSecondary, onColor: _colorScheme.onSecondary,
updateColorScheme: (color) {
_colorScheme = _colorScheme.copyWith(secondary: color);
widget.handleColorRoleChange(
widget.brightness,
secondary: color,
);
},
), ),
ColorChip( EditableColorChip(
label: 'onSecondary', label: 'onSecondary',
color: colorScheme.onSecondary, color: _colorScheme.onSecondary,
onColor: colorScheme.secondary, onColor: _colorScheme.secondary,
updateColorScheme: (color) {
_colorScheme = _colorScheme.copyWith(onSecondary: color);
widget.handleColorRoleChange(
widget.brightness,
onSecondary: color,
);
},
), ),
ColorChip( EditableColorChip(
label: 'secondaryContainer', label: 'secondaryContainer',
color: colorScheme.secondaryContainer, color: _colorScheme.secondaryContainer,
onColor: colorScheme.onSecondaryContainer, onColor: _colorScheme.onSecondaryContainer,
updateColorScheme: (color) {
_colorScheme = _colorScheme.copyWith(secondaryContainer: color);
widget.handleColorRoleChange(
widget.brightness,
secondaryContainer: color,
);
},
), ),
ColorChip( EditableColorChip(
label: 'onSecondaryContainer', label: 'onSecondaryContainer',
color: colorScheme.onSecondaryContainer, color: _colorScheme.onSecondaryContainer,
onColor: colorScheme.secondaryContainer, onColor: _colorScheme.secondaryContainer,
updateColorScheme: (color) {
_colorScheme = _colorScheme.copyWith(onSecondaryContainer: color);
widget.handleColorRoleChange(
widget.brightness,
onSecondaryContainer: color,
);
},
), ),
], ],
), ),
divider, divider,
ColorGroup( ColorGroup(
children: [ children: [
ColorChip( EditableColorChip(
label: 'tertiary', label: 'tertiary',
color: colorScheme.tertiary, color: _colorScheme.tertiary,
onColor: colorScheme.onTertiary, onColor: _colorScheme.onTertiary,
updateColorScheme: (color) {
_colorScheme = _colorScheme.copyWith(tertiary: color);
widget.handleColorRoleChange(
widget.brightness,
tertiary: color,
);
},
), ),
ColorChip( EditableColorChip(
label: 'onTertiary', label: 'onTertiary',
color: colorScheme.onTertiary, color: _colorScheme.onTertiary,
onColor: colorScheme.tertiary, onColor: _colorScheme.tertiary,
updateColorScheme: (color) {
_colorScheme = _colorScheme.copyWith(onTertiary: color);
widget.handleColorRoleChange(
widget.brightness,
onTertiary: color,
);
},
), ),
ColorChip( EditableColorChip(
label: 'tertiaryContainer', label: 'tertiaryContainer',
color: colorScheme.tertiaryContainer, color: _colorScheme.tertiaryContainer,
onColor: colorScheme.onTertiaryContainer, onColor: _colorScheme.onTertiaryContainer,
updateColorScheme: (color) {
_colorScheme = _colorScheme.copyWith(tertiaryContainer: color);
widget.handleColorRoleChange(
widget.brightness,
tertiaryContainer: color,
);
},
), ),
ColorChip( EditableColorChip(
label: 'onTertiaryContainer', label: 'onTertiaryContainer',
color: colorScheme.onTertiaryContainer, color: _colorScheme.onTertiaryContainer,
onColor: colorScheme.tertiaryContainer, onColor: _colorScheme.tertiaryContainer,
updateColorScheme: (color) {
_colorScheme = _colorScheme.copyWith(onTertiaryContainer: color);
widget.handleColorRoleChange(
widget.brightness,
onTertiaryContainer: color,
);
},
), ),
], ],
), ),
divider, divider,
ColorGroup( ColorGroup(
children: [ children: [
ColorChip( EditableColorChip(
label: 'error', label: 'error',
color: colorScheme.error, color: _colorScheme.error,
onColor: colorScheme.onError, onColor: _colorScheme.onError,
updateColorScheme: (color) {
_colorScheme = _colorScheme.copyWith(error: color);
widget.handleColorRoleChange(
widget.brightness,
error: color,
);
},
), ),
ColorChip( EditableColorChip(
label: 'onError', label: 'onError',
color: colorScheme.onError, color: _colorScheme.onError,
onColor: colorScheme.error, onColor: _colorScheme.error,
updateColorScheme: (color) {
_colorScheme = _colorScheme.copyWith(onError: color);
widget.handleColorRoleChange(
widget.brightness,
onError: color,
);
},
), ),
ColorChip( EditableColorChip(
label: 'errorContainer', label: 'errorContainer',
color: colorScheme.errorContainer, color: _colorScheme.errorContainer,
onColor: colorScheme.onErrorContainer, onColor: _colorScheme.onErrorContainer,
updateColorScheme: (color) {
_colorScheme = _colorScheme.copyWith(errorContainer: color);
widget.handleColorRoleChange(
widget.brightness,
errorContainer: color,
);
},
), ),
ColorChip( EditableColorChip(
label: 'onErrorContainer', label: 'onErrorContainer',
color: colorScheme.onErrorContainer, color: _colorScheme.onErrorContainer,
onColor: colorScheme.errorContainer, onColor: _colorScheme.errorContainer,
updateColorScheme: (color) {
_colorScheme = _colorScheme.copyWith(onErrorContainer: color);
widget.handleColorRoleChange(
widget.brightness,
onErrorContainer: color,
);
},
), ),
], ],
), ),
divider, divider,
ColorGroup( ColorGroup(
children: [ children: [
ColorChip( EditableColorChip(
label: 'surface', label: 'surface',
color: colorScheme.surface, color: _colorScheme.surface,
onColor: colorScheme.onSurface, onColor: _colorScheme.onSurface,
updateColorScheme: (color) {
_colorScheme = _colorScheme.copyWith(surface: color);
widget.handleColorRoleChange(
widget.brightness,
surface: color,
);
},
), ),
ColorChip( EditableColorChip(
label: 'onSurface', label: 'onSurface',
color: colorScheme.onSurface, color: _colorScheme.onSurface,
onColor: colorScheme.surface, onColor: _colorScheme.surface,
updateColorScheme: (color) {
_colorScheme = _colorScheme.copyWith(onSurface: color);
widget.handleColorRoleChange(
widget.brightness,
onSurface: color,
);
},
), ),
ColorChip( EditableColorChip(
label: 'surfaceVariant', label: 'surfaceVariant',
color: colorScheme.surfaceVariant, color: _colorScheme.surfaceVariant,
onColor: colorScheme.onSurfaceVariant, onColor: _colorScheme.onSurfaceVariant,
updateColorScheme: (color) {
_colorScheme = _colorScheme.copyWith(surfaceVariant: color);
widget.handleColorRoleChange(
widget.brightness,
surfaceVariant: color,
);
},
), ),
ColorChip( EditableColorChip(
label: 'onSurfaceVariant', label: 'onSurfaceVariant',
color: colorScheme.onSurfaceVariant, color: _colorScheme.onSurfaceVariant,
onColor: colorScheme.surfaceVariant, onColor: _colorScheme.surfaceVariant,
updateColorScheme: (color) {
_colorScheme = _colorScheme.copyWith(onSurfaceVariant: color);
widget.handleColorRoleChange(
widget.brightness,
onSurfaceVariant: color,
);
},
), ),
ColorChip( EditableColorChip(
label: 'surfaceTint', label: 'surfaceTint',
color: colorScheme.surfaceTint, color: _colorScheme.surfaceTint,
updateColorScheme: (color) {
_colorScheme = _colorScheme.copyWith(surfaceTint: color);
widget.handleColorRoleChange(
widget.brightness,
surfaceTint: color,
);
},
), ),
], ],
), ),
divider, divider,
ColorGroup( ColorGroup(
children: [ children: [
ColorChip( EditableColorChip(
label: 'outline', label: 'outline',
color: colorScheme.outline, color: _colorScheme.outline,
updateColorScheme: (color) {
_colorScheme = _colorScheme.copyWith(outline: color);
widget.handleColorRoleChange(
widget.brightness,
outline: color,
);
},
), ),
ColorChip( EditableColorChip(
label: 'outlineVariant', label: 'outlineVariant',
color: colorScheme.outlineVariant, color: _colorScheme.outlineVariant,
updateColorScheme: (color) {
_colorScheme = _colorScheme.copyWith(outlineVariant: color);
widget.handleColorRoleChange(
widget.brightness,
outlineVariant: color,
);
},
), ),
], ],
), ),
divider, divider,
ColorGroup( ColorGroup(
children: [ children: [
ColorChip( EditableColorChip(
label: 'inverseSurface', label: 'inverseSurface',
color: colorScheme.inverseSurface, color: _colorScheme.inverseSurface,
onColor: colorScheme.onInverseSurface, onColor: _colorScheme.onInverseSurface,
updateColorScheme: (color) {
_colorScheme = _colorScheme.copyWith(inverseSurface: color);
widget.handleColorRoleChange(
widget.brightness,
inverseSurface: color,
);
},
), ),
ColorChip( EditableColorChip(
label: 'onInverseSurface', label: 'onInverseSurface',
color: colorScheme.onInverseSurface, color: _colorScheme.onInverseSurface,
onColor: colorScheme.inverseSurface, onColor: _colorScheme.inverseSurface,
updateColorScheme: (color) {
_colorScheme = _colorScheme.copyWith(onInverseSurface: color);
widget.handleColorRoleChange(
widget.brightness,
onInverseSurface: color,
);
},
), ),
ColorChip( EditableColorChip(
label: 'inversePrimary', label: 'inversePrimary',
color: colorScheme.inversePrimary, color: _colorScheme.inversePrimary,
onColor: colorScheme.primary, onColor: _colorScheme.primary,
updateColorScheme: (color) {
_colorScheme = _colorScheme.copyWith(inversePrimary: color);
widget.handleColorRoleChange(
widget.brightness,
inversePrimary: color,
);
},
), ),
], ],
), ),
divider, divider,
ColorGroup( ColorGroup(
children: [ children: [
ColorChip( EditableColorChip(
label: 'background', label: 'background',
color: colorScheme.background, color: _colorScheme.background,
onColor: colorScheme.onBackground, onColor: _colorScheme.onBackground,
updateColorScheme: (color) {
_colorScheme = _colorScheme.copyWith(background: color);
widget.handleColorRoleChange(
widget.brightness,
background: color,
);
},
), ),
ColorChip( EditableColorChip(
label: 'onBackground', label: 'onBackground',
color: colorScheme.onBackground, color: _colorScheme.onBackground,
onColor: colorScheme.background, onColor: _colorScheme.background,
updateColorScheme: (color) {
_colorScheme = _colorScheme.copyWith(onBackground: color);
widget.handleColorRoleChange(
widget.brightness,
onBackground: color,
);
},
), ),
ColorChip( EditableColorChip(
label: 'scrim', label: 'scrim',
color: colorScheme.scrim, color: _colorScheme.scrim,
updateColorScheme: (color) {
_colorScheme = _colorScheme.copyWith(scrim: color);
widget.handleColorRoleChange(
widget.brightness,
scrim: color,
);
},
), ),
ColorChip( EditableColorChip(
label: 'shadow', label: 'shadow',
color: colorScheme.shadow, color: _colorScheme.shadow,
updateColorScheme: (color) {
_colorScheme = _colorScheme.copyWith(shadow: color);
widget.handleColorRoleChange(
widget.brightness,
shadow: color,
);
},
), ),
], ],
), ),
@ -327,6 +570,85 @@ class ColorSchemeView extends StatelessWidget {
} }
} }
class EditableColorChip extends StatefulWidget {
const EditableColorChip({
super.key,
this.onColor,
required this.label,
required this.color,
required this.updateColorScheme,
});
final String label;
final Color color;
final Color? onColor;
final void Function(Color) updateColorScheme;
@override
State<EditableColorChip> createState() => _EditableColorChipState();
}
class _EditableColorChipState extends State<EditableColorChip> {
TextEditingController textController = TextEditingController();
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
showDialog<void>(
context: context,
builder: (context) {
return AlertDialog(
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
ColorPicker(
pickerColor: widget.color,
onColorChanged: widget.updateColorScheme,
paletteType: PaletteType.hsvWithHue,
labelTypes: const [
ColorLabelType.rgb,
ColorLabelType.hsv,
ColorLabelType.hsl
],
pickerAreaBorderRadius: const BorderRadius.all(Radius.circular(10)),
hexInputController: textController,
portraitOnly: true,
),
Padding(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
child: TextField(
controller: textController,
decoration: InputDecoration(
prefixText: '#',
suffix: IconButton(
icon: const Icon(Icons.content_paste_rounded),
onPressed: () => copyToClipboard(textController.text),
),
),
autofocus: true,
maxLength: 8,
inputFormatters: [
UpperCaseTextFormatter(),
FilteringTextInputFormatter.allow(RegExp(kValidHexPattern)),
],
),
)
],
),
);
}
);
},
child: ColorChip(
label: widget.label,
color: widget.color,
onColor: widget.onColor,
),
);
}
}
class ColorGroup extends StatelessWidget { class ColorGroup extends StatelessWidget {
const ColorGroup({super.key, required this.children}); const ColorGroup({super.key, required this.children});
@ -336,6 +658,7 @@ class ColorGroup extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return RepaintBoundary( return RepaintBoundary(
child: Card( child: Card(
elevation: 0.0,
clipBehavior: Clip.antiAlias, clipBehavior: Clip.antiAlias,
child: Column( child: Column(
children: children, children: children,
@ -378,9 +701,23 @@ class ColorChip extends StatelessWidget {
child: Row( child: Row(
children: [ children: [
Expanded(child: Text(label, style: TextStyle(color: labelColor))), Expanded(child: Text(label, style: TextStyle(color: labelColor))),
Text(
color.value.toRadixString(16).substring(2),
style: GoogleFonts.spaceMono(
color: labelColor,
),
),
], ],
), ),
), ),
); );
} }
} }
void copyToClipboard(String input) {
String textToCopy = input.replaceFirst('#', '').toUpperCase();
if (textToCopy.startsWith('FF') && textToCopy.length == 8) {
textToCopy = textToCopy.replaceFirst('FF', '');
}
Clipboard.setData(ClipboardData(text: '#$textToCopy'));
}

@ -10,14 +10,50 @@ import 'constants.dart';
import 'elevation_screen.dart'; import 'elevation_screen.dart';
import 'typography_screen.dart'; import 'typography_screen.dart';
typedef ConfigColorSchemeCallback = void Function(Brightness brightness,
{ Color? primary,
Color? onPrimary,
Color? primaryContainer,
Color? onPrimaryContainer,
Color? secondary,
Color? onSecondary,
Color? secondaryContainer,
Color? onSecondaryContainer,
Color? tertiary,
Color? onTertiary,
Color? tertiaryContainer,
Color? onTertiaryContainer,
Color? error,
Color? onError,
Color? errorContainer,
Color? onErrorContainer,
Color? background,
Color? onBackground,
Color? surface,
Color? onSurface,
Color? surfaceVariant,
Color? onSurfaceVariant,
Color? outline,
Color? outlineVariant,
Color? shadow,
Color? scrim,
Color? inverseSurface,
Color? onInverseSurface,
Color? inversePrimary,
Color? surfaceTint,
});
class Home extends StatefulWidget { class Home extends StatefulWidget {
const Home({ const Home({
super.key, super.key,
required this.useLightMode, required this.useLightMode,
required this.useMaterial3, required this.useMaterial3,
required this.colorSelected, required this.colorSelected,
required this.lightColors,
required this.darkColors,
required this.handleBrightnessChange, required this.handleBrightnessChange,
required this.handleMaterialVersionChange, required this.handleMaterialVersionChange,
required this.handleColorRoleChange,
required this.handleColorSelect, required this.handleColorSelect,
required this.handleImageSelect, required this.handleImageSelect,
required this.colorSelectionMethod, required this.colorSelectionMethod,
@ -27,6 +63,8 @@ class Home extends StatefulWidget {
final bool useLightMode; final bool useLightMode;
final bool useMaterial3; final bool useMaterial3;
final ColorSeed colorSelected; final ColorSeed colorSelected;
final ColorScheme lightColors;
final ColorScheme darkColors;
final ColorImageProvider imageSelected; final ColorImageProvider imageSelected;
final ColorSelectionMethod colorSelectionMethod; final ColorSelectionMethod colorSelectionMethod;
@ -34,6 +72,7 @@ class Home extends StatefulWidget {
final void Function() handleMaterialVersionChange; final void Function() handleMaterialVersionChange;
final void Function(int value) handleColorSelect; final void Function(int value) handleColorSelect;
final void Function(int value) handleImageSelect; final void Function(int value) handleImageSelect;
final ConfigColorSchemeCallback handleColorRoleChange;
@override @override
State<Home> createState() => _HomeState(); State<Home> createState() => _HomeState();
@ -124,7 +163,11 @@ class _HomeState extends State<Home> with SingleTickerProviderStateMixin {
), ),
); );
case ScreenSelected.color: case ScreenSelected.color:
return const ColorPalettesScreen(); return ColorPalettesScreen(
handleColorRoleChange: widget.handleColorRoleChange,
lightColorScheme: widget.lightColors,
darkColorScheme: widget.darkColors,
);
case ScreenSelected.typography: case ScreenSelected.typography:
return const TypographyScreen(); return const TypographyScreen();
case ScreenSelected.elevation: case ScreenSelected.elevation:

@ -27,6 +27,8 @@ class _AppState extends State<App> {
ColorImageProvider imageSelected = ColorImageProvider.leaves; ColorImageProvider imageSelected = ColorImageProvider.leaves;
ColorScheme? imageColorScheme = const ColorScheme.light(); ColorScheme? imageColorScheme = const ColorScheme.light();
ColorSelectionMethod colorSelectionMethod = ColorSelectionMethod.colorSeed; ColorSelectionMethod colorSelectionMethod = ColorSelectionMethod.colorSeed;
ColorScheme? lightColorScheme;
ColorScheme? darkColorScheme;
bool get useLightMode { bool get useLightMode {
switch (themeMode) { switch (themeMode) {
@ -53,6 +55,7 @@ class _AppState extends State<App> {
} }
void handleColorSelect(int value) { void handleColorSelect(int value) {
clearColorScheme();
setState(() { setState(() {
colorSelectionMethod = ColorSelectionMethod.colorSeed; colorSelectionMethod = ColorSelectionMethod.colorSeed;
colorSelected = ColorSeed.values[value]; colorSelected = ColorSeed.values[value];
@ -60,6 +63,7 @@ class _AppState extends State<App> {
} }
void handleImageSelect(int value) { void handleImageSelect(int value) {
clearColorScheme();
final String url = ColorImageProvider.values[value].url; final String url = ColorImageProvider.values[value].url;
ColorScheme.fromImageProvider(provider: NetworkImage(url)) ColorScheme.fromImageProvider(provider: NetworkImage(url))
.then((newScheme) { .then((newScheme) {
@ -71,13 +75,92 @@ class _AppState extends State<App> {
}); });
} }
void handleColorRoleChange(Brightness brightness,
{ Color? primary,
Color? onPrimary,
Color? primaryContainer,
Color? onPrimaryContainer,
Color? secondary,
Color? onSecondary,
Color? secondaryContainer,
Color? onSecondaryContainer,
Color? tertiary,
Color? onTertiary,
Color? tertiaryContainer,
Color? onTertiaryContainer,
Color? error,
Color? onError,
Color? errorContainer,
Color? onErrorContainer,
Color? background,
Color? onBackground,
Color? surface,
Color? onSurface,
Color? surfaceVariant,
Color? onSurfaceVariant,
Color? outline,
Color? outlineVariant,
Color? shadow,
Color? scrim,
Color? inverseSurface,
Color? onInverseSurface,
Color? inversePrimary,
Color? surfaceTint,
}
) {
ColorScheme? copyWith(ColorScheme? colorScheme) {
return colorScheme?.copyWith(
primary: primary,
onPrimary: onPrimary,
primaryContainer: primaryContainer,
onPrimaryContainer: onPrimaryContainer,
secondary: secondary,
onSecondary: onSecondary,
secondaryContainer: secondaryContainer,
onSecondaryContainer: onSecondaryContainer,
tertiary: tertiary,
onTertiary: onTertiary,
tertiaryContainer: tertiaryContainer,
onTertiaryContainer: onTertiaryContainer,
error: error,
onError: onError,
errorContainer: errorContainer,
onErrorContainer: onErrorContainer,
background: background,
onBackground: onBackground,
surface: surface,
onSurface: onSurface,
surfaceVariant: surfaceVariant,
onSurfaceVariant: onSurfaceVariant,
outline: outline,
outlineVariant: outlineVariant,
shadow: shadow,
scrim: scrim,
inverseSurface: inverseSurface,
onInverseSurface: onInverseSurface,
inversePrimary: inversePrimary,
surfaceTint: surfaceTint,
);
}
setState(() {
switch (brightness) {
case Brightness.light:
lightColorScheme = copyWith(lightColorScheme);
case Brightness.dark:
darkColorScheme = copyWith(darkColorScheme);
}
});
}
void clearColorScheme() {
lightColorScheme = null;
darkColorScheme = null;
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MaterialApp( ThemeData lightTheme = ThemeData(
debugShowCheckedModeBanner: false,
title: 'Material 3',
themeMode: themeMode,
theme: ThemeData(
colorSchemeSeed: colorSelectionMethod == ColorSelectionMethod.colorSeed colorSchemeSeed: colorSelectionMethod == ColorSelectionMethod.colorSeed
? colorSelected.color ? colorSelected.color
: null, : null,
@ -86,23 +169,41 @@ class _AppState extends State<App> {
: null, : null,
useMaterial3: useMaterial3, useMaterial3: useMaterial3,
brightness: Brightness.light, brightness: Brightness.light,
), ).copyWith(
darkTheme: ThemeData( colorScheme: lightColorScheme,
);
ThemeData darkTheme = ThemeData(
colorSchemeSeed: colorSelectionMethod == ColorSelectionMethod.colorSeed colorSchemeSeed: colorSelectionMethod == ColorSelectionMethod.colorSeed
? colorSelected.color ? colorSelected.color
: imageColorScheme!.primary, : imageColorScheme!.primary,
useMaterial3: useMaterial3, useMaterial3: useMaterial3,
brightness: Brightness.dark, brightness: Brightness.dark,
), ).copyWith(
colorScheme: darkColorScheme,
);
lightColorScheme = lightTheme.colorScheme;
darkColorScheme = darkTheme.colorScheme;
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Material 3',
themeMode: themeMode,
theme: lightTheme,
darkTheme: darkTheme,
home: Home( home: Home(
useLightMode: useLightMode, useLightMode: useLightMode,
useMaterial3: useMaterial3, useMaterial3: useMaterial3,
colorSelected: colorSelected, colorSelected: colorSelected,
lightColors: lightTheme.colorScheme,
darkColors: darkTheme.colorScheme,
imageSelected: imageSelected, imageSelected: imageSelected,
handleBrightnessChange: handleBrightnessChange, handleBrightnessChange: handleBrightnessChange,
handleMaterialVersionChange: handleMaterialVersionChange, handleMaterialVersionChange: handleMaterialVersionChange,
handleColorSelect: handleColorSelect, handleColorSelect: handleColorSelect,
handleImageSelect: handleImageSelect, handleImageSelect: handleImageSelect,
handleColorRoleChange: handleColorRoleChange,
colorSelectionMethod: colorSelectionMethod, colorSelectionMethod: colorSelectionMethod,
), ),
); );

@ -16,6 +16,8 @@ dependencies:
cupertino_icons: ^1.0.2 cupertino_icons: ^1.0.2
url_launcher: ^6.1.8 url_launcher: ^6.1.8
google_fonts: ^6.1.0
flutter_colorpicker: ^1.0.3
dev_dependencies: dev_dependencies:
analysis_defaults: analysis_defaults:

@ -4,6 +4,7 @@
// ignore_for_file: avoid_types_on_closure_parameters // ignore_for_file: avoid_types_on_closure_parameters
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:material_3_demo/color_palettes_screen.dart'; import 'package:material_3_demo/color_palettes_screen.dart';
import 'package:material_3_demo/main.dart'; import 'package:material_3_demo/main.dart';
@ -65,11 +66,134 @@ void main() {
}); });
testWidgets('Color screen shows correct content', (tester) async { testWidgets('Color screen shows correct content', (tester) async {
await tester.pumpWidget(const MaterialApp( widgetSetup(tester, 1200);
home: Scaffold(body: Row(children: [ColorPalettesScreen()])), addTearDown(tester.view.resetPhysicalSize);
)); await tester.pumpWidget(const App());
Finder colorPageIndicator = find.descendant(of: find.byType(NavigationRail),
matching: find.byIcon(Icons.format_paint_outlined));
expect(colorPageIndicator, findsOneWidget);
await tester.tap(colorPageIndicator);
await tester.pumpAndSettle();
expect(find.text('Light ColorScheme'), findsOneWidget); expect(find.text('Light ColorScheme'), findsOneWidget);
expect(find.text('Dark ColorScheme'), findsOneWidget); expect(find.text('Dark ColorScheme'), findsOneWidget);
expect(find.byType(ColorGroup, skipOffstage: false), findsNWidgets(16)); expect(find.byType(ColorGroup, skipOffstage: false), findsNWidgets(16));
}); });
testWidgets('Color screen shows correct content', (tester) async {
widgetSetup(tester, 1200);
addTearDown(tester.view.resetPhysicalSize);
await tester.pumpWidget(const App());
Finder colorPageIndicator = find.descendant(of: find.byType(NavigationRail),
matching: find.byIcon(Icons.format_paint_outlined));
expect(colorPageIndicator, findsOneWidget);
await tester.tap(colorPageIndicator);
await tester.pumpAndSettle();
expect(find.text('Light ColorScheme'), findsOneWidget);
expect(find.text('Dark ColorScheme'), findsOneWidget);
expect(find.byType(ColorGroup, skipOffstage: false), findsNWidgets(16));
});
testWidgets('ColorScheme are editable', (tester) async {
widgetSetup(tester, 1200);
addTearDown(tester.view.resetPhysicalSize);
await tester.pumpWidget(const App());
await updatePrimaryChipColor(tester, const Color(0xFF73A450), true);
expect(find.text('73a450'), findsOneWidget);
// Test if the chip color is changed to 73a450.
Finder primaryChip = find.descendant(of: find.widgetWithText(ColorChip, 'primary').first, matching: find.byType(Container));
Container container = tester.widget<Container>(primaryChip);
expect(container.color, const Color(0xFF73A450));
});
testWidgets('Light ColorScheme is configurable; changes can be applied to the widget page', (tester) async {
widgetSetup(tester, 1200);
addTearDown(tester.view.resetPhysicalSize);
await tester.pumpWidget(const App());
Color textColor(Finder text) {
return tester.renderObject<RenderParagraph>(text).text.style!.color!;
}
await updatePrimaryChipColor(tester, const Color(0xff123456), true);
// Go to the component screen
Finder componentPageIndicator = find.descendant(of: find.byType(NavigationRail),
matching: find.byIcon(Icons.widgets_outlined));
expect(componentPageIndicator, findsOneWidget);
await tester.tap(componentPageIndicator);
await tester.pumpAndSettle();
Finder elevatedButtonText = find.text('Elevated').first;
expect(textColor(elevatedButtonText), const Color(0xff123456));
Finder outlinedButtonText = find.text('Outlined').first;
expect(textColor(outlinedButtonText), const Color(0xff123456));
Finder textButtonText = find.text('Text').first;
expect(textColor(textButtonText), const Color(0xff123456));
final Material material = tester.widget<Material>(find.descendant(
of: find.byType(FilledButton).first,
matching: find.byType(Material),
));
expect(material.color, const Color(0xff123456));
});
testWidgets('Dark ColorScheme is configurable; changes can be applied to the widget page', (tester) async {
widgetSetup(tester, 1200);
addTearDown(tester.view.resetPhysicalSize);
await tester.pumpWidget(const App());
Color textColor(Finder text) {
return tester.renderObject<RenderParagraph>(text).text.style!.color!;
}
// Switch to dark mode
await tester.tap(find.byIcon(Icons.dark_mode_outlined));
await tester.pumpAndSettle();
await updatePrimaryChipColor(tester, const Color(0xff654321), false);
// Go to the component screen
Finder componentPageIndicator = find.descendant(of: find.byType(NavigationRail),
matching: find.byIcon(Icons.widgets_outlined));
expect(componentPageIndicator, findsOneWidget);
await tester.tap(componentPageIndicator);
await tester.pumpAndSettle();
Finder elevatedButtonText = find.text('Elevated').first;
expect(textColor(elevatedButtonText), const Color(0xff654321));
Finder outlinedButtonText = find.text('Outlined').first;
expect(textColor(outlinedButtonText), const Color(0xff654321));
Finder textButtonText = find.text('Text').first;
expect(textColor(textButtonText), const Color(0xff654321));
final Material material = tester.widget<Material>(find.descendant(
of: find.byType(FilledButton).first,
matching: find.byType(Material),
));
expect(material.color, const Color(0xff654321));
});
}
Future<void> updatePrimaryChipColor(WidgetTester tester, Color newColor, bool isLight) async {
Finder colorPageIndicator = find.descendant(of: find.byType(NavigationRail),
matching: find.byIcon(Icons.format_paint_outlined));
expect(colorPageIndicator, findsOneWidget);
await tester.tap(colorPageIndicator);
await tester.pumpAndSettle();
expect(isLight ? find.text('Light ColorScheme') : find.text('Dark ColorScheme'), findsOneWidget);
Finder primaryColorChip = isLight
? find.widgetWithText(EditableColorChip, 'primary').first
: find.widgetWithText(EditableColorChip, 'primary').last;
await tester.tap(primaryColorChip);
await tester.pump();
expect(find.byType(AlertDialog), findsOneWidget);
await tester.enterText(find.byType(TextField), newColor.value.toRadixString(16));
// Tap on the barrier.
await tester.tapAt(const Offset(10.0, 10.0));
await tester.pumpAndSettle();
expect(find.byType(AlertDialog), findsNothing);
} }

Loading…
Cancel
Save