From b0023ead5dc711bbbda4ae3c0d563d9d3e3c494a Mon Sep 17 00:00:00 2001 From: Qun Cheng Date: Mon, 11 Dec 2023 21:43:17 -0800 Subject: [PATCH] make ColorScheme configurable --- .../lib/color_palettes_screen.dart | 589 ++++++++++++++---- experimental/material_3_demo/lib/home.dart | 45 +- experimental/material_3_demo/lib/main.dart | 135 +++- experimental/material_3_demo/pubspec.yaml | 2 + .../test/color_screen_test.dart | 130 +++- 5 files changed, 754 insertions(+), 147 deletions(-) diff --git a/experimental/material_3_demo/lib/color_palettes_screen.dart b/experimental/material_3_demo/lib/color_palettes_screen.dart index 580678be7..db5b6d764 100644 --- a/experimental/material_3_demo/lib/color_palettes_screen.dart +++ b/experimental/material_3_demo/lib/color_palettes_screen.dart @@ -4,6 +4,10 @@ import 'package:flutter/gestures.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'; const Widget divider = SizedBox(height: 10); @@ -14,20 +18,19 @@ const Widget divider = SizedBox(height: 10); const double narrowScreenWidthThreshold = 400; 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 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) { return Padding( padding: const EdgeInsets.symmetric(vertical: 15), @@ -38,11 +41,13 @@ class ColorPalettesScreen extends StatelessWidget { ); } - Widget schemeView(ThemeData theme) { + Widget schemeView(Brightness brightness) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 15), child: ColorSchemeView( - colorScheme: theme.colorScheme, + handleColorRoleChange: handleColorRoleChange, + colorScheme: brightness == Brightness.light ? lightColorScheme : darkColorScheme, + brightness: brightness, ), ); } @@ -83,11 +88,11 @@ class ColorPalettesScreen extends StatelessWidget { dynamicColorNotice(), divider, schemeLabel('Light ColorScheme'), - schemeView(lightTheme), + schemeView(Brightness.light), divider, divider, schemeLabel('Dark ColorScheme'), - schemeView(darkTheme), + schemeView(Brightness.dark), ], ), ); @@ -104,7 +109,7 @@ class ColorPalettesScreen extends StatelessWidget { child: Column( children: [ schemeLabel('Light ColorScheme'), - schemeView(lightTheme), + schemeView(Brightness.light), ], ), ), @@ -112,7 +117,7 @@ class ColorPalettesScreen extends StatelessWidget { child: Column( children: [ schemeLabel('Dark ColorScheme'), - schemeView(darkTheme), + schemeView(Brightness.dark), ], ), ), @@ -128,10 +133,38 @@ class ColorPalettesScreen extends StatelessWidget { } } -class ColorSchemeView extends StatelessWidget { - const ColorSchemeView({super.key, required this.colorScheme}); +class ColorSchemeView extends StatefulWidget { + const ColorSchemeView({ + super.key, + required this.handleColorRoleChange, + required this.colorScheme, + required this.brightness, + }); + final ConfigColorSchemeCallback handleColorRoleChange; final ColorScheme colorScheme; + final Brightness brightness; + + @override + State createState() => _ColorSchemeViewState(); +} + +class _ColorSchemeViewState extends State { + 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 Widget build(BuildContext context) { @@ -139,185 +172,395 @@ class ColorSchemeView extends StatelessWidget { children: [ ColorGroup( children: [ - ColorChip( + EditableColorChip( label: 'primary', - color: colorScheme.primary, - onColor: colorScheme.onPrimary, - ), - ColorChip( + color: _colorScheme.primary, + onColor: _colorScheme.onPrimary, + updateColorScheme: (color) { + _colorScheme = _colorScheme.copyWith(primary: color); + widget.handleColorRoleChange( + widget.brightness, + primary: color, + ); + }, + ), + EditableColorChip( label: 'onPrimary', - color: colorScheme.onPrimary, - onColor: colorScheme.primary, - ), - ColorChip( + color: _colorScheme.onPrimary, + onColor: _colorScheme.primary, + updateColorScheme: (color) { + _colorScheme = _colorScheme.copyWith(onPrimary: color); + widget.handleColorRoleChange( + widget.brightness, + onPrimary: color, + ); + }, + ), + EditableColorChip( label: 'primaryContainer', - color: colorScheme.primaryContainer, - onColor: colorScheme.onPrimaryContainer, - ), - ColorChip( + color: _colorScheme.primaryContainer, + onColor: _colorScheme.onPrimaryContainer, + updateColorScheme: (color) { + _colorScheme = _colorScheme.copyWith(primaryContainer: color); + widget.handleColorRoleChange( + widget.brightness, + primaryContainer: color, + ); + }, + ), + EditableColorChip( label: 'onPrimaryContainer', - color: colorScheme.onPrimaryContainer, - onColor: colorScheme.primaryContainer, + color: _colorScheme.onPrimaryContainer, + onColor: _colorScheme.primaryContainer, + updateColorScheme: (color) { + _colorScheme = _colorScheme.copyWith(onPrimaryContainer: color); + widget.handleColorRoleChange( + widget.brightness, + onPrimaryContainer: color, + ); + }, ), ], ), divider, ColorGroup( children: [ - ColorChip( + EditableColorChip( label: 'secondary', - color: colorScheme.secondary, - onColor: colorScheme.onSecondary, - ), - ColorChip( + color: _colorScheme.secondary, + onColor: _colorScheme.onSecondary, + updateColorScheme: (color) { + _colorScheme = _colorScheme.copyWith(secondary: color); + widget.handleColorRoleChange( + widget.brightness, + secondary: color, + ); + }, + ), + EditableColorChip( label: 'onSecondary', - color: colorScheme.onSecondary, - onColor: colorScheme.secondary, - ), - ColorChip( + color: _colorScheme.onSecondary, + onColor: _colorScheme.secondary, + updateColorScheme: (color) { + _colorScheme = _colorScheme.copyWith(onSecondary: color); + widget.handleColorRoleChange( + widget.brightness, + onSecondary: color, + ); + }, + ), + EditableColorChip( label: 'secondaryContainer', - color: colorScheme.secondaryContainer, - onColor: colorScheme.onSecondaryContainer, - ), - ColorChip( + color: _colorScheme.secondaryContainer, + onColor: _colorScheme.onSecondaryContainer, + updateColorScheme: (color) { + _colorScheme = _colorScheme.copyWith(secondaryContainer: color); + widget.handleColorRoleChange( + widget.brightness, + secondaryContainer: color, + ); + }, + ), + EditableColorChip( label: 'onSecondaryContainer', - color: colorScheme.onSecondaryContainer, - onColor: colorScheme.secondaryContainer, + color: _colorScheme.onSecondaryContainer, + onColor: _colorScheme.secondaryContainer, + updateColorScheme: (color) { + _colorScheme = _colorScheme.copyWith(onSecondaryContainer: color); + widget.handleColorRoleChange( + widget.brightness, + onSecondaryContainer: color, + ); + }, ), ], ), divider, ColorGroup( children: [ - ColorChip( + EditableColorChip( label: 'tertiary', - color: colorScheme.tertiary, - onColor: colorScheme.onTertiary, - ), - ColorChip( + color: _colorScheme.tertiary, + onColor: _colorScheme.onTertiary, + updateColorScheme: (color) { + _colorScheme = _colorScheme.copyWith(tertiary: color); + widget.handleColorRoleChange( + widget.brightness, + tertiary: color, + ); + }, + ), + EditableColorChip( label: 'onTertiary', - color: colorScheme.onTertiary, - onColor: colorScheme.tertiary, - ), - ColorChip( + color: _colorScheme.onTertiary, + onColor: _colorScheme.tertiary, + updateColorScheme: (color) { + _colorScheme = _colorScheme.copyWith(onTertiary: color); + widget.handleColorRoleChange( + widget.brightness, + onTertiary: color, + ); + }, + ), + EditableColorChip( label: 'tertiaryContainer', - color: colorScheme.tertiaryContainer, - onColor: colorScheme.onTertiaryContainer, - ), - ColorChip( + color: _colorScheme.tertiaryContainer, + onColor: _colorScheme.onTertiaryContainer, + updateColorScheme: (color) { + _colorScheme = _colorScheme.copyWith(tertiaryContainer: color); + widget.handleColorRoleChange( + widget.brightness, + tertiaryContainer: color, + ); + }, + ), + EditableColorChip( label: 'onTertiaryContainer', - color: colorScheme.onTertiaryContainer, - onColor: colorScheme.tertiaryContainer, + color: _colorScheme.onTertiaryContainer, + onColor: _colorScheme.tertiaryContainer, + updateColorScheme: (color) { + _colorScheme = _colorScheme.copyWith(onTertiaryContainer: color); + widget.handleColorRoleChange( + widget.brightness, + onTertiaryContainer: color, + ); + }, ), ], ), divider, ColorGroup( children: [ - ColorChip( + EditableColorChip( label: 'error', - color: colorScheme.error, - onColor: colorScheme.onError, - ), - ColorChip( + color: _colorScheme.error, + onColor: _colorScheme.onError, + updateColorScheme: (color) { + _colorScheme = _colorScheme.copyWith(error: color); + widget.handleColorRoleChange( + widget.brightness, + error: color, + ); + }, + ), + EditableColorChip( label: 'onError', - color: colorScheme.onError, - onColor: colorScheme.error, - ), - ColorChip( + color: _colorScheme.onError, + onColor: _colorScheme.error, + updateColorScheme: (color) { + _colorScheme = _colorScheme.copyWith(onError: color); + widget.handleColorRoleChange( + widget.brightness, + onError: color, + ); + }, + ), + EditableColorChip( label: 'errorContainer', - color: colorScheme.errorContainer, - onColor: colorScheme.onErrorContainer, - ), - ColorChip( + color: _colorScheme.errorContainer, + onColor: _colorScheme.onErrorContainer, + updateColorScheme: (color) { + _colorScheme = _colorScheme.copyWith(errorContainer: color); + widget.handleColorRoleChange( + widget.brightness, + errorContainer: color, + ); + }, + ), + EditableColorChip( label: 'onErrorContainer', - color: colorScheme.onErrorContainer, - onColor: colorScheme.errorContainer, + color: _colorScheme.onErrorContainer, + onColor: _colorScheme.errorContainer, + updateColorScheme: (color) { + _colorScheme = _colorScheme.copyWith(onErrorContainer: color); + widget.handleColorRoleChange( + widget.brightness, + onErrorContainer: color, + ); + }, ), ], ), divider, ColorGroup( children: [ - ColorChip( + EditableColorChip( label: 'surface', - color: colorScheme.surface, - onColor: colorScheme.onSurface, - ), - ColorChip( + color: _colorScheme.surface, + onColor: _colorScheme.onSurface, + updateColorScheme: (color) { + _colorScheme = _colorScheme.copyWith(surface: color); + widget.handleColorRoleChange( + widget.brightness, + surface: color, + ); + }, + ), + EditableColorChip( label: 'onSurface', - color: colorScheme.onSurface, - onColor: colorScheme.surface, - ), - ColorChip( + color: _colorScheme.onSurface, + onColor: _colorScheme.surface, + updateColorScheme: (color) { + _colorScheme = _colorScheme.copyWith(onSurface: color); + widget.handleColorRoleChange( + widget.brightness, + onSurface: color, + ); + }, + ), + EditableColorChip( label: 'surfaceVariant', - color: colorScheme.surfaceVariant, - onColor: colorScheme.onSurfaceVariant, - ), - ColorChip( + color: _colorScheme.surfaceVariant, + onColor: _colorScheme.onSurfaceVariant, + updateColorScheme: (color) { + _colorScheme = _colorScheme.copyWith(surfaceVariant: color); + widget.handleColorRoleChange( + widget.brightness, + surfaceVariant: color, + ); + }, + ), + EditableColorChip( label: 'onSurfaceVariant', - color: colorScheme.onSurfaceVariant, - onColor: colorScheme.surfaceVariant, - ), - ColorChip( + color: _colorScheme.onSurfaceVariant, + onColor: _colorScheme.surfaceVariant, + updateColorScheme: (color) { + _colorScheme = _colorScheme.copyWith(onSurfaceVariant: color); + widget.handleColorRoleChange( + widget.brightness, + onSurfaceVariant: color, + ); + }, + ), + EditableColorChip( label: 'surfaceTint', - color: colorScheme.surfaceTint, + color: _colorScheme.surfaceTint, + updateColorScheme: (color) { + _colorScheme = _colorScheme.copyWith(surfaceTint: color); + widget.handleColorRoleChange( + widget.brightness, + surfaceTint: color, + ); + }, ), ], ), divider, ColorGroup( children: [ - ColorChip( + EditableColorChip( label: 'outline', - color: colorScheme.outline, - ), - ColorChip( + color: _colorScheme.outline, + updateColorScheme: (color) { + _colorScheme = _colorScheme.copyWith(outline: color); + widget.handleColorRoleChange( + widget.brightness, + outline: color, + ); + }, + ), + EditableColorChip( label: 'outlineVariant', - color: colorScheme.outlineVariant, + color: _colorScheme.outlineVariant, + updateColorScheme: (color) { + _colorScheme = _colorScheme.copyWith(outlineVariant: color); + widget.handleColorRoleChange( + widget.brightness, + outlineVariant: color, + ); + }, ), ], ), divider, ColorGroup( children: [ - ColorChip( + EditableColorChip( label: 'inverseSurface', - color: colorScheme.inverseSurface, - onColor: colorScheme.onInverseSurface, - ), - ColorChip( + color: _colorScheme.inverseSurface, + onColor: _colorScheme.onInverseSurface, + updateColorScheme: (color) { + _colorScheme = _colorScheme.copyWith(inverseSurface: color); + widget.handleColorRoleChange( + widget.brightness, + inverseSurface: color, + ); + }, + ), + EditableColorChip( label: 'onInverseSurface', - color: colorScheme.onInverseSurface, - onColor: colorScheme.inverseSurface, - ), - ColorChip( + color: _colorScheme.onInverseSurface, + onColor: _colorScheme.inverseSurface, + updateColorScheme: (color) { + _colorScheme = _colorScheme.copyWith(onInverseSurface: color); + widget.handleColorRoleChange( + widget.brightness, + onInverseSurface: color, + ); + }, + ), + EditableColorChip( label: 'inversePrimary', - color: colorScheme.inversePrimary, - onColor: colorScheme.primary, + color: _colorScheme.inversePrimary, + onColor: _colorScheme.primary, + updateColorScheme: (color) { + _colorScheme = _colorScheme.copyWith(inversePrimary: color); + widget.handleColorRoleChange( + widget.brightness, + inversePrimary: color, + ); + }, ), ], ), divider, ColorGroup( children: [ - ColorChip( + EditableColorChip( label: 'background', - color: colorScheme.background, - onColor: colorScheme.onBackground, - ), - ColorChip( + color: _colorScheme.background, + onColor: _colorScheme.onBackground, + updateColorScheme: (color) { + _colorScheme = _colorScheme.copyWith(background: color); + widget.handleColorRoleChange( + widget.brightness, + background: color, + ); + }, + ), + EditableColorChip( label: 'onBackground', - color: colorScheme.onBackground, - onColor: colorScheme.background, - ), - ColorChip( + color: _colorScheme.onBackground, + onColor: _colorScheme.background, + updateColorScheme: (color) { + _colorScheme = _colorScheme.copyWith(onBackground: color); + widget.handleColorRoleChange( + widget.brightness, + onBackground: color, + ); + }, + ), + EditableColorChip( label: 'scrim', - color: colorScheme.scrim, - ), - ColorChip( + color: _colorScheme.scrim, + updateColorScheme: (color) { + _colorScheme = _colorScheme.copyWith(scrim: color); + widget.handleColorRoleChange( + widget.brightness, + scrim: color, + ); + }, + ), + EditableColorChip( 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 createState() => _EditableColorChipState(); +} + +class _EditableColorChipState extends State { + TextEditingController textController = TextEditingController(); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () { + showDialog( + 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 { const ColorGroup({super.key, required this.children}); @@ -336,6 +658,7 @@ class ColorGroup extends StatelessWidget { Widget build(BuildContext context) { return RepaintBoundary( child: Card( + elevation: 0.0, clipBehavior: Clip.antiAlias, child: Column( children: children, @@ -378,9 +701,23 @@ class ColorChip extends StatelessWidget { child: Row( children: [ 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')); +} diff --git a/experimental/material_3_demo/lib/home.dart b/experimental/material_3_demo/lib/home.dart index f31532126..ff28f1185 100644 --- a/experimental/material_3_demo/lib/home.dart +++ b/experimental/material_3_demo/lib/home.dart @@ -10,14 +10,50 @@ import 'constants.dart'; import 'elevation_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 { const Home({ super.key, required this.useLightMode, required this.useMaterial3, required this.colorSelected, + required this.lightColors, + required this.darkColors, required this.handleBrightnessChange, required this.handleMaterialVersionChange, + required this.handleColorRoleChange, required this.handleColorSelect, required this.handleImageSelect, required this.colorSelectionMethod, @@ -27,6 +63,8 @@ class Home extends StatefulWidget { final bool useLightMode; final bool useMaterial3; final ColorSeed colorSelected; + final ColorScheme lightColors; + final ColorScheme darkColors; final ColorImageProvider imageSelected; final ColorSelectionMethod colorSelectionMethod; @@ -34,6 +72,7 @@ class Home extends StatefulWidget { final void Function() handleMaterialVersionChange; final void Function(int value) handleColorSelect; final void Function(int value) handleImageSelect; + final ConfigColorSchemeCallback handleColorRoleChange; @override State createState() => _HomeState(); @@ -124,7 +163,11 @@ class _HomeState extends State with SingleTickerProviderStateMixin { ), ); case ScreenSelected.color: - return const ColorPalettesScreen(); + return ColorPalettesScreen( + handleColorRoleChange: widget.handleColorRoleChange, + lightColorScheme: widget.lightColors, + darkColorScheme: widget.darkColors, + ); case ScreenSelected.typography: return const TypographyScreen(); case ScreenSelected.elevation: diff --git a/experimental/material_3_demo/lib/main.dart b/experimental/material_3_demo/lib/main.dart index 5a0ec563b..177bdf5ea 100644 --- a/experimental/material_3_demo/lib/main.dart +++ b/experimental/material_3_demo/lib/main.dart @@ -27,6 +27,8 @@ class _AppState extends State { ColorImageProvider imageSelected = ColorImageProvider.leaves; ColorScheme? imageColorScheme = const ColorScheme.light(); ColorSelectionMethod colorSelectionMethod = ColorSelectionMethod.colorSeed; + ColorScheme? lightColorScheme; + ColorScheme? darkColorScheme; bool get useLightMode { switch (themeMode) { @@ -53,6 +55,7 @@ class _AppState extends State { } void handleColorSelect(int value) { + clearColorScheme(); setState(() { colorSelectionMethod = ColorSelectionMethod.colorSeed; colorSelected = ColorSeed.values[value]; @@ -60,6 +63,7 @@ class _AppState extends State { } void handleImageSelect(int value) { + clearColorScheme(); final String url = ColorImageProvider.values[value].url; ColorScheme.fromImageProvider(provider: NetworkImage(url)) .then((newScheme) { @@ -71,38 +75,135 @@ class _AppState extends State { }); } + 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 Widget build(BuildContext context) { + ThemeData lightTheme = ThemeData( + colorSchemeSeed: colorSelectionMethod == ColorSelectionMethod.colorSeed + ? colorSelected.color + : null, + colorScheme: colorSelectionMethod == ColorSelectionMethod.image + ? imageColorScheme + : null, + useMaterial3: useMaterial3, + brightness: Brightness.light, + ).copyWith( + colorScheme: lightColorScheme, + ); + + ThemeData darkTheme = ThemeData( + colorSchemeSeed: colorSelectionMethod == ColorSelectionMethod.colorSeed + ? colorSelected.color + : imageColorScheme!.primary, + useMaterial3: useMaterial3, + brightness: Brightness.dark, + ).copyWith( + colorScheme: darkColorScheme, + ); + + lightColorScheme = lightTheme.colorScheme; + darkColorScheme = darkTheme.colorScheme; + return MaterialApp( debugShowCheckedModeBanner: false, title: 'Material 3', themeMode: themeMode, - theme: ThemeData( - colorSchemeSeed: colorSelectionMethod == ColorSelectionMethod.colorSeed - ? colorSelected.color - : null, - colorScheme: colorSelectionMethod == ColorSelectionMethod.image - ? imageColorScheme - : null, - useMaterial3: useMaterial3, - brightness: Brightness.light, - ), - darkTheme: ThemeData( - colorSchemeSeed: colorSelectionMethod == ColorSelectionMethod.colorSeed - ? colorSelected.color - : imageColorScheme!.primary, - useMaterial3: useMaterial3, - brightness: Brightness.dark, - ), + theme: lightTheme, + darkTheme: darkTheme, home: Home( useLightMode: useLightMode, useMaterial3: useMaterial3, colorSelected: colorSelected, + lightColors: lightTheme.colorScheme, + darkColors: darkTheme.colorScheme, imageSelected: imageSelected, handleBrightnessChange: handleBrightnessChange, handleMaterialVersionChange: handleMaterialVersionChange, handleColorSelect: handleColorSelect, handleImageSelect: handleImageSelect, + handleColorRoleChange: handleColorRoleChange, colorSelectionMethod: colorSelectionMethod, ), ); diff --git a/experimental/material_3_demo/pubspec.yaml b/experimental/material_3_demo/pubspec.yaml index 98ee2daa5..344bbfbad 100644 --- a/experimental/material_3_demo/pubspec.yaml +++ b/experimental/material_3_demo/pubspec.yaml @@ -16,6 +16,8 @@ dependencies: cupertino_icons: ^1.0.2 url_launcher: ^6.1.8 + google_fonts: ^6.1.0 + flutter_colorpicker: ^1.0.3 dev_dependencies: analysis_defaults: diff --git a/experimental/material_3_demo/test/color_screen_test.dart b/experimental/material_3_demo/test/color_screen_test.dart index f9f6453fa..4d624937c 100644 --- a/experimental/material_3_demo/test/color_screen_test.dart +++ b/experimental/material_3_demo/test/color_screen_test.dart @@ -4,6 +4,7 @@ // ignore_for_file: avoid_types_on_closure_parameters import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:material_3_demo/color_palettes_screen.dart'; import 'package:material_3_demo/main.dart'; @@ -65,11 +66,134 @@ void main() { }); testWidgets('Color screen shows correct content', (tester) async { - await tester.pumpWidget(const MaterialApp( - home: Scaffold(body: Row(children: [ColorPalettesScreen()])), - )); + 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('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(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(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(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(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(find.descendant( + of: find.byType(FilledButton).first, + matching: find.byType(Material), + )); + expect(material.color, const Color(0xff654321)); + }); +} + +Future 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); }