-
Notifications
You must be signed in to change notification settings - Fork 29.7k
CupertinoTheme & CupertinoTextTheme dark mode updates #41859
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
Conversation
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.
Breaking change. brightness is no longer used anywhere and would love to get rid of it in the near future.
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.
per our style guide: Please add a comment to all // ignore: explaining why the ignore is there and when (if at all) it can be removed.
2fa44f0 to
8fe458a
Compare
8fe458a to
cf4cd0f
Compare
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
| /// The Cupertino Library provides built-in support for [CupertinoDynamicColor], | ||
| /// there are a few scenarios where you can skip writing the [CupertinoDynamicColor.resolve] | ||
| /// boilerplate. |
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 feel like this sentence needs a conjunction or to be split into 2 sentences, but this is obviously a nit.
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.
rephrased the paragraph a bit.
| /// When a Cupertino widget is provided with a `CupertinoDynamicColor`, either | ||
| /// directly in its constructor, or from an [InheritedWidget] it depends on (for example, | ||
| /// [DefaultTextStyle]), the widget will automatically resolve the color using | ||
| /// [CupertinoDynamicColor.resolve] against their own [BuildContext], on a best-effort |
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.
"against its own"
| /// return Container(color: resolvedColor); | ||
| /// }) | ||
| /// ``` | ||
| /// {@end-tool} |
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.
Tell me if my understanding is correct. Natively, systemBlue changes slightly depending on dark/light mode (and maybe other things like high contrast mode). In Flutter CupertinoColors.systemBlue will always return some static default systemBlue color. Using CupertinoDynamicColor.resolve returns the right systemBlue color for the current dark/light mode etc. Also, if passing CupertinoColors.systemBlue to a Cupertino widget, it will automatically resolve the color to the right systemBlue color 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 exactly. As of now all Cupertino widgets (with 1 or 2 exceptions I think, some PRs are blocked or still waiting to be merged) resolve the colors given to them in their build or createRenderObject/updateRenderObject methods. But if a Cupertino widget takes a Border things get complicated because it can be a subclass of Border. I feel this is going to be a big gotcha but not sure how I can better document it.
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 happens if you do pass an unresolved CupertinoDynamicColor to Border, does it use the light mode color as default? Would it be possible to log a warning in that case, or does it happen too commonly in other situations?
Now that I understand the problem the examples above are pretty clear, though. Maybe bring this up to the team and see if anyone has any ideas if you're still worried about it.
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.
Good idea! It sounds wrong to me to use a CupertinoDynamicColor that has never been resolved directly. Maybe putting some asserts in the CupertinoDynamicColor.value override to ensure no one does something like Paint()..color = CupertinoColors.systemBlue will be a viable approach. I'll try this out in a different PR to see if that breaks anything.
| // This does not change the shadow color, etc. | ||
| Paint foreground, background; | ||
| if (style?.foreground != null) | ||
| foreground..color = CupertinoDynamicColor.resolve(style?.foreground?.color, context, nullOk: nullOk); |
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 might as well be a single dot, right? Or am I missing something... Same goes for 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.
I just realized this is not working because Paint is not storing the color as is:
set color(Color value) {
assert(value != null);
final int encoded = value.value ^ _kColorDefault;
_data.setInt32(_kColorOffset, encoded, _kFakeHostEndian);
}I'll get rid of foreground and background.
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 nice, sounds good!
50fa2f2 to
4b95b14
Compare
goderbauer
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.
Couple of nits, the approach seems fine.
| // desired effect. | ||
| /// [CupertinoDynamicColor.color] will be the default effective color. | ||
| /// | ||
| /// Sometimes manually resolving a [CupertinoDynamicColor] is not necessary, because |
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 sentence leaves me wondering: How do I as a developer know when to manually resolve and when not?
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 next few sections are intended to answer that question. Should I replace the . at the end with : ?
| /// | ||
| /// ### Using a [CupertinoDynamicColor] in a Cupertino widget | ||
| /// | ||
| /// When a Cupertino widget is provided with a `CupertinoDynamicColor`, either |
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 should be enclosed in [] and not in`?
| /// When a Cupertino widget is provided with a `CupertinoDynamicColor`, either | ||
| /// directly in its constructor, or from an [InheritedWidget] it depends on (for example, | ||
| /// [DefaultTextStyle]), the widget will automatically resolve the color using | ||
| /// [CupertinoDynamicColor.resolve] against its own [BuildContext], on a best-effort |
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 best-effort? What are the failure cases?
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 we're given an instance of a class that may contain CupertinoDynamicColors (e.g. Border or TextStyle), we can't reliably determine what needs to be resolved in that class since we don't know beforehand what its runtime type is.
| /// }) | ||
| /// ``` | ||
| /// | ||
| /// ```dart |
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.
Does having two ```dart sections in the same {@tool ...} render correctly in dartdocs?
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 ended up looking like this:
Builder(builder: (BuildContext context) {
final Color resolvedColor = CupertinoDynamicColor.resolve(CupertinoColors.systemBlue, context);
return CupertinoNavigationBar(
// CupertinoNavigationBar does not know how to resolve colors used in
// a Border class.
border: Border(bottom: BorderSide(color: resolvedColor)),
);
})
// ...
Builder(builder: (BuildContext context) {
final Color resolvedColor = CupertinoDynamicColor.resolve(CupertinoColors.systemBlue, context);
// Container is not a Cupertino widget.
return Container(color: resolvedColor);
})Thanks for pointing this out!
| /// * [CupertinoUserInterfaceLevel], an [InheritedWidget] that may affect color | ||
| /// resolution of a `CupertinoDynamicColor`. | ||
| /// * https://developer.apple.com/documentation/uikit/uicolor/3238042-resolvedcolor. | ||
| /// |
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.
nit: removed this blank line
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.
per our style guide: Please add a comment to all // ignore: explaining why the ignore is there and when (if at all) it can be removed.
| this._navActionTextStyle, | ||
| this._pickerTextStyle, | ||
| this._dateTimePickerTextStyle, | ||
| ) : assert((_navActionTextStyle != null && _actionTextStyle != null) || _primaryColor != 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.
Document this requirement on the public constructor?
|
|
||
| /// Returns a copy of the current [CupertinoTextThemeData] with all the colors | ||
| /// resolved against the given [BuildContext]. | ||
| CupertinoTextThemeData resolveFrom(BuildContext context, { bool nullOk = false }) { |
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 know this was already missing before you made your change, but:): It would be cool to document what the nullOk parameters does.
| /// specified overrides. | ||
| CupertinoTextThemeData copyWith({ | ||
| Color primaryColor, | ||
| @deprecated |
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.
Move the annotation to the same line as the property that is deprecated?
| final Color labelColor; | ||
| final Color inactiveGrayColor; | ||
|
|
||
| static TextStyle applyLabelColor(TextStyle original, Color color) { |
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.
Should this method be private?
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 class is private and since the implementation of it depends on the TextStyle constant defaults, it should probably never be made public so I guess its fine?
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've made a habit of making private class methods public, I think this is OK.
5a70761 to
366175a
Compare
366175a to
1ef797a
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.
Progress report. I'm still working through this PR.
| /// | ||
| /// {@tool sample} | ||
| /// The following snippet builds a [CupertinoButton] that appears white in light | ||
| /// mode, and changes automatically to black in dark mode. |
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 don't want developers to think that they must do this to support dark mode. Maybe briefly explain that CupertinoButton's color is a dynamic color by default...
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.
How does
By default a [CupertinoButton] has no background color. The following sample
code shows how to build a [CupertinoButton] that appears white in light mode,
and changes automatically to black in dark mode.
Sound?
| /// color: CupertinoColors.white, | ||
| /// darkColor: CupertinoColors.black, | ||
| /// ), | ||
| /// onPressed: () => 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.
Please reserve the => notation for one-liners whose return type is not void.
onPressed: () { },| /// [MediaQuery], etc. | ||
| /// | ||
| /// ```dart | ||
| /// Builder(builder:(BuildContext context) { |
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.
Since code fragments like this are really just about the Builder's return value, I think it would be better to leave out the Builder. I realize that there's a small (acceptable) risk that the developer will put the fragment in a context that doesn't inherit what they expect. Here and elsewhere.
| /// | ||
| /// ### Manually Resolving a [CupertinoDynamicColor] | ||
| /// | ||
| /// When used outside of a Cupertino component, or wrapped in an object opaque to |
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.
Maybe describe this situation as creating a custom Cupertino component that configures ordinary widgets with dynamic 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.
Changed to
When used to configure a non-Cupertino widget, or wrapped in an object opaque
to the receiving Cupertino component, a [CupertinoDynamicColor] may need to be
manually resolved using [CupertinoDynamicColor.resolve], before it can used
to paint. For example, to use a custom [Border] in a [CupertinoNavigationBar],
the colors used in the [Border] have to be resolved manually before being passed
to [CupertinoNavigationBar]'s constructor.
| /// that fits the given [BuildContext]. | ||
| /// | ||
| /// This method gives the ambient [IconThemeData] a chance to update itself, | ||
| /// after retrieved by [IconTheme.of], and before being returned as the final |
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.
after retrieved => after it has been retrieved
| /// * [CupertinoIconThemeData.resolve] an implementation that resolves | ||
| /// [CupertinoIconThemeData.color] before returning. | ||
| @protected | ||
| IconThemeData resolve(BuildContext context) => 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.
Why is this a breaking change? I don't think we call adding a method to a public class a breaking change (even though it could cause a name collision).
| // | ||
| // Values derived from https://developer.apple.com/design/resources/. | ||
| const TextStyle _kDefaultLightTextStyle = TextStyle( | ||
| const TextStyle _kDefaultTextStyle = TextStyle( |
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.
Could we code-generate all of these values with an iOS app?
| /// Returns a default [CupertinoThemeData] if no [CupertinoTheme] widgets | ||
| /// exist in the ancestry tree. | ||
| /// Retrieve the [CupertinoThemeData] from an ancestor [CupertinoTheme] widget, | ||
| /// or a default [CupertinoThemeData] if no [CupertinoTheme] widgets exist |
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 no [CupertinoTheme] ancestors exist. Resolve all of the colors defined ...
| ); | ||
|
|
||
| /// Applies a visual styling theme to descendant Cupertino widgets. | ||
| /// |
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 helpful if we explained here (and probably elsewhere) that the CupertinoTheme can be overriden and that the default one is provided by CupertinoApp. Along the same lines: explain that MaterialApp includes a MaterialBasedCupertinoTheme, it will be visible to Cupertino widgets created within the MaterialApp, and it can be overridden with a CupertinoTheme.
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.
CupertinoApp and Theme are briefly mentioned in the see also section. Expanded it a little bit:
See also:
- [CupertinoThemeData], specifies the theme's visual styling.
- [CupertinoApp], which will automatically add a [CupertinoTheme] based on the
value of [CupertinoApp.theme].- [Theme], a Material theme which will automatically add a [CupertinoTheme]
with a [CupertinoThemeData] derived from the Material [ThemeData].
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.
This generally looks good, most of the feedback is about docs.
| /// | ||
| /// If no ancestral [CupertinoTheme] widget with explicit brightness value could | ||
| /// be found, the method will resort to the closest ancestor [MediaQuery] widget. | ||
| /// If no [CupertinoTheme] ancestor with explicit brightness value could be |
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.
with explicit => with an explicit
| bool get _isLight => _brightness != Brightness.dark; | ||
|
|
||
| final TextStyle _textStyle; | ||
| /// Typography of general text content for Cupertino widgets. |
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 should probably describe these values as "text styles" not typography. If for no other reason than https://api.flutter.dev/flutter/material/Typography-class.html
| final Color labelColor; | ||
| final Color inactiveGrayColor; | ||
|
|
||
| static TextStyle applyLabelColor(TextStyle original, Color color) { |
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've made a habit of making private class methods public, I think this is OK.
| /// [CupertinoColors.activeOrange] when [brightness] is light or dark. | ||
| /// elements. Defaults to [CupertinoColors.activeBlue]. | ||
| /// | ||
| /// If coming from a Material [Theme] and unspecified, [primaryColor] will be |
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 help to refer to MaterialBasedCupertinoThemeData here; assuming that class has a good explanation for why it exists, how it's created, etc.
| Color get primaryColor => _primaryColor ?? _defaults.primaryColor; | ||
| final Color _primaryColor; | ||
|
|
||
| /// A color used for content that must contrast against a [primaryColor] background. |
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.
A color that must be easy to see when rendered on a [primaryColor] background.
| /// Derived from [brightness] and [primaryColor] if unspecified, including | ||
| /// [brightness] and [primaryColor] of a Material [ThemeData] if coming | ||
| /// from a Material [Theme]. | ||
| /// Derived [primaryColor] if unspecified, including [primaryColor] of a Material |
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 difficult to parse. Is the text color for all of the textTheme TextStyles the [primaryColor]?
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 all of them, just the ones that used for interactive elements (e.g., the back button on an ios navigation bar).
| return _barBackgroundColor ?? | ||
| (_isLight ? _kDefaultBarLightBackgroundColor : _kDefaultBarDarkBackgroundColor); | ||
| } | ||
| /// on the brightness of the [BuildContext] it last resolved against. |
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.
[BuildContext] doesn't have a brightness.
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.
Changed to
Defaults to a light gray in light mode, or a dark translucent gray color in
dark mode.
| /// Return a new `CupertinoThemeData` with all its colors resolved aginst the | ||
| /// given [BuildContext]. | ||
| /// | ||
| /// It will be called in [CupertinoTheme.of]. |
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 you mean that this method is called by [CupertinoTheme.of]?
| /// Create a copy of [CupertinoThemeData] with specified attributes overridden. | ||
| /// | ||
| /// Only the current instance's specified attributes are copied instead of | ||
| /// derived values. For instance, if the current [primaryColor] is implied |
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 a bit confusing. What does it mean to imply the primaryColor in the context of copyWith? This comment seems to imply that myTheme.copyWith(primaryColor: foo) will create a copy of myTheme with the textTheme's color changed to foo? Most of our copyWith methods don't change dependent values and this one doesn't seem to?
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.
Yeah sorry I should've updated the example
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
|
|
||
| /// Returns a copy of the current [CupertinoTextThemeData] with all the colors | ||
| /// resolved against the given [BuildContext]. | ||
| /// resolved against the given [BuildContext]. Throws an exception if any of the |
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.
Need a blank line here. Like
/// Returns a copy of the current [CupertinoTextThemeData] with all the colors
/// resolved against the given [BuildContext].
///
/// Throws an exception...|
The flutter/goldens repo is no longer in play here, which is why the golden file tests are failing. |
|
I think this is all set now and triaged? I am deleting the ignore from the Flutter Gold dashboard. |
|
What should people use if they were using |
They can keep using whatever value they're using. |
Description
CupertinoTheme&CupertinoTextThemedark modeIconThemeData.resolve&CupertinoIconThemeDataisSameColorAsmatcherRelated Issues
#35541
Tests
I added the following tests:
updated goldens: flutter/goldens#49
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?