Commit b9e1be9a authored by Yegor's avatar Yegor Committed by GitHub

introduce localized text geometry in MaterialLocalizations (#11829)

* introduce localized text geometry in MaterialLocalizations

* remove geometry from color text themes

* fix merge conflict

* optional Localizations

* fix fallback; test; docs
parent f4f20c29
......@@ -12,6 +12,7 @@
/// This variable is used by [MaterialLocalizations].
const Map<String, Map<String, String>> localizations = const <String, Map<String, String>> {
"ar": const <String, String>{
"scriptCategory": r"tall",
"timeOfDayFormat": r"h:mm a",
"openAppDrawerTooltip": r"افتح قائمة التنقل",
"backButtonTooltip": r"الى الخلف",
......@@ -40,6 +41,7 @@ const Map<String, Map<String, String>> localizations = const <String, Map<String
"postMeridiemAbbreviation": r"م",
},
"de": const <String, String>{
"scriptCategory": r"English-like",
"timeOfDayFormat": r"HH:mm",
"openAppDrawerTooltip": r"Navigationsmenü öffnen",
"backButtonTooltip": r"Zurück",
......@@ -68,6 +70,7 @@ const Map<String, Map<String, String>> localizations = const <String, Map<String
"viewLicensesButtonLabel": r"LIZENZEN ANZEIGEN",
},
"en": const <String, String>{
"scriptCategory": r"English-like",
"timeOfDayFormat": r"h:mm a",
"openAppDrawerTooltip": r"Open navigation menu",
"backButtonTooltip": r"Back",
......@@ -107,6 +110,7 @@ const Map<String, Map<String, String>> localizations = const <String, Map<String
"timeOfDayFormat": r"HH:mm",
},
"es": const <String, String>{
"scriptCategory": r"English-like",
"timeOfDayFormat": r"H:mm",
"openAppDrawerTooltip": r"Abrir el menú de navegación",
"backButtonTooltip": r"Espalda",
......@@ -140,6 +144,7 @@ const Map<String, Map<String, String>> localizations = const <String, Map<String
"postMeridiemAbbreviation": r"PM",
},
"fa": const <String, String>{
"scriptCategory": r"tall",
"timeOfDayFormat": r"H:mm",
"openAppDrawerTooltip": r"منوی ناوبری را باز کنید",
"backButtonTooltip": r"بازگشت",
......@@ -166,6 +171,7 @@ const Map<String, Map<String, String>> localizations = const <String, Map<String
"viewLicensesButtonLabel": r"مشاهده مجوز",
},
"fr": const <String, String>{
"scriptCategory": r"English-like",
"timeOfDayFormat": r"HH:mm",
"openAppDrawerTooltip": r"Ouvrir le menu de navigation",
"backButtonTooltip": r"Retour",
......@@ -197,6 +203,7 @@ const Map<String, Map<String, String>> localizations = const <String, Map<String
"timeOfDayFormat": r"HH 'h' mm",
},
"he": const <String, String>{
"scriptCategory": r"English-like",
"timeOfDayFormat": r"H:mm",
"openAppDrawerTooltip": r"פתח תפריט ניווט",
"backButtonTooltip": r"אחורה",
......@@ -223,6 +230,7 @@ const Map<String, Map<String, String>> localizations = const <String, Map<String
"viewLicensesButtonLabel": r"ראה רישיונות",
},
"it": const <String, String>{
"scriptCategory": r"English-like",
"timeOfDayFormat": r"HH:mm",
"openAppDrawerTooltip": r"Apri il menu di navigazione",
"backButtonTooltip": r"Indietro",
......@@ -249,6 +257,7 @@ const Map<String, Map<String, String>> localizations = const <String, Map<String
"viewLicensesButtonLabel": r"VEDI LE LICENZE",
},
"ja": const <String, String>{
"scriptCategory": r"dense",
"timeOfDayFormat": r"H:mm",
"openAppDrawerTooltip": r"ナビゲーションメニューを開く",
"backButtonTooltip": r"戻る",
......@@ -275,6 +284,7 @@ const Map<String, Map<String, String>> localizations = const <String, Map<String
"viewLicensesButtonLabel": r"ライセンス表記",
},
"ps": const <String, String>{
"scriptCategory": r"tall",
"timeOfDayFormat": r"HH:mm",
"openAppDrawerTooltip": r"د پرانیستی نیینګ مینو",
"backButtonTooltip": r"شاته",
......@@ -301,6 +311,7 @@ const Map<String, Map<String, String>> localizations = const <String, Map<String
"viewLicensesButtonLabel": r"لیدلس وګورئ",
},
"pt": const <String, String>{
"scriptCategory": r"English-like",
"timeOfDayFormat": r"HH:mm",
"openAppDrawerTooltip": r"Abrir menu de navegação",
"backButtonTooltip": r"Costas",
......@@ -327,6 +338,7 @@ const Map<String, Map<String, String>> localizations = const <String, Map<String
"viewLicensesButtonLabel": r"VER LICENÇAS",
},
"ru": const <String, String>{
"scriptCategory": r"English-like",
"timeOfDayFormat": r"H:mm",
"openAppDrawerTooltip": r"Открыть меню навигации",
"backButtonTooltip": r"Назад",
......@@ -355,6 +367,7 @@ const Map<String, Map<String, String>> localizations = const <String, Map<String
"viewLicensesButtonLabel": r"ПРОСМОТРЕТЬ ЛИЦЕНЗИИ",
},
"sd": const <String, String>{
"scriptCategory": r"tall",
"timeOfDayFormat": r"HH:mm",
"openAppDrawerTooltip": r"اوپن جي مينڊيٽ مينيو",
"backButtonTooltip": r"پوئتي",
......@@ -381,6 +394,7 @@ const Map<String, Map<String, String>> localizations = const <String, Map<String
"viewLicensesButtonLabel": r"لائسنس ڏسو",
},
"ur": const <String, String>{
"scriptCategory": r"tall",
"timeOfDayFormat": r"h:mm a",
"openAppDrawerTooltip": r"کھولیں نیویگیشن مینو",
"backButtonTooltip": r"واپس",
......@@ -409,6 +423,7 @@ const Map<String, Map<String, String>> localizations = const <String, Map<String
"postMeridiemAbbreviation": r"PM",
},
"zh": const <String, String>{
"scriptCategory": r"dense",
"timeOfDayFormat": r"ah:mm",
"openAppDrawerTooltip": r"打开导航菜单",
"backButtonTooltip": r"返回",
......
{
"scriptCategory": "tall",
"timeOfDayFormat": "h:mm a",
"openAppDrawerTooltip": "افتح قائمة التنقل",
"backButtonTooltip": "الى الخلف",
......
{
"scriptCategory": "English-like",
"timeOfDayFormat": "HH:mm",
"@anteMeridiemAbbreviation": { "notUsed": "German time format does not use a.m. indicator" },
"@postMeridiemAbbreviation": { "notUsed": "German time format does not use p.m. indicator" },
......
{
"scriptCategory": "English-like",
"@scriptCategory": {
"description": "The name of the language's script category (see https://material.io/guidelines/style/typography.html#typography-language-categories-reference)",
"type": "text"
},
"timeOfDayFormat": "h:mm a",
"@timeOfDayFormat": {
"description": "The ICU 'Short Time' pattern, such as 'HH:mm', 'h:mm a', 'H:mm'. See: http://demo.icu-project.org/icu-bin/locexp?d_=en&_=en_US",
......
{
"scriptCategory": "English-like",
"timeOfDayFormat": "H:mm",
"@anteMeridiemAbbreviation": { "notUsed": "Standard Spanish time format does not use a.m. indicator" },
"@postMeridiemAbbreviation": { "notUsed": "Standard Spanish time format does not use p.m. indicator" },
......
{
"scriptCategory": "tall",
"timeOfDayFormat": "H:mm",
"@anteMeridiemAbbreviation": { "notUsed": "Farsi time format does not use a.m. indicator" },
"@postMeridiemAbbreviation": { "notUsed": "Farsi time format does not use p.m. indicator" },
......
{
"scriptCategory": "English-like",
"timeOfDayFormat": "HH:mm",
"@anteMeridiemAbbreviation": { "notUsed": "French time format does not use a.m. indicator" },
"@postMeridiemAbbreviation": { "notUsed": "French time format does not use p.m. indicator" },
......
{
"scriptCategory": "English-like",
"timeOfDayFormat": "H:mm",
"@anteMeridiemAbbreviation": { "notUsed": "Hebrew time format does not use a.m. indicator" },
"@postMeridiemAbbreviation": { "notUsed": "Hebrew time format does not use p.m. indicator" },
......
{
"scriptCategory": "English-like",
"timeOfDayFormat": "HH:mm",
"@anteMeridiemAbbreviation": { "notUsed": "Italian time format does not use a.m. indicator" },
"@postMeridiemAbbreviation": { "notUsed": "Italian time format does not use p.m. indicator" },
......
{
"scriptCategory": "dense",
"timeOfDayFormat": "H:mm",
"@anteMeridiemAbbreviation": { "notUsed": "Japanese time format does not use a.m. indicator" },
"@postMeridiemAbbreviation": { "notUsed": "Japanese time format does not use p.m. indicator" },
......
{
"scriptCategory": "tall",
"timeOfDayFormat": "HH:mm",
"@anteMeridiemAbbreviation": { "notUsed": "Pashto time format does not use a.m. indicator" },
"@postMeridiemAbbreviation": { "notUsed": "Pashto time format does not use p.m. indicator" },
......
{
"scriptCategory": "English-like",
"timeOfDayFormat": "HH:mm",
"@anteMeridiemAbbreviation": { "notUsed": "Portuguese time format does not use a.m. indicator" },
"@postMeridiemAbbreviation": { "notUsed": "Portuguese time format does not use p.m. indicator" },
......
{
"scriptCategory": "English-like",
"timeOfDayFormat": "H:mm",
"@anteMeridiemAbbreviation": { "notUsed": "Russian time format does not use a.m. indicator" },
"@postMeridiemAbbreviation": { "notUsed": "Russian time format does not use p.m. indicator" },
......
{
"scriptCategory": "tall",
"timeOfDayFormat": "HH:mm",
"@anteMeridiemAbbreviation": { "notUsed": "Sindhi time format does not use a.m. indicator" },
"@postMeridiemAbbreviation": { "notUsed": "Sindhi time format does not use p.m. indicator" },
......
{
"scriptCategory": "tall",
"timeOfDayFormat": "h:mm a",
"openAppDrawerTooltip": "کھولیں نیویگیشن مینو",
"backButtonTooltip": "واپس",
......
{
"scriptCategory": "dense",
"timeOfDayFormat": "ah:mm",
"openAppDrawerTooltip": "打开导航菜单",
"backButtonTooltip": "返回",
......
......@@ -9,6 +9,7 @@ import 'package:flutter/widgets.dart';
import 'package:intl/intl.dart' as intl;
import 'i18n/localizations.dart';
import 'typography.dart';
/// Defines the localized resource values used by the Material widgets.
///
......@@ -95,6 +96,19 @@ abstract class MaterialLocalizations {
/// each supported layout.
TimeOfDayFormat get timeOfDayFormat;
/// Provides geometric text preferences for the current locale.
///
/// This text theme is incomplete. For example, it lacks text color
/// information. This theme must be merged with another text theme that
/// provides the missing values. The text styles provided by this theme have
/// their [TextStyle.inherit] property set to `true`.
///
/// Typically a complete theme is obtained via [Theme.of], which can be
/// localized using the [Localizations] widget.
///
/// See also: https://material.io/guidelines/style/typography.html
TextTheme get localTextGeometry;
/// The `MaterialLocalizations` from the closest [Localizations] instance
/// that encloses the given context.
///
......@@ -287,6 +301,10 @@ class DefaultMaterialLocalizations implements MaterialLocalizations {
return _icuTimeOfDayToEnum[icuShortTimePattern];
}
/// Looks up text geometry defined in [MaterialTextGeometry].
@override
TextTheme get localTextGeometry => MaterialTextGeometry.forScriptCategory(_nameToValue["scriptCategory"]);
/// Creates an object that provides localized resource values for the
/// for the widgets of the material library.
///
......
......@@ -5,7 +5,9 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'material_localizations.dart';
import 'theme_data.dart';
import 'typography.dart';
export 'theme_data.dart' show Brightness, ThemeData;
......@@ -65,6 +67,10 @@ class Theme extends StatelessWidget {
/// The data from the closest [Theme] instance that encloses the given
/// context.
///
/// If the given context is enclosed in a [Localizations] widget providing
/// [MaterialLocalizations], the returned data is localized according to the
/// nearest available [MaterialLocalizations].
///
/// Defaults to [new ThemeData.fallback] if there is no [Theme] in the given
/// build context.
///
......@@ -123,7 +129,11 @@ class Theme extends StatelessWidget {
return null;
return inheritedTheme.theme.data;
}
return (inheritedTheme != null) ? inheritedTheme.theme.data : _kFallbackTheme;
final ThemeData colorTheme = (inheritedTheme != null) ? inheritedTheme.theme.data : _kFallbackTheme;
final MaterialLocalizations localizations = MaterialLocalizations.of(context);
final TextTheme geometryTheme = localizations?.localTextGeometry ?? MaterialTextGeometry.englishLike;
return ThemeData.localize(colorTheme, geometryTheme);
}
@override
......
......@@ -401,10 +401,13 @@ class Localizations extends StatefulWidget {
return new List<LocalizationsDelegate<dynamic>>.from(scope.localizationsState.widget.delegates);
}
/// Returns the 'type' localized resources for the widget tree that
/// corresponds to [BuildContext] `context`.
/// Returns the localized resources object of the given `type` for the widget
/// tree that corresponds to the given `context`.
///
/// Returns `null` if no resources object of the given `type` exists within
/// the given `context`.
///
/// This method is typically used by a static factory method on the 'type'
/// This method is typically used by a static factory method on the `type`
/// class. For example Flutter's MaterialLocalizations class looks up Material
/// resources with a method defined like this:
///
......@@ -417,8 +420,7 @@ class Localizations extends StatefulWidget {
assert(context != null);
assert(type != null);
final _LocalizationsScope scope = context.inheritFromWidgetOfExactType(_LocalizationsScope);
assert(scope != null, 'a Localizations ancestor was not found');
return scope.localizationsState.resourcesFor<T>(type);
return scope?.localizationsState?.resourcesFor<T>(type);
}
@override
......
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/widgets.dart';
void main() {
testWidgets('$MaterialLocalizations localizes text inside the tree', (WidgetTester tester) async {
await tester.pumpWidget(new MaterialApp(
home: new ListView(
children: <Widget>[
new LocalizationTracker(key: const ValueKey<String>('outer')),
new Localizations(
locale: const Locale('zh', 'CN'),
delegates: <LocalizationsDelegate<dynamic>>[
new _MaterialLocalizationsDelegate(
new DefaultMaterialLocalizations(const Locale('zh', 'CN')),
),
const DefaultWidgetsLocalizationsDelegate(),
],
child: new LocalizationTracker(key: const ValueKey<String>('inner')),
),
],
),
));
final LocalizationTrackerState outerTracker = tester.state(find.byKey(const ValueKey<String>('outer')));
expect(outerTracker.captionFontSize, 12.0);
final LocalizationTrackerState innerTracker = tester.state(find.byKey(const ValueKey<String>('inner')));
expect(innerTracker.captionFontSize, 13.0);
});
}
class LocalizationTracker extends StatefulWidget {
LocalizationTracker({Key key}) : super(key: key);
@override
State<StatefulWidget> createState() => new LocalizationTrackerState();
}
class LocalizationTrackerState extends State<LocalizationTracker> {
double captionFontSize;
@override
Widget build(BuildContext context) {
captionFontSize = Theme.of(context).textTheme.caption.fontSize;
return new Container();
}
}
// Same as _MaterialLocalizationsDelegate in widgets/app.dart
class _MaterialLocalizationsDelegate extends LocalizationsDelegate<MaterialLocalizations> {
const _MaterialLocalizationsDelegate(this.localizations);
final MaterialLocalizations localizations;
@override
Future<MaterialLocalizations> load(Locale locale) {
return new SynchronousFuture<MaterialLocalizations>(localizations);
}
@override
bool shouldReload(_MaterialLocalizationsDelegate old) => false;
}
// Same as _WidgetsLocalizationsDelegate in widgets/app.dart
class DefaultWidgetsLocalizationsDelegate extends LocalizationsDelegate<WidgetsLocalizations> {
const DefaultWidgetsLocalizationsDelegate();
@override
Future<WidgetsLocalizations> load(Locale locale) {
return new SynchronousFuture<WidgetsLocalizations>(new DefaultWidgetsLocalizations(locale));
}
@override
bool shouldReload(DefaultWidgetsLocalizationsDelegate old) => false;
}
......@@ -4,8 +4,8 @@
import 'dart:async';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
......@@ -789,8 +789,11 @@ void main() {
});
testWidgets('TextField with default helperStyle', (WidgetTester tester) async {
final ThemeData themeData = new ThemeData(
hintColor: Colors.blue[500],
final ThemeData themeData = ThemeData.localize(
new ThemeData(
hintColor: Colors.blue[500],
),
MaterialTextGeometry.forScriptCategory(MaterialTextGeometry.englishLikeCategory),
);
await tester.pumpWidget(
......
......@@ -53,7 +53,9 @@ void main() {
)
);
expect(Theme.of(capturedContext), equals(new ThemeData.fallback()));
final dynamic localizedTheme = Theme.of(capturedContext);
expect('${localizedTheme.runtimeType}', '_LocalizedThemeData');
expect(localizedTheme.delegate, equals(new ThemeData.fallback()));
expect(Theme.of(capturedContext, shadowThemeOnly: true), isNull);
});
......
......@@ -30,23 +30,27 @@ void main() {
test('Typography on iOS defaults to the correct SF font family based on size', () {
// Ref: https://developer.apple.com/ios/human-interface-guidelines/visual-design/typography/
final Matcher hasCorrectFont = predicate((TextStyle s) {
return s.fontFamily == (s.fontSize <= 19.0 ? '.SF UI Text' : '.SF UI Display');
}, 'Uses SF Display font for font sizes over 19.0, otherwise SF Text font');
final Matcher isDisplayFont = predicate((TextStyle s) {
return s.fontFamily == '.SF UI Display';
}, 'Uses SF Display font');
final Matcher isTextFont = predicate((TextStyle s) {
return s.fontFamily == '.SF UI Text';
}, 'Uses SF Text font');
final Typography typography = new Typography(platform: TargetPlatform.iOS);
for (TextTheme textTheme in <TextTheme>[typography.black, typography.white]) {
expect(textTheme.display4, hasCorrectFont);
expect(textTheme.display3, hasCorrectFont);
expect(textTheme.display2, hasCorrectFont);
expect(textTheme.display1, hasCorrectFont);
expect(textTheme.headline, hasCorrectFont);
expect(textTheme.title, hasCorrectFont);
expect(textTheme.subhead, hasCorrectFont);
expect(textTheme.body2, hasCorrectFont);
expect(textTheme.body1, hasCorrectFont);
expect(textTheme.caption, hasCorrectFont);
expect(textTheme.button, hasCorrectFont);
expect(textTheme.display4, isDisplayFont);
expect(textTheme.display3, isDisplayFont);
expect(textTheme.display2, isDisplayFont);
expect(textTheme.display1, isDisplayFont);
expect(textTheme.headline, isDisplayFont);
expect(textTheme.title, isDisplayFont);
expect(textTheme.subhead, isTextFont);
expect(textTheme.body2, isTextFont);
expect(textTheme.body1, isTextFont);
expect(textTheme.caption, isTextFont);
expect(textTheme.button, isTextFont);
}
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment