-
Notifications
You must be signed in to change notification settings - Fork 29.7k
CupertinoDynamicColor and friends #37719
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
CupertinoDynamicColor and friends #37719
Conversation
69dc0ea to
c702009
Compare
c702009 to
f408d63
Compare
HansMuller
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be OK to create cupertino_dynamic_color.dart and cupertino_system_colors.dart. Separate files help with reading code, since there's no need to worry about private dependencies.
| @@ -1,8 +1,21 @@ | |||
| // Copyright 2017 The Chromium Authors. All rights reserved. | |||
| // Copyright 2019 The Chromium Authors. All rights reserved. | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is an existing file; the header shouldn't change.
| @required Color darkElevatedColor, | ||
| @required Color highContrastElevatedColor, | ||
| @required Color darkHighContrastElevatedColor, | ||
| }) : this._( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Best to assert that all of the color parameters are non-null; if the CupertinoDynamicColor._ constructor fails the error message is likely to be confusing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I remember correctly if you're calling a constructor from the same class (i.e. not super.foo) you won't be able to add asserts or any other expressions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh never mind.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Never mind the never mind... although the static analyzer didn't give me trouble, when I tried to run flutter test it failed to compile:
src/cupertino/colors.dart:148:3: Error: Final field '_colorMap' is not initialized by this constructor.
Try to initialize the field using an initializing formal or a field initializer.
CupertinoDynamicColor({
^
lib/src/cupertino/colors.dart:321:33: Context: '_colorMap' is defined here.
final List<List<List>> _colorMap;
^^^^^^^^^
| CupertinoDynamicColor._( | ||
| Color value, | ||
| this._colorMap, | ||
| ) : assert(() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the CupertinoDyanmicColor asserts that all of its parameters are non-null, this shouldn't be needed.
| /// or a [MediaQuery] whose [MediaQueryData.platformBrightness] is [PlatformBrightness.light]. | ||
| /// - has a [MediaQuery] whose [MediaQueryData.highContrast] is `false`. | ||
| /// - has a [CupertinoUserInterfaceLevel] that indicates [CupertinoUserInterfaceLevelData.base]. | ||
| Color get color => _colorMap[1][0][0]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like seeing the 3 bits like this :-).
| /// color in the color family. | ||
| /// | ||
| /// When used as a regular color, `CupertinoDynamicColor` is equivalent to the | ||
| /// effective color, which is determined by the [BuildContext] it is last resolved |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be worth pointing out that the "effective" color is this color's [Color.value].
| return (inheritedTheme?.theme?.data ?? const CupertinoThemeData()).resolveFrom(context, nullOk: true); | ||
| } | ||
|
|
||
| /// Retrieve the [Brightness] value from an ancestor [CupertinoTheme] widget. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
from an => from the closest
| /// when the current [BuildContext] is not available (e.g., in [CupertinoApp]'s | ||
| /// constructor). | ||
| @immutable | ||
| class CupertinoSystemColorsData extends Diagnosticable { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This class should have lerp and debugFillProperties methods, see packages/flutter/lib/src/material/tooltip_theme.dart for examples.
There are some boilerplate-style tests to write for FooData classes like this. See the first 3 tests in packages/flutter/tests/material/tooltip_theme_tests.dart for examples.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What would be the use case of lerping CupertinoSystemColorsData?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure it applies in this case, but in other theme data classes they are usually a field in the overall ThemeData which needs to lerp all of its fields in its own lerp function (which is used for animating theme changes).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can't really think of a use case where lerping the entire CupertinoSystemColorsData, and on iOS light/dark change doesn't seem to animate
| assert(data != null), | ||
| super(key: key, child: child); | ||
|
|
||
| /// Creates a widget that provides a [CupertinoSystemColorsData] extracted from |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We haven't provided this sort of thing for other themes (inherited widgets) and it's apt to be confusing as defined because the child will not "see" the CupertinoSystemColors.
Prefer to leave this out now; write a utility in the test file that adds the extra context with a Builder. In the future, perhaps we'll update all themes to include such a factory.
| /// Falls back to [fromSystem] and then [fallbackValues], if such information | ||
| /// is not available in the given [BuildContext]. | ||
| static CupertinoSystemColorsData of(BuildContext context) { | ||
| final CupertinoSystemColors widget = context.inheritFromWidgetOfExactType(CupertinoSystemColors); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The optional parameter is usually called nullOk.
| /// | ||
| /// On iOS 13 and above all the colors will be fetched from the operating system. | ||
| /// On Android and lower versions of iOS this will return null. | ||
| static CupertinoSystemColorsData get fromSystem => null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lets leave it out until it's needed.
8a03a53 to
415c8a3
Compare
415c8a3 to
039edf7
Compare
HansMuller
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
| /// Falls back to [fromSystem] and then [fallbackValues], if such information | ||
| /// is not available in the given [BuildContext]. | ||
| static CupertinoSystemColorsData of(BuildContext context) { | ||
| final CupertinoSystemColors widget = context.inheritFromWidgetOfExactType(CupertinoSystemColors); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That sounds OK to me.
| /// * Settings -> Accessibility -> High contrast text (Android) | ||
| /// | ||
| /// This flag is currently only updated on iOS devices that are running iOS 13 | ||
| /// or above, and Android devices that are running Android Q and above. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we decided that, for now, we're not reporting it for Android Q because it's still an experimental Android feature.
| expect(system0, isNot(withDifferentLink)); | ||
| }); | ||
|
|
||
| test('CupertinoSystemColorsData.hasCode', () { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hasCode => hashCode
justinmc
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, just a question for my own sake.
| <Color>[highContrastColor, highContrastElevatedColor], | ||
| ], | ||
| ], | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you explain this choice of data structure for _colorMap? Is it just that you need to be able to group them and iterate them?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It does seem that this structure adds a complication without much benefit that I can see (but there is a good chance I am missing something here).
Having a 3-dimensional list doesn't document itself very well. For example is the first index based on brightness, elevation or contrast? Does a dark brightness map to a 0 or a 1 index?
Perhaps a simpler approach would be to just treat the CupertinoDynamicColor as a data structure class with fields for each of the colors. The resolve methods would just choose which of the colors are needed based on the context properties. It would lead to a large if-then-else statement to pick which of the 8 colors it resolves to, but it would be explicit. I think it would also mean you wouldn't need the _isPlatformBrightnessDependent type functions as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, also in case in the future more traits are needed to resolve a dynamic color, or some traits become non-binary, it's much easier to maintain the resolve method this way, than having a bunch of if statements, or with bit acrobatics.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
updated the implementation of the getters, it should be more readable for the getters now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think using a 3d list to represent the traits kinda mathematically makes sense, the 3 traits can be thought of as 3 orthogonal bases to form the whole trait space.
Some of the values are enums so it's worse than a few if else's, it's going to be nested switches, because the style guide discourages using == for enum matching.
There're also some negligible optimizations in it:
- the
CupertinoDynamicColor.resolvemethod is almost 0 copy because it just passes the reference of the array around. CupertinoDynamicColor.==is also faster (just realized this and made the change).
| highContrastColor: color0, | ||
| darkHighContrastColor: color0, | ||
| )); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for updating, it's a lot easier to understand now.
darrenaustin
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good. Nice work. My main concern is over the data structure used to store the colors in CupertinoDynamicColor. The nested list representation is hard to follow and document.
| /// When used as a regular color, `CupertinoDynamicColor` is equivalent to the | ||
| /// effective color (i.e. [CupertinoDynamicColor.value] will come from the effective | ||
| /// color), which is determined by the [BuildContext] it is last resolved against. If it has never | ||
| /// been resolved, typically the light, normal contrast, base elevation variant |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"typically"? what causes it to not be the light, normal contrast, base elevation variant? Or is this documented in the individual colors?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right now a CupertinoDynamicColor coming fresh out of a constructor always uses color as its effective color. Removing that "typically".
| // Color darkModeColor; | ||
|
|
||
| /// A palette of [Color] constants that describe colors commonly used when | ||
| /// matching the iOS platform aesthetics. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we want to add a note here that they should probably switch over to using the dynamic and system colors?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now is probably not the best time, since we're still using the fallback values in CupertinoSystemColors, and we're not even using those new colors ourselves? Maybe as the last step of dark mode?
| <Color>[highContrastColor, highContrastElevatedColor], | ||
| ], | ||
| ], | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It does seem that this structure adds a complication without much benefit that I can see (but there is a good chance I am missing something here).
Having a 3-dimensional list doesn't document itself very well. For example is the first index based on brightness, elevation or contrast? Does a dark brightness map to a 0 or a 1 index?
Perhaps a simpler approach would be to just treat the CupertinoDynamicColor as a data structure class with fields for each of the colors. The resolve methods would just choose which of the colors are needed based on the context properties. It would lead to a large if-then-else statement to pick which of the 8 colors it resolves to, but it would be explicit. I think it would also mean you wouldn't need the _isPlatformBrightnessDependent type functions as well.
| int interfaceElevationNumber = 0; | ||
|
|
||
| // If this CupertinoDynamicColor cares about brightness. | ||
| if (_isPlatformBrightnessDependent) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need the _isPlatformBrightnessDependent needed here? (and similar for the other dependent calls below)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's to avoid registering unnecessary dependencies. A widget that only uses a brightness independent CupertinoDynamicColor should not be rebuilt when platform brightness changes.
| /// when the current [BuildContext] is not available (e.g., in [CupertinoApp]'s | ||
| /// constructor). | ||
| @immutable | ||
| class CupertinoSystemColorsData extends Diagnosticable { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure it applies in this case, but in other theme data classes they are usually a field in the overall ThemeData which needs to lerp all of its fields in its own lerp function (which is used for animating theme changes).
d1117f8 to
d45350e
Compare
…flutter into cupertino-dynamic-color
HansMuller
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
|
|
||
| const double _kPadding = 8.0; | ||
| const Color _kTrackColor = Color(0xFFB5B5B5); | ||
| //const Color _kTrackColor = Color(0xFFB5B5B5); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like you intended to delete this
| Color _trackColor; | ||
| set trackColor(Color value) { | ||
| if (value == _trackColor) | ||
| return; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
missing indent
…flutter into cupertino-dynamic-color
…flutter into cupertino-dynamic-color
Description
CupertinoDynamicColorCupertinoUserInterfaceLevelCupertinoSystemColorsRelated Issues
#35541
Tests
I added the following tests:
packages/flutter/test/cupertino/colors_test.dart
Checklist
Before you create this PR confirm that it meets all requirements listed below by checking the relevant checkboxes (
[x]). This will ensure a smooth and quick review process.///).flutter analyze --flutter-repo) does not report any problems on my PR.Breaking Change
Does your PR require Flutter developers to manually update their apps to accommodate your change?