Skip to content

Conversation

@LongCatIsLooong
Copy link
Contributor

@LongCatIsLooong LongCatIsLooong commented Aug 6, 2019

Description

  • Introduce CupertinoDynamicColor
  • Introduce CupertinoUserInterfaceLevel
  • Introduce CupertinoSystemColors

Related 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.

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I signed the CLA.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I updated/added relevant documentation (doc comments with ///).
  • All existing and new tests are passing.
  • The analyzer (flutter analyze --flutter-repo) does not report any problems on my PR.
  • I am willing to follow-up on review comments in a timely manner.

Breaking Change

Does your PR require Flutter developers to manually update their apps to accommodate your change?

  • No, this is not a breaking change.

@fluttergithubbot fluttergithubbot added f: cupertino flutter/packages/flutter/cupertino repository f: material design flutter/packages/flutter/material repository. framework flutter/packages/flutter repository. See also f: labels. labels Aug 6, 2019
@LongCatIsLooong LongCatIsLooong force-pushed the cupertino-dynamic-color branch 3 times, most recently from 69dc0ea to c702009 Compare August 12, 2019 18:35
@LongCatIsLooong LongCatIsLooong force-pushed the cupertino-dynamic-color branch from c702009 to f408d63 Compare August 12, 2019 19:36
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.

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.
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 an existing file; the header shouldn't change.

@required Color darkElevatedColor,
@required Color highContrastElevatedColor,
@required Color darkHighContrastElevatedColor,
}) : this._(
Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

oh never mind.

Copy link
Contributor Author

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(() {
Copy link
Contributor

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];
Copy link
Contributor

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
Copy link
Contributor

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.
Copy link
Contributor

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 {
Copy link
Contributor

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.

Copy link
Contributor Author

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?

Copy link
Contributor

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).

Copy link
Contributor Author

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
Copy link
Contributor

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);
Copy link
Contributor

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;
Copy link
Contributor

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.

@LongCatIsLooong LongCatIsLooong force-pushed the cupertino-dynamic-color branch 3 times, most recently from 8a03a53 to 415c8a3 Compare August 14, 2019 18:24
@LongCatIsLooong LongCatIsLooong force-pushed the cupertino-dynamic-color branch from 415c8a3 to 039edf7 Compare August 14, 2019 19:25
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

/// 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);
Copy link
Contributor

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.
Copy link
Contributor

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', () {
Copy link
Contributor

Choose a reason for hiding this comment

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

hasCode => hashCode

Copy link
Contributor

@justinmc justinmc left a 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],
],
],
);
Copy link
Contributor

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?

Copy link
Contributor

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.

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, 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.

Copy link
Contributor Author

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.

Copy link
Contributor Author

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.resolve method 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,
));
});
Copy link
Contributor

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.

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. 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
Copy link
Contributor

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?

Copy link
Contributor Author

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.
Copy link
Contributor

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?

Copy link
Contributor Author

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],
],
],
);
Copy link
Contributor

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) {
Copy link
Contributor

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)

Copy link
Contributor Author

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 {
Copy link
Contributor

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).

@LongCatIsLooong LongCatIsLooong force-pushed the cupertino-dynamic-color branch from d1117f8 to d45350e Compare August 16, 2019 03:29
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


const double _kPadding = 8.0;
const Color _kTrackColor = Color(0xFFB5B5B5);
//const Color _kTrackColor = Color(0xFFB5B5B5);
Copy link
Contributor

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;
Copy link
Contributor

Choose a reason for hiding this comment

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

missing indent

@LongCatIsLooong LongCatIsLooong merged commit a671b28 into flutter:master Aug 22, 2019
@LongCatIsLooong LongCatIsLooong deleted the cupertino-dynamic-color branch August 22, 2019 03:07
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Aug 4, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

f: cupertino flutter/packages/flutter/cupertino repository f: material design flutter/packages/flutter/material repository. framework flutter/packages/flutter repository. See also f: labels.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants