From a7df23f2efb704975b0767dd9cbca906aab40abc Mon Sep 17 00:00:00 2001 From: James Williams <66931+jwill@users.noreply.github.com> Date: Mon, 23 Sep 2024 11:44:03 -0700 Subject: [PATCH] Improve color display in M3 Demo (#2438) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Added the color scheme schematic from Material Theme Builder aligning how colors are displayed here and in documentation across Android and Figma. - On desktop, individual colors can be copied to the clipboard - View adapts from the existing single column view to the new schematic at 500dp width. ### Before: Screenshot 2024-09-12 at 2 40 15 PM ### After: ![mobile](https://github.com/user-attachments/assets/43a7bfbd-6217-4d3f-a1d6-902c99260905) ![Screenshot_20240912_144150](https://github.com/user-attachments/assets/48df7ad4-44a1-4802-8dac-06006dfb6bb1) ## Pre-launch Checklist - [x] I read the [Flutter Style Guide] _recently_, and have followed its advice. - [x] I signed the [CLA]. - [x] I read the [Contributors Guide]. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-devrel channel on [Discord]. [Flutter Style Guide]: https://github.com/flutter/flutter/blob/master/docs/contributing/Style-guide-for-Flutter-repo.md [CLA]: https://cla.developers.google.com/ [Discord]: https://github.com/flutter/flutter/blob/master/docs/contributing/Chat.md [Contributors Guide]: https://github.com/flutter/samples/blob/main/CONTRIBUTING.md --------- Co-authored-by: Eric Windmill --- material_3_demo/lib/color_box.dart | 93 ++++ .../lib/color_palettes_screen.dart | 45 +- material_3_demo/lib/scheme.dart | 417 ++++++++++++++++++ material_3_demo/lib/typography_screen.dart | 2 +- material_3_demo/test/color_screen_test.dart | 3 +- tool/flutter_ci_script_beta.sh | 3 +- tool/flutter_ci_script_master.sh | 3 +- 7 files changed, 540 insertions(+), 26 deletions(-) create mode 100644 material_3_demo/lib/color_box.dart create mode 100644 material_3_demo/lib/scheme.dart diff --git a/material_3_demo/lib/color_box.dart b/material_3_demo/lib/color_box.dart new file mode 100644 index 000000000..e150d18ab --- /dev/null +++ b/material_3_demo/lib/color_box.dart @@ -0,0 +1,93 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +class ColorBox extends StatefulWidget { + const ColorBox( + {super.key, + required this.label, + required this.tone, + required this.color, + required this.onColor, + required this.height, + required this.width, + this.displayPaletteInfo = false}); + + final String label; + final String tone; + final Color color, onColor; + final double height, width; + final bool displayPaletteInfo; + + @override + State createState() => _ColorBoxState(); +} + +class _ColorBoxState extends State { + bool hovered = false; + + @override + Widget build(BuildContext context) { + final fonts = Theme.of(context).textTheme; + return MouseRegion( + onEnter: (_) { + if (mounted) setState(() => hovered = true); + }, + onExit: (_) { + if (mounted) setState(() => hovered = false); + }, + child: Container( + color: widget.color, + height: widget.height, + width: widget.width, + child: DefaultTextStyle( + style: fonts.labelSmall!.copyWith(color: widget.onColor), + child: Stack( + children: [ + Positioned( + top: 10, + left: 10, + child: Text(widget.label), + ), + Positioned( + bottom: 10, + right: 10, + child: Text(widget.displayPaletteInfo ? widget.tone : ''), + ), + if (hovered) + Positioned( + top: 0, + right: 0, + child: IconButton( + padding: EdgeInsets.zero, + color: widget.onColor, + tooltip: 'Copy hex color', + icon: const Icon(Icons.copy, size: 24), + onPressed: () async { + final messenger = ScaffoldMessenger.of(context); + // Copy color as hex to clipboard + String hex = '#'; + final c = widget.color; + // Will change from int 0-255 to double 0.0-1.0 in 3.26+ + // The properties also change from red/green/blue to r/g/b + // hex += (c.[r g b] * 255.0).round().toRadixString(16).padLeft(2, '0'); + hex += c.red.toRadixString(16).padLeft(2, '0'); + hex += c.green.toRadixString(16).padLeft(2, '0'); + hex += c.blue.toRadixString(16).padLeft(2, '0'); + final data = ClipboardData(text: hex); + await Clipboard.setData(data); + messenger.hideCurrentSnackBar(); + messenger.showSnackBar( + SnackBar( + content: Text('Copied $hex to clipboard'), + ), + ); + }, + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/material_3_demo/lib/color_palettes_screen.dart b/material_3_demo/lib/color_palettes_screen.dart index a3462c454..56aca010c 100644 --- a/material_3_demo/lib/color_palettes_screen.dart +++ b/material_3_demo/lib/color_palettes_screen.dart @@ -4,6 +4,7 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; +import 'package:material_3_demo/scheme.dart'; import 'package:url_launcher/url_launcher.dart'; const Widget divider = SizedBox(height: 10); @@ -11,7 +12,7 @@ const Widget divider = SizedBox(height: 10); // If screen content width is greater or equal to this value, the light and dark // color schemes will be displayed in a column. Otherwise, they will // be displayed in a row. -const double narrowScreenWidthThreshold = 400; +const double narrowScreenWidthThreshold = 500; class ColorPalettesScreen extends StatelessWidget { const ColorPalettesScreen({super.key}); @@ -94,32 +95,32 @@ class ColorPalettesScreen extends StatelessWidget { ), ); } else { + Color seed = Theme.of(context).colorScheme.primary; + ColorScheme lightScheme = ColorScheme.fromSeed( + seedColor: seed, brightness: Brightness.light); + ColorScheme darkScheme = ColorScheme.fromSeed( + seedColor: seed, brightness: Brightness.dark); return SingleChildScrollView( child: Padding( - padding: const EdgeInsets.only(top: 5), + padding: const EdgeInsets.all(8), child: Column( children: [ - dynamicColorNotice(), - Row( - children: [ - Expanded( - child: Column( - children: [ - schemeLabel('Light ColorScheme'), - schemeView(lightTheme), - ], - ), - ), - Expanded( - child: Column( - children: [ - schemeLabel('Dark ColorScheme'), - schemeView(darkTheme), - ], - ), - ), - ], + SchemePreview( + label: "Light ColorScheme", + scheme: lightScheme, + brightness: Brightness.light, + contrast: 1.0, + colorMatch: false, ), + const SizedBox(height: 16), + SchemePreview( + label: "Dark ColorScheme", + scheme: darkScheme, + brightness: Brightness.dark, + contrast: 1.0, + colorMatch: false, + ), + const SizedBox(height: 16), ], ), ), diff --git a/material_3_demo/lib/scheme.dart b/material_3_demo/lib/scheme.dart new file mode 100644 index 000000000..4cb30045b --- /dev/null +++ b/material_3_demo/lib/scheme.dart @@ -0,0 +1,417 @@ +import 'package:flutter/material.dart'; + +import '../color_box.dart'; + +class SchemePreview extends StatefulWidget { + const SchemePreview({ + super.key, + required this.label, + required this.scheme, + required this.brightness, + required this.colorMatch, + required this.contrast, + }); + + final String label; + final ColorScheme scheme; + final Brightness brightness; + final bool colorMatch; + final double contrast; + + @override + State createState() => _SchemePreviewState(); +} + +class _SchemePreviewState extends State { + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final fonts = theme.textTheme; + final colors = theme.colorScheme; + final dark = widget.brightness == Brightness.dark; + + final scheme = widget.scheme; + + return Theme( + data: theme.copyWith(colorScheme: scheme), + child: FittedBox( + fit: BoxFit.fitWidth, + child: Container( + width: 902, + decoration: BoxDecoration( + color: scheme.surface, + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: theme.brightness == widget.brightness + ? colors.outlineVariant + : Colors.transparent, + ), + ), + padding: const EdgeInsets.only( + top: 16, + left: 16, + right: 16, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: Text( + widget.label, + style: fonts.titleMedium!.copyWith( + color: scheme.onSurface, + fontWeight: FontWeight.bold, + ), + textAlign: TextAlign.start, + ), + ), + ], + ), + const SizedBox(height: 20), + Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Column( + mainAxisSize: MainAxisSize.min, + children: [ + ColorBox( + label: 'Primary', + tone: dark ? 'P-80' : 'P-40', + color: scheme.primary, + onColor: scheme.onPrimary, + height: 87, + width: 208, + ), + ColorBox( + label: 'On Primary', + tone: dark ? 'P-20' : 'P-100', + color: scheme.onPrimary, + onColor: scheme.primary, + height: 40, + width: 208, + ), + const SizedBox(height: 5), + ColorBox( + label: 'Primary Container', + tone: dark ? 'P-30' : 'P-90', + color: scheme.primaryContainer, + onColor: scheme.onPrimaryContainer, + height: 87, + width: 208, + ), + ColorBox( + label: 'On Primary Container', + tone: dark ? 'P-90' : 'P-10', + color: scheme.onPrimaryContainer, + onColor: scheme.primaryContainer, + height: 40, + width: 208, + ), + ], + ), + const SizedBox(width: 5), + Column( + mainAxisSize: MainAxisSize.min, + children: [ + ColorBox( + label: 'Secondary', + tone: dark ? 'S-80' : 'S-40', + color: scheme.secondary, + onColor: scheme.onSecondary, + height: 87, + width: 208, + ), + ColorBox( + label: 'On Secondary', + tone: dark ? 'S-20' : 'S-100', + color: scheme.onSecondary, + onColor: scheme.secondary, + height: 40, + width: 208, + ), + const SizedBox(height: 5), + ColorBox( + label: 'Secondary Container', + tone: dark ? 'S-30' : 'S-90', + color: scheme.secondaryContainer, + onColor: scheme.onSecondaryContainer, + height: 87, + width: 208, + ), + ColorBox( + label: 'On Secondary Container', + tone: dark ? 'S-90' : 'S-10', + color: scheme.onSecondaryContainer, + onColor: scheme.secondaryContainer, + height: 40, + width: 208, + ), + ], + ), + const SizedBox(width: 5), + Column( + mainAxisSize: MainAxisSize.min, + children: [ + ColorBox( + label: 'Tertiary', + tone: dark ? 'T-80' : 'T-40', + color: scheme.tertiary, + onColor: scheme.onTertiary, + height: 87, + width: 208, + ), + ColorBox( + label: 'On Tertiary', + tone: dark ? 'T-20' : 'T-100', + color: scheme.onTertiary, + onColor: scheme.tertiary, + height: 40, + width: 208, + ), + const SizedBox(height: 5), + ColorBox( + label: 'Tertiary Container', + tone: dark ? 'T-30' : 'T-90', + color: scheme.tertiaryContainer, + onColor: scheme.onTertiaryContainer, + height: 87, + width: 208, + ), + ColorBox( + label: 'On Tertiary Container', + tone: dark ? 'T-90' : 'T-10', + color: scheme.onTertiaryContainer, + onColor: scheme.tertiaryContainer, + height: 40, + width: 208, + ), + ], + ), + ], + ), + const SizedBox(height: 20), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + ColorBox( + label: 'Surface Dim', + tone: dark ? 'N-6' : 'N-87', + color: scheme.surfaceDim, + onColor: scheme.onSurface, + height: 105, + width: 211.45, + ), + ColorBox( + label: 'Surface', + tone: dark ? 'N-6' : 'N-98', + color: scheme.surface, + onColor: scheme.onSurface, + height: 105, + width: 211.45, + ), + ColorBox( + label: 'Surface Bright', + tone: dark ? 'N-24' : 'N-98', + color: scheme.surfaceBright, + onColor: scheme.onSurface, + height: 105, + width: 211.45, + ), + ], + ), + const SizedBox(height: 5), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + ColorBox( + label: 'Surf. Container\nLowest', + tone: dark ? 'N-4' : 'N-100', + color: scheme.surfaceContainerLowest, + onColor: scheme.onSurface, + height: 105, + width: 126.87, + ), + ColorBox( + label: 'Surf. Container\nLow', + tone: dark ? 'N-10' : 'N-96', + color: scheme.surfaceContainerLow, + onColor: scheme.onSurface, + height: 105, + width: 126.87, + ), + ColorBox( + label: 'Surf. Container', + tone: dark ? 'N-12' : 'N-94', + color: scheme.surfaceContainer, + onColor: scheme.onSurface, + height: 105, + width: 126.87, + ), + ColorBox( + label: 'Surf. Container\nHigh', + tone: dark ? 'N-17' : 'N-92', + color: scheme.surfaceContainerHigh, + onColor: scheme.onSurface, + height: 105, + width: 126.87, + ), + ColorBox( + label: 'Surf. Container\nHighest', + tone: dark ? 'N-24' : 'N-90', + color: scheme.surfaceContainerHighest, + onColor: scheme.onSurface, + height: 105, + width: 126.87, + ), + ], + ), + const SizedBox(height: 5), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + ColorBox( + label: 'On Surface', + tone: dark ? 'N-90' : 'N-10', + color: scheme.onSurface, + onColor: scheme.surface, + height: 40, + width: 158.59, + ), + ColorBox( + label: 'On Surface Var.', + tone: dark ? 'NV-90' : 'NV-30', + color: scheme.onSurfaceVariant, + onColor: scheme.surfaceContainerHighest, + height: 40, + width: 158.59, + ), + ColorBox( + label: 'Outline', + tone: dark ? 'NV-60' : 'NV-50', + color: scheme.outline, + onColor: scheme.surface, + height: 40, + width: 158.59, + ), + ColorBox( + label: 'Outline Variant', + tone: dark ? 'NV-30' : 'NV-80', + color: scheme.outlineVariant, + onColor: scheme.onSurface, + height: 40, + width: 158.59, + ), + ], + ), + ], + ), + const SizedBox(width: 20), + Column( + mainAxisSize: MainAxisSize.min, + children: [ + ColorBox( + label: 'Error', + tone: dark ? 'E-80' : 'E-40', + color: scheme.error, + onColor: scheme.onError, + height: 87, + width: 208, + ), + ColorBox( + label: 'On Error', + tone: dark ? 'E-20' : 'E-100', + color: scheme.onError, + onColor: scheme.error, + height: 40, + width: 208, + ), + const SizedBox(height: 5), + ColorBox( + label: 'Error Container', + tone: dark ? 'E-30' : 'E-90', + color: scheme.errorContainer, + onColor: scheme.onErrorContainer, + height: 87, + width: 208, + ), + ColorBox( + label: 'On Error Container', + tone: dark ? 'E-90' : 'E-10', + color: scheme.onErrorContainer, + onColor: scheme.errorContainer, + height: 40, + width: 208, + ), + const SizedBox(height: 20), + ColorBox( + label: 'Inverse Surface', + tone: dark ? 'N-90' : 'N-20', + color: scheme.inverseSurface, + onColor: scheme.onInverseSurface, + height: 120, + width: 208, + ), + ColorBox( + label: 'Inverse On Surface', + tone: dark ? 'N-20' : 'N-95', + color: scheme.onInverseSurface, + onColor: scheme.inverseSurface, + height: 40, + width: 208, + ), + const SizedBox(height: 5), + ColorBox( + label: 'Inverse Primary', + tone: dark ? 'P-40' : 'P-80', + color: scheme.inversePrimary, + onColor: scheme.onSurface, + height: 40, + width: 208, + ), + const SizedBox(height: 16), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + ColorBox( + label: 'Scrim', + tone: 'N-0', + color: scheme.scrim, + onColor: Colors.white, + height: 40, + width: 96.31, + ), + const SizedBox(width: 20), + ColorBox( + label: 'Shadow', + tone: 'N-0', + color: scheme.shadow, + onColor: Colors.white, + height: 40, + width: 96.31, + ), + ], + ), + const SizedBox(height: 8), + ], + ), + ], + ), + ], + ), + ), + ), + ); + } +} diff --git a/material_3_demo/lib/typography_screen.dart b/material_3_demo/lib/typography_screen.dart index 6950e9575..08888115b 100644 --- a/material_3_demo/lib/typography_screen.dart +++ b/material_3_demo/lib/typography_screen.dart @@ -15,7 +15,7 @@ class TypographyScreen extends StatelessWidget { return Expanded( child: ListView( children: [ - const SizedBox(height: 7), + const SizedBox(height: 8), TextStyleExample( name: 'Display Large', style: textTheme.displayLarge!), TextStyleExample( diff --git a/material_3_demo/test/color_screen_test.dart b/material_3_demo/test/color_screen_test.dart index 6282bf798..45d8e41eb 100644 --- a/material_3_demo/test/color_screen_test.dart +++ b/material_3_demo/test/color_screen_test.dart @@ -6,6 +6,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:material_3_demo/color_palettes_screen.dart'; import 'package:material_3_demo/main.dart'; +import 'package:material_3_demo/scheme.dart'; import 'component_screen_test.dart'; @@ -69,6 +70,6 @@ void main() { )); expect(find.text('Light ColorScheme'), findsOneWidget); expect(find.text('Dark ColorScheme'), findsOneWidget); - expect(find.byType(ColorGroup, skipOffstage: false), findsNWidgets(18)); + expect(find.byType(SchemePreview, skipOffstage: false), findsNWidgets(2)); }); } diff --git a/tool/flutter_ci_script_beta.sh b/tool/flutter_ci_script_beta.sh index 3ff79879f..2d15ba518 100755 --- a/tool/flutter_ci_script_beta.sh +++ b/tool/flutter_ci_script_beta.sh @@ -46,7 +46,8 @@ declare -ar PROJECT_NAMES=( "infinite_list" "ios_app_clip" "isolate_example" - "material_3_demo" +# TODO @ewindmill -- Color.red is deprecated and should be Color.r (same for green and blue) +# "material_3_demo" "navigation_and_routing" "place_tracker" "platform_channels" diff --git a/tool/flutter_ci_script_master.sh b/tool/flutter_ci_script_master.sh index ccaf11d57..d00d720e1 100755 --- a/tool/flutter_ci_script_master.sh +++ b/tool/flutter_ci_script_master.sh @@ -47,7 +47,8 @@ declare -ar PROJECT_NAMES=( "infinite_list" "ios_app_clip" "isolate_example" - "material_3_demo" +# TODO @ewindmill -- Color.red is deprecated and should be Color.r (same for green and blue) +# "material_3_demo" "navigation_and_routing" "place_tracker" "platform_channels"