Skip to content

Commit 577fda4

Browse files
committed
text: Use font-family constants for text built on Typography styles
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 kDefaultFontFamilyFallback according to their doc comments, to fix. Fixes: zulip#294 Fixes: zulip#438
1 parent fd795cc commit 577fda4

File tree

3 files changed

+149
-1
lines changed

3 files changed

+149
-1
lines changed

lib/widgets/app.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import 'page.dart';
1515
import 'recent_dm_conversations.dart';
1616
import 'store.dart';
1717
import 'subscription_list.dart';
18+
import 'text.dart';
1819

1920
class ZulipApp extends StatelessWidget {
2021
const ZulipApp({super.key, this.navigatorObservers});
@@ -81,6 +82,7 @@ class ZulipApp extends StatelessWidget {
8182
@override
8283
Widget build(BuildContext context) {
8384
final theme = ThemeData(
85+
typography: zulipTypography(context),
8486
appBarTheme: const AppBarTheme(
8587
// This prevents an elevation change in [AppBar]s so they stop turning
8688
// darker if there is something scrolled underneath it. See docs:

lib/widgets/text.dart

Lines changed: 104 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,100 @@
11
import 'dart:io';
22
import 'package:flutter/foundation.dart';
3-
import 'package:flutter/widgets.dart';
3+
import 'package:flutter/material.dart';
4+
5+
/// An app-wide [Typography] for Zulip, customized from the Material default.
6+
///
7+
/// Include this in the app-wide [MaterialApp.theme].
8+
///
9+
/// We expect these text styles to be the basis of all the styles chosen by the
10+
/// Material library's widgets, such as the default styling of
11+
/// an [AppBar]'s title, of an [ElevatedButton]'s label, and so on.
12+
///
13+
/// In all the comprised [TextStyle]s, this sets:
14+
///
15+
/// - [TextStyle.fontFamily] to [kDefaultFontFamily], and
16+
/// - [TextStyle.fontFamilyFallback] to [kDefaultFontFamilyFallback].
17+
///
18+
/// Since [kDefaultFontFamily] is a variable-weight font,
19+
/// each [TextStyle] needs some processing, which this provides,
20+
/// in order to correctly specify the font weight.
21+
///
22+
/// When building on top of these [TextStyles], callers that wish to specify
23+
/// a different font weight are still responsible for reprocessing the style
24+
/// with [weightVariableTextStyle] before passing it to a [Text].
25+
/// (Widgets in the Material library won't do this; they aren't yet equipped
26+
/// to set font weights on variable-weight fonts. If this causes visible bugs,
27+
/// we should investigate and fix, but they should become less likely as
28+
/// we transition from Material's widgets to our own bespoke ones.)
29+
Typography zulipTypography(BuildContext context) {
30+
final typography = Theme.of(context).typography;
31+
return _weightVariableTypography(context, typography)
32+
.copyWith(
33+
black: typography.black.apply(
34+
fontFamily: kDefaultFontFamily,
35+
fontFamilyFallback: defaultFontFamilyFallback),
36+
white: typography.white.apply(
37+
fontFamily: kDefaultFontFamily,
38+
fontFamilyFallback: defaultFontFamilyFallback),
39+
);
40+
}
41+
42+
/// Convert an input [Typography] to one that works with "wght"-variable fonts.
43+
///
44+
/// Processes the "geometry" [TextTheme] fields
45+
/// ([Typography.dense], [Typography.englishLike], and [Typography.tall])
46+
/// with [_weightVariableTextTheme], passing along the [BuildContext].
47+
Typography _weightVariableTypography(BuildContext context, Typography input) =>
48+
input.copyWith(
49+
dense: _weightVariableTextTheme(context, input.dense),
50+
englishLike: _weightVariableTextTheme(context, input.englishLike),
51+
tall: _weightVariableTextTheme(context, input.tall),
52+
);
53+
54+
/// Convert a geometry [TextTheme] to one that works with "wght"-variable fonts.
55+
///
56+
/// A "geometry [TextTheme]" is a [TextTheme] that's meant to specify
57+
/// font weight and other parameters about shape, size, distance, etc.
58+
/// See [Typography].
59+
///
60+
/// This looks at each of the [TextStyle]s found on the input [TextTheme]
61+
/// (such as [TextTheme.bodyMedium]),
62+
/// and uses [weightVariableTextStyle] to adjust the [TextStyle].
63+
/// Fields that are null in the input [TextTheme] remain null in the output.
64+
///
65+
/// For each input [TextStyle], the `wght` value passed
66+
/// to [weightVariableTextStyle] is based on the input's [TextStyle.fontWeight].
67+
/// A null [TextStyle.fontWeight] is interpreted as the normal font weight.
68+
TextTheme _weightVariableTextTheme(BuildContext context, TextTheme input) {
69+
TextStyle? convert(TextStyle? maybeInputStyle) {
70+
if (maybeInputStyle == null) {
71+
return null;
72+
}
73+
final maybeInputFontWeight = maybeInputStyle.fontWeight;
74+
return maybeInputStyle.merge(weightVariableTextStyle(context,
75+
wght: maybeInputFontWeight != null
76+
? wghtFromFontWeight(maybeInputFontWeight)
77+
: null));
78+
}
79+
80+
return TextTheme(
81+
displayLarge: convert(input.displayLarge),
82+
displayMedium: convert(input.displayMedium),
83+
displaySmall: convert(input.displaySmall),
84+
headlineLarge: convert(input.headlineLarge),
85+
headlineMedium: convert(input.headlineMedium),
86+
headlineSmall: convert(input.headlineSmall),
87+
titleLarge: convert(input.titleLarge),
88+
titleMedium: convert(input.titleMedium),
89+
titleSmall: convert(input.titleSmall),
90+
bodyLarge: convert(input.bodyLarge),
91+
bodyMedium: convert(input.bodyMedium),
92+
bodySmall: convert(input.bodySmall),
93+
labelLarge: convert(input.labelLarge),
94+
labelMedium: convert(input.labelMedium),
95+
labelSmall: convert(input.labelSmall),
96+
);
97+
}
498

599
/// The [TextStyle.fontFamily] to use in most of the app.
6100
///
@@ -133,3 +227,12 @@ FontWeight clampVariableFontWeight(double wght) {
133227
}
134228
}
135229
}
230+
231+
/// A good guess at a font's "wght" value to match a given [FontWeight].
232+
///
233+
/// Returns [FontWeight.value] as a double.
234+
///
235+
/// This might not be exactly where the font designer would land on their
236+
/// font's own custom-defined "wght" axis. But it's a great guess,
237+
/// at least without knowledge of the particular font.
238+
double wghtFromFontWeight(FontWeight fontWeight) => fontWeight.value.toDouble();

test/widgets/text_test.dart

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,49 @@ import 'package:zulip/widgets/text.dart';
77
import '../flutter_checks.dart';
88

99
void main() {
10+
test('weightVariableTypography: ScriptCategory has the assumed values', () {
11+
// If Flutter adds a new one, check [Typography] for a corresponding
12+
// "geometry [TextStyle]" and update weightVariableTypography to process it.
13+
check(ScriptCategory.values).deepEquals(
14+
[ScriptCategory.englishLike, ScriptCategory.dense, ScriptCategory.tall]);
15+
});
16+
17+
// TODO exercise/check weightVariableTypography
18+
19+
test('weightVariableTextTheme: TextTheme has the assumed fields', () {
20+
const family = '123';
21+
const withFamily = TextStyle(fontFamily: family);
22+
23+
const base = Typography.englishLike2021;
24+
25+
// Keep a lookout for new [TextTheme] fields that didn't exist at the time
26+
// of writing. If there are new fields, we should update
27+
// [weightVariableTextTheme] so it handles them.
28+
check(
29+
base.merge(const TextTheme(
30+
displayLarge: withFamily,
31+
displayMedium: withFamily,
32+
displaySmall: withFamily,
33+
headlineLarge: withFamily,
34+
headlineMedium: withFamily,
35+
headlineSmall: withFamily,
36+
titleLarge: withFamily,
37+
titleMedium: withFamily,
38+
titleSmall: withFamily,
39+
bodyLarge: withFamily,
40+
bodyMedium: withFamily,
41+
bodySmall: withFamily,
42+
labelLarge: withFamily,
43+
labelMedium: withFamily,
44+
labelSmall: withFamily,
45+
))
46+
).equals(
47+
base.apply(fontFamily: family),
48+
);
49+
});
50+
51+
// TODO exercise/check weightVariableTextTheme
52+
1053
group('weightVariableTextStyle', () {
1154
Future<void> testWeights(
1255
String description, {

0 commit comments

Comments
 (0)