-
Notifications
You must be signed in to change notification settings - Fork 309
text: Apply "Source Sans 3" and (on Android) "Noto Color Emoji" widely across the app #439
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
I would probably write a test of |
Thanks! I see there's a lot going on here, so I'm afraid I won't be able to get to it today before my vacation. But I'll be glad to take a look in a couple of weeks when I'm back 🙂 |
60ec0d5
to
f9985e4
Compare
Oops, I didn't previously get back to this PR in my catching-up after vacation. Looking now; just rebased it onto current main. |
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! Glad to see the text styling get more centralized like this. Generally these changes all look good — various small comments below.
lib/widgets/text.dart
Outdated
/// A [FontVariation] "wght" value that's 300 above a given, clamped to [kWghtMax]. | ||
double bolderWght(double baseWght) { | ||
assert(baseWght >= kWghtMin && baseWght <= kWghtMax); | ||
return (baseWght + 300).clamp(kWghtMin, kWghtMax).toDouble(); |
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.
return (baseWght + 300).clamp(kWghtMin, kWghtMax).toDouble(); | |
return clampDouble(baseWght + 300, kWghtMin, kWghtMax); |
(clampDouble
is from flutter/foundation
; see its doc.)
lib/widgets/text.dart
Outdated
|
||
/// A [FontVariation] "wght" value that's 300 above a given, clamped to [kWghtMax]. | ||
double bolderWght(double baseWght) { | ||
assert(baseWght >= kWghtMin && baseWght <= kWghtMax); |
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: I find this sort of condition more readable when the comparisons are all <=
or <
so that everything's in order:
assert(baseWght >= kWghtMin && baseWght <= kWghtMax); | |
assert(kWghtMin <= baseWght && baseWght <= kWghtMax); |
test/widgets/text_test.dart
Outdated
testWeights('default `wght`, specific `wghtIfPlatformRequestsBold`; platform requests bold', | ||
styleBuilder: (context) => weightVariableTextStyle(context, wght: 775), |
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 test's description and code don't seem to match.
fontFamily: 'Source Sans 3', | ||
fontSize: 16, | ||
letterSpacing: 0.02 * 16, | ||
height: (18 / 16), | ||
).merge(weightVariableTextStyle(context, wght: 600, wghtIfPlatformRequestsBold: 900)); | ||
).merge(weightVariableTextStyle(context, wght: 600)); |
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.
Happy to see these repetitive elements getting centralized!
I'd actually had the thought in the last couple of weeks that I should take a look at trying to deduplicate some of the recurring code around our text styles. This PR looks like great progress already in that direction.
lib/widgets/text.dart
Outdated
/// Processes the "geometry" [TextTheme] fields | ||
/// ([Typography.dense], [Typography.englishLike], and [Typography.tall]) | ||
/// with [weightVariableTextTheme], passing along the [BuildContext]. | ||
Typography weightVariableTypography(BuildContext context, Typography input) => |
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: Let's put all the Typography and TextTheme stuff (the flutter/material
stuff) consecutively in this file.
At the top seems good, consistent with the main entry point being the thing at the top.
lib/widgets/text.dart
Outdated
/// | ||
/// This assumes that if a non-null field omits [TextStyle.fontWeight], then | ||
/// the normal font weight is implied, and it will be expressed in the output. | ||
TextTheme weightVariableTextTheme(BuildContext context, TextTheme input) { |
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.
Similarly:
TextTheme weightVariableTextTheme(BuildContext context, TextTheme input) { | |
TextTheme _weightVariableTextTheme(BuildContext context, TextTheme input) { |
lib/widgets/text.dart
Outdated
/// This looks at each of `input`'s fields, such as [TextTheme.bodyMedium], and: | ||
/// | ||
/// If the field is non-null, calls [TextStyle.merge], passing the result of | ||
/// [weightVariableTextStyle] called with: | ||
/// - this function's [BuildContext] param, and | ||
/// - for `wght`: | ||
/// - if present, the field's [TextStyle.fontWeight] | ||
/// (via [wghtFromFontWeight]), else | ||
/// - null. | ||
/// | ||
/// If the field is null, gives null. | ||
/// | ||
/// This assumes that if a non-null field omits [TextStyle.fontWeight], then | ||
/// the normal font weight is implied, and it will be expressed in the output. |
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 awfully close to restating the code, and it feels like more specific detail than one wants when trying to understand how to use the function.
Here's a version that tries to be a bit more abstract, and hopefully also easier to understand the point:
/// This looks at each of `input`'s fields, such as [TextTheme.bodyMedium], and: | |
/// | |
/// If the field is non-null, calls [TextStyle.merge], passing the result of | |
/// [weightVariableTextStyle] called with: | |
/// - this function's [BuildContext] param, and | |
/// - for `wght`: | |
/// - if present, the field's [TextStyle.fontWeight] | |
/// (via [wghtFromFontWeight]), else | |
/// - null. | |
/// | |
/// If the field is null, gives null. | |
/// | |
/// This assumes that if a non-null field omits [TextStyle.fontWeight], then | |
/// the normal font weight is implied, and it will be expressed in the output. | |
/// This looks at each of the [TextStyle]s found on the input [TextTheme] | |
/// (such as [TextTheme.bodyMedium]), | |
/// and uses [weightVariableTextStyle] to adjust the [TextStyle]. | |
/// Fields that are null in the input [TextTheme] remain null in the output. | |
/// | |
/// For each input [TextStyle], the `wght` value passed | |
/// to [weightVariableTextStyle] is based on the input's [TextStyle.fontWeight]. | |
/// A null [TextStyle.fontWeight] is interpreted as the normal font weight. |
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.
Great; thanks for the suggestion!
lib/widgets/content.dart
Outdated
static TextStyle getTextStyle(BuildContext context) => const TextStyle( | ||
fontFamily: 'Source Sans 3', | ||
fontSize: 14, | ||
height: (17 / 14), | ||
).merge(weightVariableTextStyle(context)); | ||
fontSize: kBaseFontSize, | ||
height: (17 / kBaseFontSize), | ||
); |
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.
For a further nice cleanup (probably in a further cleanup commit at the end):
static const textStyle = TextStyle(
fontSize: kBaseFontSize,
height: (17 / kBaseFontSize));
Since we're no longer looking at context
, this can become a constant.
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.
Ah indeed; I meant to do this as a followup here but forgot.
textStyle: | ||
// Restate [FilledButton]'s default, which inherits from | ||
// [zulipTypography]… | ||
Theme.of(context).textTheme.labelLarge! | ||
// …then clobber some attributes to follow Figma: | ||
.merge(const TextStyle( | ||
fontSize: 18, | ||
height: (23 / 18)) | ||
.merge(weightVariableTextStyle(context, wght: 400))), |
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.
Hmm interesting. I guess this will be a case to consider when thinking about how we ultimately want to arrange the data flow for the text styles.
lib/widgets/text.dart
Outdated
final kDefaultFontFamilyFallback = [ | ||
// iOS doesn't support any of the formats this font is available in. | ||
// If we use it on iOS, we'll get blank spaces where we could have had Apple- | ||
// style emojis. | ||
if (defaultTargetPlatform == TargetPlatform.android) 'Noto Color Emoji', |
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 of a perilous pattern because defaultTargetPlatform
can change over time. Not likely to happen in a running app — but it happens all the time in tests, which indeed is the point of defaultTargetPlatform
.
So as a static final, kDefaultFontFamilyFallback
will get computed once when it's first accessed, and subsequent accesses will get the same value even if defaultTargetPlatform
has changed. If we have a test that cares about the value, then the test could pass or fail depending on which other tests ran first. That in turn can be a big pain for debugging why some test is behaving the way it is (e.g. for figuring out why some other change broke a test).
I think a good fix would be to make it a getter, just like defaultTargetPlatform
itself:
List<String> get defaultFontFamilyFallback => [
There's no performance need to memoize it, because at the tip of the PR we're only calling this twice per build of ZulipApp
.
f9985e4
to
8801f85
Compare
Thanks for the review! Revision pushed. |
Oh, and what do you think of #439 (comment) :
? I haven't done that yet but could. A |
8801f85
to
4b5902e
Compare
Just pushed a revision with a test that spot-checks |
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 the revision! Sorry for the belated review. This all looks good, with a few nits.
I think there'll also be a few spots to update when you rebase atop the package:checks
upgrade you're doing right now.
if (wghtIfPlatformRequestsBold != null) 'wghtIfPlatformRequestsBold: $wghtIfPlatformRequestsBold', | ||
]; | ||
result = result.copyWith( | ||
debugLabel: 'weightVariableTextStyle(${attributes.join(', ')})'); |
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 / good news:
This won't actually take effect until an upstream issue is fixed:
https://github.com/flutter/flutter/issues/141140
That issue has been fixed! (Your upstream PR for it got merged the next day.)
const kDefaultFontFamily = 'Source Sans 3'; | ||
|
||
/// The [TextStyle.fontFamilyFallback] for use with [kDefaultFontFamily]. | ||
List<String> get defaultFontFamilyFallback => [ |
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: commit message has a reference to a previous revision's name for this:
If there's some corner of the app where the two fonts aren't getting
applied, we'll eventually find it, and apply kDefaultFontFamily and
kDefaultFontFamilyFallback according to their doc comments, to fix.
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: this now refers to a defaultFontFamily
, which doesn't exist 🙂 :
If there's some corner of the app where the two fonts aren't getting
applied, we'll eventually find it, and apply defaultFontFamily and
defaultFontFamilyFallback according to their doc comments, to fix.
test/widgets/text_test.dart
Outdated
..fontFamilyFallback.isNotNull().deepEquals(defaultFontFamilyFallback); | ||
|
||
matchesWeight(FontWeight weight) => it<TextStyle>() | ||
..fontWeight.isNotNull().equals(weight) |
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:
..fontWeight.isNotNull().equals(weight) | |
..fontWeight.equals(weight) |
test/widgets/text_test.dart
Outdated
final matchesFonts = it<TextStyle>() | ||
..fontFamily.equals(kDefaultFontFamily) | ||
..fontFamilyFallback.isNotNull().deepEquals(defaultFontFamilyFallback); |
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:
final matchesFonts = it<TextStyle>() | |
..fontFamily.equals(kDefaultFontFamily) | |
..fontFamilyFallback.isNotNull().deepEquals(defaultFontFamilyFallback); | |
final matchesFontFamilies = it<TextStyle>() | |
..fontFamily.equals(kDefaultFontFamily) | |
..fontFamilyFallback.isNotNull().deepEquals(defaultFontFamilyFallback); |
since it's not concerned with the font weight, etc., only the font families
test/widgets/text_test.dart
Outdated
check(const TextTheme().toDiagnosticsNode().getProperties().map((n) => n.name).toList()) | ||
.deepEquals([ | ||
'displayLarge', 'displayMedium', 'displaySmall', |
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: can make this a bit more robust to possible upstream changes that we shouldn't need to care about:
check(const TextTheme().toDiagnosticsNode().getProperties().map((n) => n.name).toList()) | |
.deepEquals([ | |
'displayLarge', 'displayMedium', 'displaySmall', | |
check(const TextTheme().toDiagnosticsNode().getProperties().map((n) => n.name).toList()) | |
.unorderedEquals([ | |
'displayLarge', 'displayMedium', 'displaySmall', |
In zulip#436, we brought back Noto Color Emoji, but just for reaction chips. This brings it to message content in the message list, and all the other places where we've been specifying Source Sans 3. This leaves some edge cases where we'll still fall back to a system emoji font, like if there's an emoji in a user's name and you're looking at the profile screen. (Or in the "sender" area of a message in the message list, for that matter, checking again just now.) Those will be fixed later in this series, along with a cleanup that eliminates the repetition of [kDefaultFontFamily] and [kDefaultFontFamilyFallback] that we see in this commit. Fixes-partly: zulip#438
…tyle Now this should only call for work when the bold text setting is changed, and not when other parts of [MediaQueryData] change.
While we're at it, make them doubles.
…Bold In particular, remove this assert: assert((wght != null) == (wghtIfPlatformRequestsBold != null)); and default the effective `wghtIfPlatformRequestsBold`, when absent, to a consistent formula that gives the same result for all the callers so far. (Including callers that haven't been passing either `wght` or `wghtIfPlatformRequestsBold`: those have been getting [FontWeight.normal.value] / [FontWeight.bold.value], which is 400 / 700; and our new helper gives 700 for 400.)
…tyle This will take effect thanks to my PR for an upstream issue: flutter/flutter#141140 flutter/flutter#141141
4b5902e
to
9553f9f
Compare
Thanks for the review! Revision pushed, atop my latest revision for that |
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 the revision! One fresh nit below — then please go ahead and merge.
const kDefaultFontFamily = 'Source Sans 3'; | ||
|
||
/// The [TextStyle.fontFamilyFallback] for use with [kDefaultFontFamily]. | ||
List<String> get defaultFontFamilyFallback => [ |
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: this now refers to a defaultFontFamily
, which doesn't exist 🙂 :
If there's some corner of the app where the two fonts aren't getting
applied, we'll eventually find it, and apply defaultFontFamily and
defaultFontFamilyFallback according to their doc comments, to fix.
With plumbing to make kDefaultFamily work, since it's a variable-weight font. As it happens, most -- perhaps all -- of the app's text is built on Typography styles. See the next commit for some thoughts on that. Since that's the case, I'll mark this commit as fixing these issues: - zulip#294 Use "Source Sans 3" font for most UI text - zulip#438 Consistently use "Noto Color Emoji" for emojis on Android If there's some corner of the app where the two fonts aren't getting applied, we'll eventually find it, and apply kDefaultFontFamily and defaultFontFamilyFallback according to their doc comments, to fix. Fixes: zulip#294 Fixes: zulip#438
9553f9f
to
97afdcd
Compare
Thanks! Merged, with that fix. |
(As usual please pardon the battery-life indicator in some of these screenshots. Ugh.)
In the second set of screenshots, note that the 🤯 emoji is made to look the same in the message list and the reaction chip. (This is taken on the office Android device.)
Fixes: #294
Fixes: #438