Commit e57d94c3 authored by Hans Muller's avatar Hans Muller Committed by GitHub

Make it possible to localize an app title (#12105)

parent 9d59fb0c
...@@ -85,6 +85,7 @@ class MaterialApp extends StatefulWidget { ...@@ -85,6 +85,7 @@ class MaterialApp extends StatefulWidget {
MaterialApp({ // can't be const because the asserts use methods on Map :-( MaterialApp({ // can't be const because the asserts use methods on Map :-(
Key key, Key key,
this.title: '', this.title: '',
this.onGenerateTitle,
this.color, this.color,
this.theme, this.theme,
this.home, this.home,
...@@ -132,9 +133,30 @@ class MaterialApp extends StatefulWidget { ...@@ -132,9 +133,30 @@ class MaterialApp extends StatefulWidget {
), ),
super(key: key); super(key: key);
/// A one-line description of this app for use in the window manager. /// A one-line description used by the device to identify the app for the user.
///
/// On Android the titles appear above the task manager's app snapshots which are
/// displayed when the user presses the "recent apps" button. Similarly, on
/// iOS the titles appear in the App Switcher when the user double presses the
/// home button.
///
/// To provide a localized title instead, use [onGenerateTitle].
///
/// This value is passed unmodified to [WidgetsApp.title].
final String title; final String title;
/// If non-null this function is called to produce the app's
/// title string, otherwise [title] is used.
///
/// The [onGenerateTitle] `context` parameter includes the [WidgetApp]'s
/// [Localizations] widget so that this callback can be used to produce a
/// localized title.
///
/// This callback function must not return null.
///
/// This value is passed unmodified to [WidgetsApp.onGenerateTitle].
final GenerateAppTitle onGenerateTitle;
/// The colors to use for the application's widgets. /// The colors to use for the application's widgets.
final ThemeData theme; final ThemeData theme;
...@@ -320,7 +342,7 @@ class MaterialApp extends StatefulWidget { ...@@ -320,7 +342,7 @@ class MaterialApp extends StatefulWidget {
/// configure this list to match the locales they support. /// configure this list to match the locales they support.
/// ///
/// This list must not null. It's default value is just /// This list must not null. It's default value is just
/// `[const Locale('en', 'US')]`. It is simply passed along to the /// `[const Locale('en', 'US')]`. It is passed along unmodified to the
/// [WidgetsApp] built by this widget. /// [WidgetsApp] built by this widget.
/// ///
/// The order of the list matters. By default, if the device's locale doesn't /// The order of the list matters. By default, if the device's locale doesn't
...@@ -508,6 +530,7 @@ class _MaterialAppState extends State<MaterialApp> { ...@@ -508,6 +530,7 @@ class _MaterialAppState extends State<MaterialApp> {
child: new WidgetsApp( child: new WidgetsApp(
key: new GlobalObjectKey(this), key: new GlobalObjectKey(this),
title: widget.title, title: widget.title,
onGenerateTitle: widget.onGenerateTitle,
textStyle: _errorTextStyle, textStyle: _errorTextStyle,
// blue is the primary color of the default theme // blue is the primary color of the default theme
color: widget.color ?? theme?.primaryColor ?? Colors.blue, color: widget.color ?? theme?.primaryColor ?? Colors.blue,
......
...@@ -34,6 +34,16 @@ export 'dart:ui' show Locale; ...@@ -34,6 +34,16 @@ export 'dart:ui' show Locale;
/// parameter is just the value of [WidgetApp.supportedLocales]. /// parameter is just the value of [WidgetApp.supportedLocales].
typedef Locale LocaleResolutionCallback(Locale locale, Iterable<Locale> supportedLocales); typedef Locale LocaleResolutionCallback(Locale locale, Iterable<Locale> supportedLocales);
/// The signature of [WidgetsApp.onGenerateTitle].
///
/// Used to generate a value for the app's [Title.title], which the device uses
/// to identify the app for the user. The `context` includes the [WidgetApp]'s
/// [Localizations] widget so that this method can be used to produce a
/// localized title.
///
/// This function must not return null.
typedef String GenerateAppTitle(BuildContext context);
// Delegate that fetches the default (English) strings. // Delegate that fetches the default (English) strings.
class _WidgetsLocalizationsDelegate extends LocalizationsDelegate<WidgetsLocalizations> { class _WidgetsLocalizationsDelegate extends LocalizationsDelegate<WidgetsLocalizations> {
const _WidgetsLocalizationsDelegate(); const _WidgetsLocalizationsDelegate();
...@@ -71,6 +81,7 @@ class WidgetsApp extends StatefulWidget { ...@@ -71,6 +81,7 @@ class WidgetsApp extends StatefulWidget {
@required this.onGenerateRoute, @required this.onGenerateRoute,
this.onUnknownRoute, this.onUnknownRoute,
this.title: '', this.title: '',
this.onGenerateTitle,
this.textStyle, this.textStyle,
@required this.color, @required this.color,
this.navigatorObservers: const <NavigatorObserver>[], this.navigatorObservers: const <NavigatorObserver>[],
...@@ -99,9 +110,26 @@ class WidgetsApp extends StatefulWidget { ...@@ -99,9 +110,26 @@ class WidgetsApp extends StatefulWidget {
assert(debugShowWidgetInspector != null), assert(debugShowWidgetInspector != null),
super(key: key); super(key: key);
/// A one-line description of this app for use in the window manager. /// A one-line description used by the device to identify the app for the user.
///
/// On Android the titles appear above the task manager's app snapshots which are
/// displayed when the user presses the "recent apps" button. Similarly, on
/// iOS the titles appear in the App Switcher when the user double presses the
/// home button.
///
/// To provide a localized title instead, use [onGenerateTitle].
final String title; final String title;
/// If non-null this callback function is called to produce the app's
/// title string, otherwise [title] is used.
///
/// The [onGenerateTitle] `context` parameter includes the [WidgetApp]'s
/// [Localizations] widget so that this callback can be used to produce a
/// localized title.
///
/// This callback function must not return null.
final GenerateAppTitle onGenerateTitle;
/// The default text style for [Text] in the application. /// The default text style for [Text] in the application.
final TextStyle textStyle; final TextStyle textStyle;
...@@ -465,10 +493,22 @@ class _WidgetsAppState extends State<WidgetsApp> implements WidgetsBindingObserv ...@@ -465,10 +493,22 @@ class _WidgetsAppState extends State<WidgetsApp> implements WidgetsBindingObserv
child: new Localizations( child: new Localizations(
locale: widget.locale ?? _locale, locale: widget.locale ?? _locale,
delegates: _localizationsDelegates.toList(), delegates: _localizationsDelegates.toList(),
child: new Title( // This Builder exists to provide a context below the Localizations widget.
title: widget.title, // The onGenerateCallback() can refer to Localizations via its context
color: widget.color, // parameter.
child: result, child: new Builder(
builder: (BuildContext context) {
String title = widget.title;
if (widget.onGenerateTitle != null) {
title = widget.onGenerateTitle(context);
assert(title != null, 'onGenerateTitle must return a non-null String');
}
return new Title(
title: title,
color: widget.color,
child: result,
);
},
), ),
), ),
); );
......
// 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_test/flutter_test.dart';
import 'package:flutter/widgets.dart';
const Color kTitleColor = const Color(0xFF333333);
const String kTitleString = 'Hello World';
Future<Null> pumpApp(WidgetTester tester, { GenerateAppTitle onGenerateTitle }) async {
await tester.pumpWidget(
new WidgetsApp(
supportedLocales: <Locale>[
const Locale('en', 'US'),
const Locale('en', 'GB'),
],
title: kTitleString,
color: kTitleColor,
onGenerateTitle: onGenerateTitle,
onGenerateRoute: (RouteSettings settings) {
return new PageRouteBuilder<Null>(
pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
return new Container();
}
);
},
),
);
}
void main() {
testWidgets('Specified title and color are used to build a Title', (WidgetTester tester) async {
await pumpApp(tester);
expect(tester.widget<Title>(find.byType(Title)).title, kTitleString);
expect(tester.widget<Title>(find.byType(Title)).color, kTitleColor);
});
testWidgets('onGenerateTitle handles changing locales', (WidgetTester tester) async {
String generateTitle(BuildContext context) {
return Localizations.localeOf(context).toString();
}
await pumpApp(tester, onGenerateTitle: generateTitle);
expect(tester.widget<Title>(find.byType(Title)).title, 'en_US');
expect(tester.widget<Title>(find.byType(Title)).color, kTitleColor);
await tester.binding.setLocale('en', 'GB');
await tester.pump();
expect(tester.widget<Title>(find.byType(Title)).title, 'en_GB');
expect(tester.widget<Title>(find.byType(Title)).color, kTitleColor);
// Not a supported locale, so we switch to supportedLocales[0], en_US
await tester.binding.setLocale('fr', 'CA');
await tester.pump();
expect(tester.widget<Title>(find.byType(Title)).title, 'en_US');
expect(tester.widget<Title>(find.byType(Title)).color, kTitleColor);
});
}
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