CupertinoDynamicColor and friends#37719
Conversation
69dc0ea to
c702009
Compare
c702009 to
f408d63
Compare
HansMuller
left a comment
There was a problem hiding this comment.
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.
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.
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.
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.
oh never mind.
There was a problem hiding this comment.
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.
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.
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.
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.
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.
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.
What would be the use case of lerping CupertinoSystemColorsData?
There was a problem hiding this comment.
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.
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.
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.
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.
Lets leave it out until it's needed.
8a03a53 to
415c8a3
Compare
415c8a3 to
039edf7
Compare
| /// 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); |
| /// * 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.
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', () { |
justinmc
left a comment
There was a problem hiding this comment.
LGTM, just a question for my own sake.
| <Color>[highContrastColor, highContrastElevatedColor], | ||
| ], | ||
| ], | ||
| ); |
There was a problem hiding this comment.
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.
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.
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.
updated the implementation of the getters, it should be more readable for the getters now.
There was a problem hiding this comment.
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.
Thanks for updating, it's a lot easier to understand now.
darrenaustin
left a comment
There was a problem hiding this comment.
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.
"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.
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.
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.
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.
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.
Why do we need the _isPlatformBrightnessDependent needed here? (and similar for the other dependent calls below)
There was a problem hiding this comment.
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.
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
|
|
||
| const double _kPadding = 8.0; | ||
| const Color _kTrackColor = Color(0xFFB5B5B5); | ||
| //const Color _kTrackColor = Color(0xFFB5B5B5); |
There was a problem hiding this comment.
Looks like you intended to delete this
| Color _trackColor; | ||
| set trackColor(Color value) { | ||
| if (value == _trackColor) | ||
| return; |
…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?