Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrated Switch to Material 3 #110095

Merged
merged 25 commits into from Sep 9, 2022

Conversation

QuncCccccc
Copy link
Contributor

Part of: #91605

Updated the Switch widget with support for Material Design 3.

Screen Shot 2022-08-22 at 10 22 35 AMScreen Shot 2022-08-22 at 10 20 55 AM

In order to use the Switch with the new Material 3 defaults, turn on the useMaterial3 flag in the ThemeData:

  return MaterialApp(
    theme: ThemeData(useMaterial3: true),
    // ...
  );

Fixes: #103536

Pre-launch Checklist

  • I read the [Contributor Guide] and followed the process outlined there for submitting PRs.
  • I read the [Tree Hygiene] wiki page, which explains my responsibilities.
  • I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement].
  • I signed the [CLA].
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is [test-exempt].
  • All existing and new tests are passing.

@flutter-dashboard flutter-dashboard bot added f: material design flutter/packages/flutter/material repository. framework flutter/packages/flutter repository. See also f: labels. team Infra upgrades, team productivity, code health, technical debt. See also team: labels. labels Aug 23, 2022
@QuncCccccc QuncCccccc force-pushed the update_switch_to_material3 branch 2 times, most recently from a128d1e to 6be39fe Compare August 24, 2022 06:38
@QuncCccccc QuncCccccc marked this pull request as ready for review August 24, 2022 16:45
@QuncCccccc QuncCccccc requested review from darrenaustin and HansMuller and removed request for darrenaustin and HansMuller August 24, 2022 20:32
@mavyfaby
Copy link

Any updates?

Copy link
Contributor

@darrenaustin darrenaustin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@QuncCccccc sorry this took so long to review. There is a lot here!

Looks great. Nice work. I have a question about the defaults vs theme data below.

In a future PR we should add an example or two showing how to use the new thumb image and icons, as well as update the demo app.

/// onChanged: (_) => true,
/// thumbImage: MaterialStateProperty.resolveWith<ImageProvider>((Set<MaterialState> states) {
/// if (states.contains(MaterialState.disabled)) {
/// return MemoryImage(Uint8List.fromList(<int>[1, 2]));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For this example, perhaps just have disabledImage and normalImage variables for the images so that we don't have to complicate the example with the details of MemoryImage.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small question: do you really want thumbImage? Can't I use Icon(Icons.check) to do the check? And should there be only a single thumbImage? In the Material specs they use x for unselected, check for selected.

@@ -1078,3 +1322,288 @@ class _SwitchPainter extends ToggleablePainter {
super.dispose();
}
}

class _SwitchDefaults {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So is the reason for this class because we have a bunch of values that need to change between M2 and M3, but they aren't in the SwitchThemeData? If so, perhaps we should just add them to the ThemeData so that apps can customize them if they want. That would be consistent with the way we handle other components. Although there is a lot here, that maybe we don't want to bloat the ThemeData with.

If we don't want to make them part of the ThemeData, then it might be cleaner to just have a _SwitchConfig interface and have a hand coded _SwitchConfigM2 and the generated _SwitchConfigM3 that implement it. That will make it easier to ditch the M2 implementation when we switch over to M3 completely.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for the suggestions! Just created a separate config class for both M2 and M3. Please let me know if there're any problems:)

packages/flutter/test/material/switch_test.dart Outdated Show resolved Hide resolved
Copy link
Contributor

@HansMuller HansMuller left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't finished reviewing this yet

packages/flutter/lib/src/material/switch.dart Outdated Show resolved Hide resolved
if (states.contains(MaterialState.pressed)) {
return ${componentColor('md.comp.switch.unselected.pressed.state-layer')};
}
return null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be Colors.transparent? How do we expect overlayColor=null to be interpreted in this case?

CC @darrenaustin - Maybe this is another case where we want null to mean "no overlay" instead of "no preference".

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When the switch is not in any of the states(hovered, focused, and pressed), should we just give a null value for the state layer?

BuildContext context;
final ColorScheme _colors;

static const double iconSize = ${tokens['md.comp.switch.unselected.icon.size']};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good to see all of these configuration parameters being derived from the token db.

Copy link
Contributor

@HansMuller HansMuller left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great (good to see all of the tests). I realize that there's still feedback from Darren that needs to be resolved.

/// The optional icon on the thumb of this switch when the switch is on.
///
/// This property can be null.
final IconData? activeIcon;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are more Switch color and thumb image configuration parameters than we need, given MaterialStateProperty. In a future PR and design doc, I think we should either migrate developers to a new switch widget, one that only has MaterialStateProperty parameters, or a new version of Switch that deprecates all of the excess baggage.

@@ -441,11 +498,11 @@ class Switch extends StatelessWidget {
height: size.height,
alignment: Alignment.center,
child: CupertinoSwitch(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to my comment about Switch's icon and color properties: in the future, perhaps we can avoid delegating to a Cupertino library widget, and just configure the Material Switch widget instead.

@HansMuller
Copy link
Contributor

@bernaferrari (per #110095 (comment)) I agree that just using "thumb" instead of "thumbImage" would make the API more concise, using thumbImage has two advantages:

  • It doesn't completely define the appearance of the thumb, just an image that that's drawn on the thumb
  • It makes its relationship with the existing cloud of fooThumbImage properties obvious.

@bernaferrari
Copy link
Contributor

bernaferrari commented Sep 7, 2022

Yeah, but for me the "image" part is kind of confusing as it is not really an image when it is something else. An icon is supposedly different from an image. So I think something like thumbForeground could make more sense so people know anything can go there.

Copy link
Contributor

@darrenaustin darrenaustin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. I a have a couple of small comments and a question about thumbImage below.

packages/flutter/lib/src/material/switch.dart Outdated Show resolved Hide resolved
packages/flutter/lib/src/material/switch.dart Outdated Show resolved Hide resolved
@@ -1217,7 +1217,7 @@ class ThemeData with Diagnosticable {
/// * Typography: `typography` (see table above)
///
/// ### Components
/// * Common buttons: [ElevatedButton], [FilledButton], [OutlinedButton], [TextButton]
/// * Common buttons: [ElevatedButton], [FilledButton], [OutlinedButton], [TextButton], [IconButton]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah we missed this one. Nice catch.

Comment on lines 776 to 799
if (thumbWidget is Image) {
effectiveActiveThumbImage = (widget.thumbImage?.resolve(activeStates) as Image?)?.image;
effectiveInactiveThumbImage = (widget.thumbImage?.resolve(inactiveStates) as Image?)?.image;
}
final Widget? thumbThemeWidget = switchTheme.thumbImage?.resolve(states);
effectiveActiveThumbImage ??= _widgetThumbImage.resolve(activeStates);
effectiveInactiveThumbImage ??= _widgetThumbImage.resolve(inactiveStates);
if (thumbThemeWidget is Image) {
effectiveActiveThumbImage ??= (switchTheme.thumbImage?.resolve(activeStates) as Image?)?.image;
effectiveInactiveThumbImage ??= (switchTheme.thumbImage?.resolve(inactiveStates) as Image?)?.image;
}

Icon? effectiveActiveIcon;
Icon? effectiveInactiveIcon;
if (theme.useMaterial3) {
if (thumbWidget is Icon) {
effectiveActiveIcon = widget.thumbImage?.resolve(activeStates) as Icon?;
effectiveInactiveIcon = widget.thumbImage?.resolve(inactiveStates) as Icon?;
}
if (thumbThemeWidget is Icon) {
effectiveActiveIcon ??= switchTheme.thumbImage?.resolve(activeStates) as Icon?;
effectiveInactiveIcon ??= switchTheme.thumbImage?.resolve(inactiveStates) as Icon?;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How is thumbImage used if it is not an Icon or Image? What if it was just a label or a container with a shape and color? Will it be rendered on the thumb of the switch?

Or do we only support image and icon widgets for thumbImage? If so we would at least need to document that restriction.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, the thumbImage currently only supports Image and Icon. Investigating how we can paint a widget directly. Thanks for the suggestion!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the challenge, because it uses a BoxDecoration and pre-renders/caches it. I don't know how to improve that.
_cachedThumbPainter = _createDefaultThumbDecoration(thumbColor, thumbImage, thumbErrorListener).createBoxPainter(_handleDecorationChanged);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is tricky. But I agree that the restriction that only allows 2 types here is a little odd. To support the optional icon feature, I changed the thumbImage to the thumbIcon, and just used the type of MaterialStateProperty<Icon?>? instead of MaterialStateProperty<Widget?>?. Eventually, we will refactor this part of the code and allow a widget painting on the thumb of Switch.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perfect!

Copy link
Contributor

@HansMuller HansMuller left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

packages/flutter/lib/src/material/switch.dart Show resolved Hide resolved
Copy link
Contributor

@darrenaustin darrenaustin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

flutter-lgtm

Nice work.

@QuncCccccc QuncCccccc merged commit 75fac6a into flutter:master Sep 9, 2022
122 checks passed
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Sep 10, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/plugins that referenced this pull request Sep 10, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/plugins that referenced this pull request Sep 10, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/plugins that referenced this pull request Sep 11, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/plugins that referenced this pull request Sep 12, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/plugins that referenced this pull request Sep 12, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/plugins that referenced this pull request Sep 12, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/plugins that referenced this pull request Sep 13, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
f: material design flutter/packages/flutter/material repository. framework flutter/packages/flutter repository. See also f: labels. team Infra upgrades, team productivity, code health, technical debt. See also team: labels.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Update Switch to support Material 3
6 participants