From a490a6a11d0242e7debee0e8fcf79ccd3cf770aa Mon Sep 17 00:00:00 2001 From: Qun Cheng <36861262+QuncCccccc@users.noreply.github.com> Date: Wed, 31 Aug 2022 13:38:32 -0700 Subject: [PATCH] Migrated `Checkbox` to Material 3 Colors (#110537) --- dev/tools/gen_defaults/bin/gen_defaults.dart | 2 + .../gen_defaults/lib/checkbox_template.dart | 119 +++++++ .../flutter/lib/src/material/checkbox.dart | 232 +++++++++++-- .../flutter/test/material/checkbox_test.dart | 322 +++++++++++++----- 4 files changed, 550 insertions(+), 125 deletions(-) create mode 100644 dev/tools/gen_defaults/lib/checkbox_template.dart diff --git a/dev/tools/gen_defaults/bin/gen_defaults.dart b/dev/tools/gen_defaults/bin/gen_defaults.dart index 4e3d669b54..7186877068 100644 --- a/dev/tools/gen_defaults/bin/gen_defaults.dart +++ b/dev/tools/gen_defaults/bin/gen_defaults.dart @@ -20,6 +20,7 @@ import 'dart:io'; import 'package:gen_defaults/app_bar_template.dart'; import 'package:gen_defaults/button_template.dart'; import 'package:gen_defaults/card_template.dart'; +import 'package:gen_defaults/checkbox_template.dart'; import 'package:gen_defaults/chip_action_template.dart'; import 'package:gen_defaults/chip_filter_template.dart'; import 'package:gen_defaults/chip_input_template.dart'; @@ -108,6 +109,7 @@ Future main(List args) async { ButtonTemplate('md.comp.outlined-button', 'OutlinedButton', '$materialLib/outlined_button.dart', tokens).updateFile(); ButtonTemplate('md.comp.text-button', 'TextButton', '$materialLib/text_button.dart', tokens).updateFile(); CardTemplate('Card', '$materialLib/card.dart', tokens).updateFile(); + CheckboxTemplate('Checkbox', '$materialLib/checkbox.dart', tokens).updateFile(); ChipActionTemplate('ActionChip', '$materialLib/chip_action.dart', tokens).updateFile(); ChipFilterTemplate('FilterChip', '$materialLib/chip_filter.dart', tokens).updateFile(); ChipFilterTemplate('FilterChip', '$materialLib/chip_choice.dart', tokens).updateFile(); diff --git a/dev/tools/gen_defaults/lib/checkbox_template.dart b/dev/tools/gen_defaults/lib/checkbox_template.dart new file mode 100644 index 0000000000..2bfeaac996 --- /dev/null +++ b/dev/tools/gen_defaults/lib/checkbox_template.dart @@ -0,0 +1,119 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'template.dart'; + +class CheckboxTemplate extends TokenTemplate { + const CheckboxTemplate(super.blockName, super.fileName, super.tokens, { + super.colorSchemePrefix = '_colors.', + }); + + @override + String generate() => ''' +class _${blockName}DefaultsM3 extends CheckboxThemeData { + _${blockName}DefaultsM3(BuildContext context) + : _theme = Theme.of(context), + _colors = Theme.of(context).colorScheme; + + final ThemeData _theme; + final ColorScheme _colors; + + @override + MaterialStateProperty get fillColor { + return MaterialStateProperty.resolveWith((Set states) { + if (states.contains(MaterialState.disabled)) { + if (states.contains(MaterialState.selected)) { + return ${componentColor('md.comp.checkbox.selected.disabled.container')}; + } + return ${componentColor('md.comp.checkbox.unselected.disabled.outline')}.withOpacity(${opacity('md.comp.checkbox.unselected.disabled.container.opacity')}); + } + if (states.contains(MaterialState.selected)) { + if (states.contains(MaterialState.pressed)) { + return ${componentColor('md.comp.checkbox.selected.pressed.container')}; + } + if (states.contains(MaterialState.hovered)) { + return ${componentColor('md.comp.checkbox.selected.hover.container')}; + } + if (states.contains(MaterialState.focused)) { + return ${componentColor('md.comp.checkbox.selected.focus.container')}; + } + return ${componentColor('md.comp.checkbox.selected.container')}; + } + if (states.contains(MaterialState.pressed)) { + return ${componentColor('md.comp.checkbox.unselected.pressed.outline')}; + } + if (states.contains(MaterialState.hovered)) { + return ${componentColor('md.comp.checkbox.unselected.hover.outline')}; + } + if (states.contains(MaterialState.focused)) { + return ${componentColor('md.comp.checkbox.unselected.focus.outline')}; + } + return ${componentColor('md.comp.checkbox.unselected.outline')}; + }); + } + + @override + MaterialStateProperty get checkColor { + return MaterialStateProperty.resolveWith((Set states) { + if (states.contains(MaterialState.disabled)) { + if (states.contains(MaterialState.selected)) { + return ${componentColor('md.comp.checkbox.selected.disabled.icon')}; + } + return Colors.transparent; // No icons available when the checkbox is unselected. + } + if (states.contains(MaterialState.selected)) { + if (states.contains(MaterialState.pressed)) { + return ${componentColor('md.comp.checkbox.selected.pressed.icon')}; + } + if (states.contains(MaterialState.hovered)) { + return ${componentColor('md.comp.checkbox.selected.hover.icon')}; + } + if (states.contains(MaterialState.focused)) { + return ${componentColor('md.comp.checkbox.selected.focus.icon')}; + } + return ${componentColor('md.comp.checkbox.selected.icon')}; + } + return Colors.transparent; // No icons available when the checkbox is unselected. + }); + } + + @override + MaterialStateProperty get overlayColor { + return MaterialStateProperty.resolveWith((Set states) { + if (states.contains(MaterialState.selected)) { + if (states.contains(MaterialState.pressed)) { + return ${componentColor('md.comp.checkbox.selected.pressed.state-layer')}; + } + if (states.contains(MaterialState.hovered)) { + return ${componentColor('md.comp.checkbox.selected.hover.state-layer')}; + } + if (states.contains(MaterialState.focused)) { + return ${componentColor('md.comp.checkbox.selected.focus.state-layer')}; + } + return Colors.transparent; + } + if (states.contains(MaterialState.pressed)) { + return ${componentColor('md.comp.checkbox.unselected.pressed.state-layer')}; + } + if (states.contains(MaterialState.hovered)) { + return ${componentColor('md.comp.checkbox.unselected.hover.state-layer')}; + } + if (states.contains(MaterialState.focused)) { + return ${componentColor('md.comp.checkbox.unselected.focus.state-layer')}; + } + return Colors.transparent; + }); + } + + @override + double get splashRadius => ${tokens['md.comp.checkbox.state-layer.size']} / 2; + + @override + MaterialTapTargetSize get materialTapTargetSize => _theme.materialTapTargetSize; + + @override + VisualDensity get visualDensity => _theme.visualDensity; +} +'''; +} diff --git a/packages/flutter/lib/src/material/checkbox.dart b/packages/flutter/lib/src/material/checkbox.dart index bfb7aae9ca..0beb12708d 100644 --- a/packages/flutter/lib/src/material/checkbox.dart +++ b/packages/flutter/lib/src/material/checkbox.dart @@ -5,6 +5,8 @@ import 'package:flutter/widgets.dart'; import 'checkbox_theme.dart'; +import 'color_scheme.dart'; +import 'colors.dart'; import 'constants.dart'; import 'debug.dart'; import 'material_state.dart'; @@ -383,19 +385,6 @@ class _CheckboxState extends State with TickerProviderStateMixin, Togg }); } - MaterialStateProperty get _defaultFillColor { - final ThemeData themeData = Theme.of(context); - return MaterialStateProperty.resolveWith((Set states) { - if (states.contains(MaterialState.disabled)) { - return themeData.disabledColor; - } - if (states.contains(MaterialState.selected)) { - return themeData.colorScheme.secondary; - } - return themeData.unselectedWidgetColor; - }); - } - BorderSide? _resolveSide(BorderSide? side) { if (side is MaterialStateBorderSide) { return MaterialStateProperty.resolveAs(side, states); @@ -409,14 +398,16 @@ class _CheckboxState extends State with TickerProviderStateMixin, Togg @override Widget build(BuildContext context) { assert(debugCheckHasMaterial(context)); - final ThemeData themeData = Theme.of(context); final CheckboxThemeData checkboxTheme = CheckboxTheme.of(context); + final CheckboxThemeData defaults = Theme.of(context).useMaterial3 + ? _CheckboxDefaultsM3(context) + : _CheckboxDefaultsM2(context); final MaterialTapTargetSize effectiveMaterialTapTargetSize = widget.materialTapTargetSize ?? checkboxTheme.materialTapTargetSize - ?? themeData.materialTapTargetSize; + ?? defaults.materialTapTargetSize!; final VisualDensity effectiveVisualDensity = widget.visualDensity ?? checkboxTheme.visualDensity - ?? themeData.visualDensity; + ?? defaults.visualDensity!; Size size; switch (effectiveMaterialTapTargetSize) { case MaterialTapTargetSize.padded: @@ -438,40 +429,48 @@ class _CheckboxState extends State with TickerProviderStateMixin, Togg // so that they can be lerped between. final Set activeStates = states..add(MaterialState.selected); final Set inactiveStates = states..remove(MaterialState.selected); - final Color effectiveActiveColor = widget.fillColor?.resolve(activeStates) + final Color? activeColor = widget.fillColor?.resolve(activeStates) ?? _widgetFillColor.resolve(activeStates) - ?? checkboxTheme.fillColor?.resolve(activeStates) - ?? _defaultFillColor.resolve(activeStates); - final Color effectiveInactiveColor = widget.fillColor?.resolve(inactiveStates) + ?? checkboxTheme.fillColor?.resolve(activeStates); + final Color effectiveActiveColor = activeColor + ?? defaults.fillColor!.resolve(activeStates)!; + final Color? inactiveColor = widget.fillColor?.resolve(inactiveStates) ?? _widgetFillColor.resolve(inactiveStates) - ?? checkboxTheme.fillColor?.resolve(inactiveStates) - ?? _defaultFillColor.resolve(inactiveStates); + ?? checkboxTheme.fillColor?.resolve(inactiveStates); + final Color effectiveInactiveColor = inactiveColor + ?? defaults.fillColor!.resolve(inactiveStates)!; final Set focusedStates = states..add(MaterialState.focused); final Color effectiveFocusOverlayColor = widget.overlayColor?.resolve(focusedStates) ?? widget.focusColor ?? checkboxTheme.overlayColor?.resolve(focusedStates) - ?? themeData.focusColor; + ?? defaults.overlayColor!.resolve(focusedStates)!; final Set hoveredStates = states..add(MaterialState.hovered); final Color effectiveHoverOverlayColor = widget.overlayColor?.resolve(hoveredStates) - ?? widget.hoverColor - ?? checkboxTheme.overlayColor?.resolve(hoveredStates) - ?? themeData.hoverColor; + ?? widget.hoverColor + ?? checkboxTheme.overlayColor?.resolve(hoveredStates) + ?? defaults.overlayColor!.resolve(hoveredStates)!; final Set activePressedStates = activeStates..add(MaterialState.pressed); final Color effectiveActivePressedOverlayColor = widget.overlayColor?.resolve(activePressedStates) - ?? checkboxTheme.overlayColor?.resolve(activePressedStates) - ?? effectiveActiveColor.withAlpha(kRadialReactionAlpha); + ?? checkboxTheme.overlayColor?.resolve(activePressedStates) + ?? activeColor?.withAlpha(kRadialReactionAlpha) + ?? defaults.overlayColor!.resolve(activePressedStates)!; final Set inactivePressedStates = inactiveStates..add(MaterialState.pressed); final Color effectiveInactivePressedOverlayColor = widget.overlayColor?.resolve(inactivePressedStates) - ?? checkboxTheme.overlayColor?.resolve(inactivePressedStates) - ?? effectiveActiveColor.withAlpha(kRadialReactionAlpha); + ?? checkboxTheme.overlayColor?.resolve(inactivePressedStates) + ?? inactiveColor?.withAlpha(kRadialReactionAlpha) + ?? defaults.overlayColor!.resolve(inactivePressedStates)!; final Color effectiveCheckColor = widget.checkColor ?? checkboxTheme.checkColor?.resolve(states) - ?? const Color(0xFFFFFFFF); + ?? defaults.checkColor!.resolve(states)!; + + final double effectiveSplashRadius = widget.splashRadius + ?? checkboxTheme.splashRadius + ?? defaults.splashRadius!; return Semantics( checked: widget.value ?? false, @@ -489,7 +488,7 @@ class _CheckboxState extends State with TickerProviderStateMixin, Togg ..reactionColor = effectiveActivePressedOverlayColor ..hoverColor = effectiveHoverOverlayColor ..focusColor = effectiveFocusOverlayColor - ..splashRadius = widget.splashRadius ?? checkboxTheme.splashRadius ?? kRadialReactionRadius + ..splashRadius = effectiveSplashRadius ..downPosition = downPosition ..isFocused = states.contains(MaterialState.focused) ..isHovered = states.contains(MaterialState.hovered) @@ -683,3 +682,172 @@ class _CheckboxPainter extends ToggleablePainter { } } } + +// Hand coded defaults based on Material Design 2. +class _CheckboxDefaultsM2 extends CheckboxThemeData { + _CheckboxDefaultsM2(BuildContext context) + : _theme = Theme.of(context), + _colors = Theme.of(context).colorScheme; + + final ThemeData _theme; + final ColorScheme _colors; + + @override + MaterialStateProperty get fillColor { + return MaterialStateProperty.resolveWith((Set states) { + if (states.contains(MaterialState.disabled)) { + return _theme.disabledColor; + } + if (states.contains(MaterialState.selected)) { + return _colors.secondary; + } + return _theme.unselectedWidgetColor; + }); + } + + @override + MaterialStateProperty get checkColor { + return MaterialStateProperty.all(const Color(0xFFFFFFFF)); + } + + @override + MaterialStateProperty get overlayColor { + return MaterialStateProperty.resolveWith((Set states) { + if (states.contains(MaterialState.pressed)) { + return fillColor.resolve(states).withAlpha(kRadialReactionAlpha); + } + if (states.contains(MaterialState.hovered)) { + return _theme.hoverColor; + } + if (states.contains(MaterialState.focused)) { + return _theme.focusColor; + } + return Colors.transparent; + }); + } + + @override + double get splashRadius => kRadialReactionRadius; + + @override + MaterialTapTargetSize get materialTapTargetSize => _theme.materialTapTargetSize; + + @override + VisualDensity get visualDensity => _theme.visualDensity; +} + +// BEGIN GENERATED TOKEN PROPERTIES - Checkbox + +// Do not edit by hand. The code between the "BEGIN GENERATED" and +// "END GENERATED" comments are generated from data in the Material +// Design token database by the script: +// dev/tools/gen_defaults/bin/gen_defaults.dart. + +// Token database version: v0_101 + +class _CheckboxDefaultsM3 extends CheckboxThemeData { + _CheckboxDefaultsM3(BuildContext context) + : _theme = Theme.of(context), + _colors = Theme.of(context).colorScheme; + + final ThemeData _theme; + final ColorScheme _colors; + + @override + MaterialStateProperty get fillColor { + return MaterialStateProperty.resolveWith((Set states) { + if (states.contains(MaterialState.disabled)) { + if (states.contains(MaterialState.selected)) { + return _colors.onSurface.withOpacity(0.38); + } + return _colors.onSurface.withOpacity(0.38); + } + if (states.contains(MaterialState.selected)) { + if (states.contains(MaterialState.pressed)) { + return _colors.primary; + } + if (states.contains(MaterialState.hovered)) { + return _colors.primary; + } + if (states.contains(MaterialState.focused)) { + return _colors.primary; + } + return _colors.primary; + } + if (states.contains(MaterialState.pressed)) { + return _colors.onSurface; + } + if (states.contains(MaterialState.hovered)) { + return _colors.onSurface; + } + if (states.contains(MaterialState.focused)) { + return _colors.onSurface; + } + return _colors.onSurface; + }); + } + + @override + MaterialStateProperty get checkColor { + return MaterialStateProperty.resolveWith((Set states) { + if (states.contains(MaterialState.disabled)) { + if (states.contains(MaterialState.selected)) { + return _colors.surface; + } + return Colors.transparent; // No icons available when the checkbox is unselected. + } + if (states.contains(MaterialState.selected)) { + if (states.contains(MaterialState.pressed)) { + return _colors.onPrimary; + } + if (states.contains(MaterialState.hovered)) { + return _colors.onPrimary; + } + if (states.contains(MaterialState.focused)) { + return _colors.onPrimary; + } + return _colors.onPrimary; + } + return Colors.transparent; // No icons available when the checkbox is unselected. + }); + } + + @override + MaterialStateProperty get overlayColor { + return MaterialStateProperty.resolveWith((Set states) { + if (states.contains(MaterialState.selected)) { + if (states.contains(MaterialState.pressed)) { + return _colors.onSurface.withOpacity(0.12); + } + if (states.contains(MaterialState.hovered)) { + return _colors.primary.withOpacity(0.08); + } + if (states.contains(MaterialState.focused)) { + return _colors.primary.withOpacity(0.12); + } + return Colors.transparent; + } + if (states.contains(MaterialState.pressed)) { + return _colors.primary.withOpacity(0.12); + } + if (states.contains(MaterialState.hovered)) { + return _colors.onSurface.withOpacity(0.08); + } + if (states.contains(MaterialState.focused)) { + return _colors.onSurface.withOpacity(0.12); + } + return Colors.transparent; + }); + } + + @override + double get splashRadius => 40.0 / 2; + + @override + MaterialTapTargetSize get materialTapTargetSize => _theme.materialTapTargetSize; + + @override + VisualDensity get visualDensity => _theme.visualDensity; +} + +// END GENERATED TOKEN PROPERTIES - Checkbox diff --git a/packages/flutter/test/material/checkbox_test.dart b/packages/flutter/test/material/checkbox_test.dart index 06c163ab72..5c0120a025 100644 --- a/packages/flutter/test/material/checkbox_test.dart +++ b/packages/flutter/test/material/checkbox_test.dart @@ -14,6 +14,7 @@ import '../rendering/mock_canvas.dart'; import '../widgets/semantics_tester.dart'; void main() { + final ThemeData theme = ThemeData(); setUp(() { debugResetSemanticsIdCounter(); }); @@ -21,7 +22,7 @@ void main() { testWidgets('Checkbox size is configurable by ThemeData.materialTapTargetSize', (WidgetTester tester) async { await tester.pumpWidget( Theme( - data: ThemeData(materialTapTargetSize: MaterialTapTargetSize.padded), + data: theme.copyWith(materialTapTargetSize: MaterialTapTargetSize.padded), child: Directionality( textDirection: TextDirection.ltr, child: Material( @@ -40,7 +41,7 @@ void main() { await tester.pumpWidget( Theme( - data: ThemeData(materialTapTargetSize: MaterialTapTargetSize.shrinkWrap), + data: theme.copyWith(materialTapTargetSize: MaterialTapTargetSize.shrinkWrap), child: Directionality( textDirection: TextDirection.ltr, child: Material( @@ -61,10 +62,13 @@ void main() { testWidgets('CheckBox semantics', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); - await tester.pumpWidget(Material( - child: Checkbox( - value: false, - onChanged: (bool? b) { }, + await tester.pumpWidget(Theme( + data: theme, + child: Material( + child: Checkbox( + value: false, + onChanged: (bool? b) { }, + ), ), )); @@ -76,10 +80,13 @@ void main() { isFocusable: true, )); - await tester.pumpWidget(Material( - child: Checkbox( - value: true, - onChanged: (bool? b) { }, + await tester.pumpWidget(Theme( + data: theme, + child: Material( + child: Checkbox( + value: true, + onChanged: (bool? b) { }, + ), ), )); @@ -92,10 +99,13 @@ void main() { isFocusable: true, )); - await tester.pumpWidget(const Material( - child: Checkbox( - value: false, - onChanged: null, + await tester.pumpWidget(Theme( + data: theme, + child: const Material( + child: Checkbox( + value: false, + onChanged: null, + ), ), )); @@ -113,10 +123,13 @@ void main() { hasEnabledState: true, )); - await tester.pumpWidget(const Material( - child: Checkbox( - value: true, - onChanged: null, + await tester.pumpWidget(Theme( + data: theme, + child: const Material( + child: Checkbox( + value: true, + onChanged: null, + ), ), )); @@ -131,13 +144,16 @@ void main() { testWidgets('Can wrap CheckBox with Semantics', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); - await tester.pumpWidget(Material( - child: Semantics( - label: 'foo', - textDirection: TextDirection.ltr, - child: Checkbox( - value: false, - onChanged: (bool? b) { }, + await tester.pumpWidget(Theme( + data: theme, + child: Material( + child: Semantics( + label: 'foo', + textDirection: TextDirection.ltr, + child: Checkbox( + value: false, + onChanged: (bool? b) { }, + ), ), ), )); @@ -158,19 +174,22 @@ void main() { bool? checkBoxValue; await tester.pumpWidget( - Material( - child: StatefulBuilder( - builder: (BuildContext context, StateSetter setState) { - return Checkbox( - tristate: true, - value: checkBoxValue, - onChanged: (bool? value) { - setState(() { - checkBoxValue = value; - }); - }, - ); - }, + Theme( + data: theme, + child: Material( + child: StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return Checkbox( + tristate: true, + value: checkBoxValue, + onChanged: (bool? value) { + setState(() { + checkBoxValue = value; + }); + }, + ); + }, + ), ), ), ); @@ -201,11 +220,14 @@ void main() { testWidgets('has semantics for tristate', (WidgetTester tester) async { final SemanticsTester semantics = SemanticsTester(tester); await tester.pumpWidget( - Material( - child: Checkbox( - tristate: true, - value: null, - onChanged: (bool? newValue) { }, + Theme( + data: theme, + child: Material( + child: Checkbox( + tristate: true, + value: null, + onChanged: (bool? newValue) { }, + ), ), ), ); @@ -221,11 +243,14 @@ void main() { ), hasLength(1)); await tester.pumpWidget( - Material( - child: Checkbox( - tristate: true, - value: true, - onChanged: (bool? newValue) { }, + Theme( + data: theme, + child: Material( + child: Checkbox( + tristate: true, + value: true, + onChanged: (bool? newValue) { }, + ), ), ), ); @@ -242,11 +267,14 @@ void main() { ), hasLength(1)); await tester.pumpWidget( - Material( - child: Checkbox( - tristate: true, - value: false, - onChanged: (bool? newValue) { }, + Theme( + data: theme, + child: Material( + child: Checkbox( + tristate: true, + value: false, + onChanged: (bool? newValue) { }, + ), ), ), ); @@ -273,18 +301,21 @@ void main() { final SemanticsTester semanticsTester = SemanticsTester(tester); await tester.pumpWidget( - Material( - child: StatefulBuilder( - builder: (BuildContext context, StateSetter setState) { - return Checkbox( - value: checkboxValue, - onChanged: (bool? value) { - setState(() { - checkboxValue = value; - }); - }, - ); - }, + Theme( + data: theme, + child: Material( + child: StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return Checkbox( + value: checkboxValue, + onChanged: (bool? value) { + setState(() { + checkboxValue = value; + }); + }, + ); + }, + ), ), ), ); @@ -306,15 +337,18 @@ void main() { testWidgets('CheckBox tristate rendering, programmatic transitions', (WidgetTester tester) async { Widget buildFrame(bool? checkboxValue) { - return Material( - child: StatefulBuilder( - builder: (BuildContext context, StateSetter setState) { - return Checkbox( - tristate: true, - value: checkboxValue, - onChanged: (bool? value) { }, - ); - }, + return Theme( + data: theme, + child: Material( + child: StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return Checkbox( + tristate: true, + value: checkboxValue, + onChanged: (bool? value) { }, + ); + }, + ), ), ); } @@ -391,10 +425,14 @@ void main() { activeColor = const Color(0xFF00FF00); + ThemeData themeData = ThemeData(); + final bool material3 = themeData.useMaterial3; + final ColorScheme colorScheme = material3 + ? const ColorScheme.light().copyWith(primary: activeColor) + : const ColorScheme.light().copyWith(secondary: activeColor); + themeData = themeData.copyWith(colorScheme: colorScheme); await tester.pumpWidget(buildFrame( - themeData: ThemeData( - colorScheme: const ColorScheme.light() - .copyWith(secondary: activeColor))), + themeData: themeData), ); await tester.pumpAndSettle(); expect(getCheckboxRenderer(), paints..path(color: activeColor)); // paints's color is 0xFF00FF00 (theme) @@ -412,6 +450,7 @@ void main() { bool? value = true; Widget buildApp({bool enabled = true}) { return MaterialApp( + theme: theme, home: Material( child: Center( child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) { @@ -434,13 +473,19 @@ void main() { await tester.pumpWidget(buildApp()); await tester.pumpAndSettle(); + final bool material3 = theme.useMaterial3; expect(focusNode.hasPrimaryFocus, isTrue); expect( Material.of(tester.element(find.byType(Checkbox))), - paints - ..circle(color: Colors.orange[500]) - ..path(color: const Color(0xff2196f3)) - ..path(color: Colors.white), + material3 + ? (paints + ..circle(color: Colors.orange[500]) + ..path(color: const Color(0xff2196f3)) + ..path(color: theme.colorScheme.onPrimary)) + : (paints + ..circle(color: Colors.orange[500]) + ..path(color: const Color(0xff2196f3)) + ..path(color: Colors.white)) ); // Check the false value. @@ -453,7 +498,7 @@ void main() { paints ..circle(color: Colors.orange[500]) ..drrect( - color: const Color(0x8a000000), + color: material3 ? theme.colorScheme.onSurface : const Color(0x8a000000), outer: RRect.fromLTRBR(15.0, 15.0, 33.0, 33.0, const Radius.circular(1.0)), inner: RRect.fromLTRBR(17.0, 17.0, 31.0, 31.0, const Radius.circular(-1.0)), ), @@ -468,7 +513,7 @@ void main() { Material.of(tester.element(find.byType(Checkbox))), paints ..drrect( - color: const Color(0x61000000), + color: material3 ? theme.colorScheme.onSurface.withOpacity(0.38) : const Color(0x61000000), outer: RRect.fromLTRBR(15.0, 15.0, 33.0, 33.0, const Radius.circular(1.0)), inner: RRect.fromLTRBR(17.0, 17.0, 31.0, 31.0, const Radius.circular(-1.0)), ), @@ -480,6 +525,7 @@ void main() { const double splashRadius = 30; Widget buildApp() { return MaterialApp( + theme: theme, home: Material( child: Center( child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) { @@ -506,8 +552,10 @@ void main() { testWidgets('Checkbox can be hovered and has correct hover color', (WidgetTester tester) async { tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; bool? value = true; + final bool material3 = theme.useMaterial3; Widget buildApp({bool enabled = true}) { return MaterialApp( + theme: theme, home: Material( child: Center( child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) { @@ -531,7 +579,7 @@ void main() { Material.of(tester.element(find.byType(Checkbox))), paints ..path(color: const Color(0xff2196f3)) - ..path(color: const Color(0xffffffff),style: PaintingStyle.stroke, strokeWidth: 2.0), + ..path(color: material3 ? theme.colorScheme.onPrimary : const Color(0xffffffff), style: PaintingStyle.stroke, strokeWidth: 2.0), ); // Start hovering @@ -544,7 +592,7 @@ void main() { Material.of(tester.element(find.byType(Checkbox))), paints ..path(color: const Color(0xff2196f3)) - ..path(color: const Color(0xffffffff), style: PaintingStyle.stroke, strokeWidth: 2.0), + ..path(color: material3 ? theme.colorScheme.onPrimary : const Color(0xffffffff), style: PaintingStyle.stroke, strokeWidth: 2.0), ); // Check what happens when disabled. @@ -553,8 +601,8 @@ void main() { expect( Material.of(tester.element(find.byType(Checkbox))), paints - ..path(color: const Color(0x61000000)) - ..path(color: const Color(0xffffffff), style: PaintingStyle.stroke, strokeWidth: 2.0), + ..path(color: material3 ? theme.colorScheme.onSurface.withOpacity(0.38) : const Color(0x61000000)) + ..path(color: material3 ? theme.colorScheme.surface : const Color(0xffffffff), style: PaintingStyle.stroke, strokeWidth: 2.0), ); }); @@ -563,6 +611,7 @@ void main() { bool? value = true; Widget buildApp({bool enabled = true}) { return MaterialApp( + theme: theme, home: Material( child: Center( child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) { @@ -603,6 +652,7 @@ void main() { Future buildTest(VisualDensity visualDensity) async { return tester.pumpWidget( MaterialApp( + theme: theme, home: Material( child: Center( child: Checkbox( @@ -693,6 +743,7 @@ void main() { // Test Checkbox() constructor await tester.pumpWidget( MaterialApp( + theme: theme, home: Scaffold( body: Align( alignment: Alignment.topLeft, @@ -721,6 +772,7 @@ void main() { // Test default cursor await tester.pumpWidget( MaterialApp( + theme: theme, home: Scaffold( body: Align( alignment: Alignment.topLeft, @@ -742,8 +794,9 @@ void main() { // Test default cursor when disabled await tester.pumpWidget( - const MaterialApp( - home: Scaffold( + MaterialApp( + theme: theme, + home: const Scaffold( body: Align( alignment: Alignment.topLeft, child: Material( @@ -764,8 +817,9 @@ void main() { // Test cursor when tristate await tester.pumpWidget( - const MaterialApp( - home: Scaffold( + MaterialApp( + theme: theme, + home: const Scaffold( body: Align( alignment: Alignment.topLeft, child: Material( @@ -807,7 +861,7 @@ void main() { Widget buildFrame({required bool enabled}) { return Material( child: Theme( - data: ThemeData(), + data: theme, child: StatefulBuilder( builder: (BuildContext context, StateSetter setState) { return Checkbox( @@ -856,7 +910,7 @@ void main() { Widget buildFrame() { return Material( child: Theme( - data: ThemeData(), + data: theme, child: StatefulBuilder( builder: (BuildContext context, StateSetter setState) { return Checkbox( @@ -901,6 +955,7 @@ void main() { Widget buildApp() { return MaterialApp( + theme: theme, home: Material( child: Center( child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) { @@ -932,6 +987,82 @@ void main() { ); }); + testWidgets('Checkbox default overlay color in active/pressed/focused/hovered states', (WidgetTester tester) async { + final FocusNode focusNode = FocusNode(debugLabel: 'Checkbox'); + tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; + + final ColorScheme colors = theme.colorScheme; + final bool material3 = theme.useMaterial3; + Widget buildCheckbox({bool active = false, bool focused = false}) { + return MaterialApp( + theme: theme, + home: Scaffold( + body: Checkbox( + focusNode: focusNode, + autofocus: focused, + value: active, + onChanged: (_) { }, + ), + ), + ); + } + + await tester.pumpWidget(buildCheckbox()); + await tester.startGesture(tester.getCenter(find.byType(Checkbox))); + await tester.pumpAndSettle(); + + expect( + Material.of(tester.element(find.byType(Checkbox))), + material3 + ? (paints..circle(color: colors.primary.withOpacity(0.12))) + : (paints + ..circle(color: theme.unselectedWidgetColor.withAlpha(kRadialReactionAlpha),) + ), + reason: 'Default inactive pressed Checkbox should have overlay color from default fillColor', + ); + + await tester.pumpWidget(buildCheckbox(active: true)); + await tester.startGesture(tester.getCenter(find.byType(Checkbox))); + await tester.pumpAndSettle(); + + expect( + Material.of(tester.element(find.byType(Checkbox))), + material3 + ? (paints..circle(color: colors.onSurface.withOpacity(0.12))) + : (paints + ..circle(color: colors.secondary.withAlpha(kRadialReactionAlpha),) + ), + reason: 'Default active pressed Checkbox should have overlay color from default fillColor', + ); + + await tester.pumpWidget(buildCheckbox(focused: true)); + await tester.pumpAndSettle(); + + expect(focusNode.hasPrimaryFocus, isTrue); + expect( + Material.of(tester.element(find.byType(Checkbox))), + material3 + ? (paints..circle(color: colors.onSurface.withOpacity(0.12))) + : (paints..circle(color: theme.focusColor)), + reason: 'Focused Checkbox should use default focused overlay color', + ); + + await tester.pumpWidget(Container()); // reset test + await tester.pumpWidget(buildCheckbox()); + final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); + await gesture.addPointer(); + await gesture.moveTo(tester.getCenter(find.byType(Checkbox))); + await tester.pumpAndSettle(); + + expect( + Material.of(tester.element(find.byType(Checkbox))), + material3 + ? (paints..circle(color: colors.onSurface.withOpacity(0.08))) + : (paints..circle(color: theme.hoverColor)), + reason: 'Hovered Checkbox should use default hovered overlay color', + ); + }); + testWidgets('Checkbox overlay color resolves in active/pressed/focused/hovered states', (WidgetTester tester) async { final FocusNode focusNode = FocusNode(debugLabel: 'Checkbox'); tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; @@ -963,6 +1094,7 @@ void main() { Widget buildCheckbox({bool active = false, bool focused = false, bool useOverlay = true}) { return MaterialApp( + theme: theme, home: Scaffold( body: Checkbox( focusNode: focusNode, @@ -1085,6 +1217,7 @@ void main() { Widget buildTristateCheckbox() { return MaterialApp( + theme: theme, home: Scaffold( body: StatefulBuilder( builder: (BuildContext context, StateSetter setState) { @@ -1175,6 +1308,7 @@ void main() { testWidgets('Do not crash when widget disappears while pointer is down', (WidgetTester tester) async { Widget buildCheckbox(bool show) { return MaterialApp( + theme: theme, home: Material( child: Center( child: show ? Checkbox(value: true, onChanged: (_) { }) : Container(), @@ -1205,6 +1339,7 @@ void main() { Widget buildApp({ bool? value, bool enabled = true }) { return MaterialApp( + theme: theme, home: Material( child: Center( child: Checkbox( @@ -1268,6 +1403,7 @@ void main() { Widget buildApp({ bool? value, bool enabled = true }) { return MaterialApp( + theme: theme, home: Material( child: Center( child: Checkbox(