Commit 8c2c5022 authored by Hans Muller's avatar Hans Muller Committed by GitHub

Updated I18N API for Flutter (#11497)

parent 7676e511
To rebuild the i18n files:
## Regenerating the i18n files
The files in this directory are based on ../lib/stock_strings.dart
which defines all of the localizable strings used by the stocks
app. The stocks app uses
the [Dart `intl` package](https://github.com/dart-lang/intl).
Rebuilding everything requires two steps.
With the `examples/stocks` as the current directory, generate
`intl_messages.arb` from `lib/stock_strings.dart`:
```
pub run intl_translation:generate_from_arb \
--output-dir=lib/i18n \
--generated-file-prefix=stock_ \
--no-use-deferred-loading \
lib/*.dart \
lib/i18n/*.arb
flutter pub pub run intl_translation:extract_to_arb --output-dir=lib/i18n lib/stock_strings.dart
```
The `intl_messages.arb` file is a JSON format map with one entry for
each `Intl.message()` function defined in `stock_strings.dart`. This
file was used to create the English and Spanish localizations,
`stocks_en.arb` and `stocks_es.arb`. The `intl_messages.arb` wasn't
checked into the repository, since it only serves as a template for
the other `.arb` files.
With the `examples/stocks` as the current directory, generate a
`stock_messages_<locale>.dart` for each `stocks_<locale>.arb` file and
`stock_messages_all.dart`, which imports all of the messages files:
```
flutter pub pub run intl_translation:generate_from_arb --output-dir=lib/i18n \
--generated-file-prefix=stock_ --no-use-deferred-loading lib/*.dart lib/i18n/stocks_*.arb
```
The `StockStrings` class uses the generated `initializeMessages()`
function (`stock_messages_all.dart`) to load the localized messages
and `Intl.message()` to look them up.
......@@ -11,7 +11,8 @@ import 'package:intl/src/intl_helpers.dart';
import 'stock_messages_en.dart' as messages_en;
import 'stock_messages_es.dart' as messages_es;
Map<String, Function> _deferredLibraries = {
typedef Future<dynamic> LibraryLoader();
Map<String, LibraryLoader> _deferredLibraries = {
'en': () => new Future.value(null),
'es': () => new Future.value(null),
};
......
......@@ -13,9 +13,7 @@ import 'package:flutter/rendering.dart' show
debugPaintLayerBordersEnabled,
debugPaintPointersEnabled,
debugRepaintRainbowEnabled;
import 'package:intl/intl.dart';
import 'i18n/stock_messages_all.dart';
import 'stock_data.dart';
import 'stock_home.dart';
import 'stock_settings.dart';
......@@ -23,6 +21,14 @@ import 'stock_strings.dart';
import 'stock_symbol_viewer.dart';
import 'stock_types.dart';
class _StocksLocalizationsDelegate extends LocalizationsDelegate<StockStrings> {
@override
Future<StockStrings> load(Locale locale) => StockStrings.load(locale);
@override
bool shouldReload(_StocksLocalizationsDelegate old) => false;
}
class StocksApp extends StatefulWidget {
@override
StocksAppState createState() => new StocksAppState();
......@@ -99,13 +105,6 @@ class StocksAppState extends State<StocksApp> {
return null;
}
Future<LocaleQueryData> _onLocaleChanged(Locale locale) async {
final String localeString = locale.toString();
await initializeMessages(localeString);
Intl.defaultLocale = localeString;
return StockStrings.instance;
}
@override
Widget build(BuildContext context) {
assert(() {
......@@ -119,6 +118,9 @@ class StocksAppState extends State<StocksApp> {
return new MaterialApp(
title: 'Stocks',
theme: theme,
localizationsDelegates: <_StocksLocalizationsDelegate>[
new _StocksLocalizationsDelegate(),
],
debugShowMaterialGrid: _configuration.debugShowGrid,
showPerformanceOverlay: _configuration.showPerformanceOverlay,
showSemanticsDebugger: _configuration.showSemanticsDebugger,
......@@ -127,7 +129,6 @@ class StocksAppState extends State<StocksApp> {
'/settings': (BuildContext context) => new StockSettings(_configuration, configurationUpdater)
},
onGenerateRoute: _getRoute,
onLocaleChanged: _onLocaleChanged
);
}
}
......
......@@ -2,39 +2,52 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'package:intl/intl.dart';
import 'package:flutter/widgets.dart';
// Wrappers for strings that are shown in the UI. The strings can be
// translated for different locales using the Dart intl package.
//
// Locale-specific values for the strings live in the i18n/*.arb files.
//
// To generate the stock_messages_*.dart files from the ARB files, run:
// pub run intl:generate_from_arb --output-dir=lib/i18n --generated-file-prefix=stock_ --no-use-deferred-loading lib/stock_strings.dart lib/i18n/stocks_*.arb
import 'i18n/stock_messages_all.dart';
class StockStrings extends LocaleQueryData {
static StockStrings of(BuildContext context) {
return LocaleQuery.of(context);
// Information about how this file relates to i18n/stock_messages_all.dart and how the i18n files
// were generated can be found in i18n/regenerate.md.
class StockStrings {
StockStrings(Locale locale) : _localeName = locale.toString();
final String _localeName;
static Future<StockStrings> load(Locale locale) {
return initializeMessages(locale.toString())
.then((Null _) {
return new StockStrings(locale);
});
}
static final StockStrings instance = new StockStrings();
static StockStrings of(BuildContext context) {
return Localizations.of<StockStrings>(context, StockStrings);
}
String title() => Intl.message(
'Stocks',
name: 'title',
desc: 'Title for the Stocks application'
);
String title() {
return Intl.message(
'<Stocks>',
name: 'title',
desc: 'Title for the Stocks application',
locale: _localeName,
);
}
String market() => Intl.message(
'MARKET',
name: 'market',
desc: 'Label for the Market tab'
desc: 'Label for the Market tab',
locale: _localeName,
);
String portfolio() => Intl.message(
'PORTFOLIO',
name: 'portfolio',
desc: 'Label for the Portfolio tab'
desc: 'Label for the Portfolio tab',
locale: _localeName,
);
}
......@@ -13,9 +13,19 @@ void main() {
stocks.main();
await tester.idle(); // see https://github.com/flutter/flutter/issues/1865
await tester.pump();
// The initial test app's locale is "_", so we're seeing the fallback translation here.
expect(find.text('MARKET'), findsOneWidget);
await tester.binding.setLocale('es', 'US');
await tester.idle();
// The Localizations widget has been built with the new locale. The
// new locale's strings are loaded asynchronously, so we're still
// displaying the previous locale's strings.
await tester.pump();
expect(find.text('MARKET'), findsOneWidget);
// The localized strings have finished loading and dependent
// widgets have been updated.
await tester.pump();
expect(find.text('MERCADO'), findsOneWidget);
});
......
......@@ -57,6 +57,7 @@ export 'src/material/ink_well.dart';
export 'src/material/input_decorator.dart';
export 'src/material/list_tile.dart';
export 'src/material/material.dart';
export 'src/material/material_localizations.dart';
export 'src/material/mergeable_material.dart';
export 'src/material/page.dart';
export 'src/material/paginated_data_table.dart';
......
......@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'package:flutter/rendering.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
......@@ -10,11 +12,10 @@ import 'arc.dart';
import 'colors.dart';
import 'floating_action_button.dart';
import 'icons.dart';
import 'material_localizations.dart';
import 'page.dart';
import 'theme.dart';
export 'dart:ui' show Locale;
const TextStyle _errorTextStyle = const TextStyle(
color: const Color(0xD0FF0000),
fontFamily: 'monospace',
......@@ -25,6 +26,16 @@ const TextStyle _errorTextStyle = const TextStyle(
decorationStyle: TextDecorationStyle.double
);
class _MaterialLocalizationsDelegate extends LocalizationsDelegate<MaterialLocalizations> {
const _MaterialLocalizationsDelegate();
@override
Future<MaterialLocalizations> load(Locale locale) => MaterialLocalizations.load(locale);
@override
bool shouldReload(_MaterialLocalizationsDelegate old) => false;
}
/// An application that uses material design.
///
/// A convenience widget that wraps a number of widgets that are commonly
......@@ -80,7 +91,8 @@ class MaterialApp extends StatefulWidget {
this.initialRoute,
this.onGenerateRoute,
this.onUnknownRoute,
this.onLocaleChanged,
this.locale,
this.localizationsDelegates,
this.navigatorObservers: const <NavigatorObserver>[],
this.debugShowMaterialGrid: false,
this.showPerformanceOverlay: false,
......@@ -129,10 +141,9 @@ class MaterialApp extends StatefulWidget {
/// normally, unless [initialRoute] is specified. It's also the route that's
/// displayed if the [initialRoute] can't be displayed.
///
/// To be able to directly call [Theme.of], [MediaQuery.of],
/// [LocaleQuery.of], etc, in the code sets the [home] argument in
/// the constructor, you can use a [Builder] widget to get a
/// [BuildContext].
/// To be able to directly call [Theme.of], [MediaQuery.of], etc, in the code
/// that sets the [home] argument in the constructor, you can use a [Builder]
/// widget to get a [BuildContext].
///
/// If [home] is specified, then [routes] must not include an entry for `/`,
/// as [home] takes its place.
......@@ -210,9 +221,16 @@ class MaterialApp extends StatefulWidget {
/// message.
final RouteFactory onUnknownRoute;
/// Callback that is called when the operating system changes the
/// current locale.
final LocaleChangedCallback onLocaleChanged;
/// The initial locale for this app's [Localizations] widget.
///
/// If the `locale` is null the system's locale value is used.
final Locale locale;
/// The delegates for this app's [Localizations] widget.
///
/// The delegates collectively define all of the localized resources
/// for this application's [Localizations] widget.
final Iterable<LocalizationsDelegate<dynamic>> localizationsDelegates;
/// Turns on a performance overlay.
///
......@@ -297,6 +315,14 @@ class _MaterialAppState extends State<MaterialApp> {
_heroController = new HeroController(createRectTween: _createRectTween);
}
// Combine the Localizations for Material with the ones contributed
// by the localizationsDelegates parameter, if any.
Iterable<LocalizationsDelegate<dynamic>> _createLocalizationsDelegates() sync* {
yield const _MaterialLocalizationsDelegate();
if (widget.localizationsDelegates != null)
yield* widget.localizationsDelegates;
}
RectTween _createRectTween(Rect begin, Rect end) {
return new MaterialRectArcTween(begin: begin, end: end);
}
......@@ -319,6 +345,7 @@ class _MaterialAppState extends State<MaterialApp> {
return null;
}
Route<dynamic> _onUnknownRoute(RouteSettings settings) {
assert(() {
if (widget.onUnknownRoute == null) {
......@@ -369,7 +396,8 @@ class _MaterialAppState extends State<MaterialApp> {
initialRoute: widget.initialRoute,
onGenerateRoute: _onGenerateRoute,
onUnknownRoute: _onUnknownRoute,
onLocaleChanged: widget.onLocaleChanged,
locale: widget.locale,
localizationsDelegates: _createLocalizationsDelegates(),
showPerformanceOverlay: widget.showPerformanceOverlay,
checkerboardRasterCacheImages: widget.checkerboardRasterCacheImages,
checkerboardOffscreenLayers: widget.checkerboardOffscreenLayers,
......
......@@ -15,6 +15,7 @@ import 'flexible_space_bar.dart';
import 'icon_button.dart';
import 'icons.dart';
import 'material.dart';
import 'material_localizations.dart';
import 'page.dart';
import 'scaffold.dart';
import 'tabs.dart';
......@@ -352,7 +353,7 @@ class _AppBarState extends State<AppBar> {
leading = new IconButton(
icon: const Icon(Icons.menu),
onPressed: _handleDrawerButton,
tooltip: 'Open navigation menu' // TODO(ianh): Figure out how to localize this string
tooltip: MaterialLocalizations.of(context).openAppDrawerTooltip,
);
} else {
if (canPop)
......
......@@ -6,6 +6,7 @@ import 'package:flutter/widgets.dart';
import 'icon_button.dart';
import 'icons.dart';
import 'material_localizations.dart';
import 'theme.dart';
/// A "back" icon that's appropriate for the current [TargetPlatform].
......@@ -83,7 +84,7 @@ class BackButton extends StatelessWidget {
return new IconButton(
icon: const BackButtonIcon(),
color: color,
tooltip: 'Back', // TODO(ianh): Figure out how to localize this string
tooltip: MaterialLocalizations.of(context).backButtonTooltip,
onPressed: () {
Navigator.of(context).maybePop();
}
......@@ -115,7 +116,7 @@ class CloseButton extends StatelessWidget {
Widget build(BuildContext context) {
return new IconButton(
icon: const Icon(Icons.close),
tooltip: 'Close', // TODO(ianh): Figure out how to localize this string
tooltip: MaterialLocalizations.of(context).closeButtonTooltip,
onPressed: () {
Navigator.of(context).maybePop();
},
......
......@@ -23,6 +23,7 @@ import 'icon_button.dart';
import 'icons.dart';
import 'ink_well.dart';
import 'material.dart';
import 'material_localizations.dart';
import 'theme.dart';
import 'typography.dart';
......@@ -548,7 +549,7 @@ class _MonthPickerState extends State<MonthPicker> {
left: 8.0,
child: new IconButton(
icon: const Icon(Icons.chevron_left),
tooltip: 'Previous month',
tooltip: MaterialLocalizations.of(context).previousMonthTooltip,
onPressed: _isDisplayingFirstMonth ? null : _handlePreviousMonth,
),
),
......@@ -557,7 +558,7 @@ class _MonthPickerState extends State<MonthPicker> {
right: 8.0,
child: new IconButton(
icon: const Icon(Icons.chevron_right),
tooltip: 'Next month',
tooltip: MaterialLocalizations.of(context).nextMonthTooltip,
onPressed: _isDisplayingLastMonth ? null : _handleNextMonth,
),
),
......
// 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 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
/// Default localized resource values for the material widgets.
///
/// This class is just a placeholder, it only provides English values.
class MaterialLocalizations {
const MaterialLocalizations._(this.locale) : assert(locale != null);
/// The locale for which the values of this class's localized resources
/// have been translated.
final Locale locale;
/// Creates an object that provides default localized resource values for the
/// for the widgets of the material library.
///
/// This method is typically used to create a [DefaultLocalizationsDelegate].
/// The [MaterialApp] does so by default.
static Future<MaterialLocalizations> load(Locale locale) {
return new SynchronousFuture<MaterialLocalizations>(new MaterialLocalizations._(locale));
}
/// The `MaterialLocalizations` from the closest [Localizations] instance
/// that encloses the given context.
///
/// This method is just a convenient shorthand for:
/// `Localizations.of<MaterialLocalizations>(context, MaterialLocalizations)`.
///
/// References to the localized resources defined by this class are typically
/// written in terms of this method. For example:
/// ```dart
/// tooltip: MaterialLocalizations.of(context).backButtonTooltip,
/// ```
static MaterialLocalizations of(BuildContext context) {
return Localizations.of<MaterialLocalizations>(context, MaterialLocalizations);
}
/// The tooltip for the leading [AppBar] menu (aka 'hamburger') button
String get openAppDrawerTooltip => 'Open navigation menu';
/// The [BackButton]'s tooltip.
String get backButtonTooltip => 'Back';
/// The [CloseButton]'s tooltip.
String get closeButtonTooltip => 'Close';
/// The tooltip for the [MonthPicker]'s "next month" button.
String get nextMonthTooltip => 'Next month';
/// The tooltip for the [MonthPicker]'s "previous month" button.
String get previousMonthTooltip => 'Previous month';
}
......@@ -11,9 +11,8 @@ import 'package:flutter/rendering.dart';
import 'banner.dart';
import 'basic.dart';
import 'binding.dart';
import 'container.dart';
import 'framework.dart';
import 'locale_query.dart';
import 'localizations.dart';
import 'media_query.dart';
import 'navigator.dart';
import 'performance_overlay.dart';
......@@ -22,10 +21,7 @@ import 'text.dart';
import 'title.dart';
import 'widget_inspector.dart';
/// Signature for a function that is called when the operating system changes the current locale.
///
/// Used by [WidgetsApp.onLocaleChanged].
typedef Future<LocaleQueryData> LocaleChangedCallback(Locale locale);
export 'dart:ui' show Locale;
/// A convenience class that wraps a number of widgets that are commonly
/// required for an application.
......@@ -34,7 +30,7 @@ typedef Future<LocaleQueryData> LocaleChangedCallback(Locale locale);
/// back button to popping the [Navigator] or quitting the application.
///
/// See also: [CheckedModeBanner], [DefaultTextStyle], [MediaQuery],
/// [LocaleQuery], [Title], [Navigator], [Overlay], [SemanticsDebugger] (the
/// [Localizations], [Title], [Navigator], [Overlay], [SemanticsDebugger] (the
/// widgets wrapped by this one).
///
/// The [onGenerateRoute] argument is required, and corresponds to
......@@ -54,7 +50,8 @@ class WidgetsApp extends StatefulWidget {
@required this.color,
this.navigatorObservers: const <NavigatorObserver>[],
this.initialRoute,
this.onLocaleChanged,
this.locale,
this.localizationsDelegates,
this.showPerformanceOverlay: false,
this.checkerboardRasterCacheImages: false,
this.checkerboardOffscreenLayers: false,
......@@ -130,9 +127,16 @@ class WidgetsApp extends StatefulWidget {
/// * [Navigator.pop], for removing a route from the stack.
final String initialRoute;
/// Callback that is called when the operating system changes the
/// current locale.
final LocaleChangedCallback onLocaleChanged;
/// The initial locale for this app's [Localizations] widget.
///
/// If the 'locale' is null the system's locale value is used.
final Locale locale;
/// The delegates for this app's [Localizations] widget.
///
/// The delegates collectively define all of the localized resources
/// for this application's [Localizations] widget.
final Iterable<LocalizationsDelegate<dynamic>> localizationsDelegates;
/// Turns on a performance overlay.
/// https://flutter.io/debugging/#performanceoverlay
......@@ -214,13 +218,13 @@ class WidgetsApp extends StatefulWidget {
class _WidgetsAppState extends State<WidgetsApp> implements WidgetsBindingObserver {
GlobalObjectKey<NavigatorState> _navigator;
LocaleQueryData _localeData;
Locale _locale;
@override
void initState() {
super.initState();
_navigator = new GlobalObjectKey<NavigatorState>(this);
didChangeLocale(ui.window.locale);
_locale = ui.window.locale;
WidgetsBinding.instance.addObserver(this);
}
......@@ -258,10 +262,9 @@ class _WidgetsAppState extends State<WidgetsApp> implements WidgetsBindingObserv
@override
void didChangeLocale(Locale locale) {
if (widget.onLocaleChanged != null) {
widget.onLocaleChanged(locale).then<Null>((LocaleQueryData data) {
if (mounted)
setState(() { _localeData = data; });
if (locale != _locale) {
setState(() {
_locale = locale;
});
}
}
......@@ -274,19 +277,11 @@ class _WidgetsAppState extends State<WidgetsApp> implements WidgetsBindingObserv
@override
Widget build(BuildContext context) {
if (widget.onLocaleChanged != null && _localeData == null) {
// If the app expects a locale but we don't yet know the locale, then
// don't build the widgets now.
// TODO(ianh): Make this unnecessary. See https://github.com/flutter/flutter/issues/1865
// TODO(ianh): The following line should not be included in release mode, only in profile and debug modes.
WidgetsBinding.instance.preventThisFrameFromBeingReportedAsFirstFrame();
return new Container();
}
Widget result = new MediaQuery(
data: new MediaQueryData.fromWindow(ui.window),
child: new LocaleQuery(
data: _localeData,
child: new Localizations(
locale: widget.locale ?? _locale,
delegates: widget.localizationsDelegates,
child: new Title(
title: widget.title,
color: widget.color,
......
......@@ -389,19 +389,35 @@ abstract class WidgetsBinding extends BindingBase with GestureBinding, RendererB
}
bool _needToReportFirstFrame = true;
bool _thisFrameWasUseful = true;
int _deferFirstFrameReportCount = 0;
bool get _reportFirstFrame => _deferFirstFrameReportCount == 0;
/// Tell the framework that the frame we are currently building
/// should not be considered to be a useful first frame.
/// Tell the framework not to report the frame it is building as a "useful"
/// first frame until there is a corresponding call to [allowFirstFrameReport].
///
/// This is used by [WidgetsApp] to report the first frame.
//
// TODO(ianh): This method should only be available in debug and profile modes.
void preventThisFrameFromBeingReportedAsFirstFrame() {
_thisFrameWasUseful = false;
void deferFirstFrameReport() {
assert(_deferFirstFrameReportCount >= 0);
_deferFirstFrameReportCount += 1;
}
void _handleBuildScheduled() {
/// When called after [deferFirstFrameReport]: tell the framework to report
/// the frame it is building as a "useful" first frame.
///
/// This method may only be called once for each corresponding call
/// to [deferFirstFrameReport].
///
/// This is used by [WidgetsApp] to report the first frame.
//
// TODO(ianh): This method should only be available in debug and profile modes.
void allowFirstFrameReport() {
assert(_deferFirstFrameReportCount >= 1);
_deferFirstFrameReportCount -= 1;
}
void _handleBuildScheduled() {
// If we're in the process of building dirty elements, then changes
// should not trigger a new frame.
assert(() {
......@@ -522,14 +538,10 @@ abstract class WidgetsBinding extends BindingBase with GestureBinding, RendererB
}
// TODO(ianh): Following code should not be included in release mode, only profile and debug modes.
// See https://github.com/dart-lang/sdk/issues/27192
if (_needToReportFirstFrame) {
if (_thisFrameWasUseful) {
developer.Timeline.instantSync('Widgets completed first useful frame');
developer.postEvent('Flutter.FirstFrame', <String, dynamic>{});
_needToReportFirstFrame = false;
} else {
_thisFrameWasUseful = true;
}
if (_needToReportFirstFrame && _reportFirstFrame) {
developer.Timeline.instantSync('Widgets completed first useful frame');
developer.postEvent('Flutter.FirstFrame', <String, dynamic>{});
_needToReportFirstFrame = false;
}
}
......@@ -556,11 +568,15 @@ abstract class WidgetsBinding extends BindingBase with GestureBinding, RendererB
@override
Future<Null> performReassemble() {
_needToReportFirstFrame = true;
preventThisFrameFromBeingReportedAsFirstFrame();
deferFirstFrameReport();
if (renderViewElement != null)
buildOwner.reassemble(renderViewElement);
return super.performReassemble();
// TODO(hansmuller): eliminate the value variable after analyzer bug
// https://github.com/flutter/flutter/issues/11646 is fixed.
final Future<Null> value = super.performReassemble();
return value.then((Null _) {
allowFirstFrameReport();
});
}
}
......
// Copyright 2015 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 'framework.dart';
/// Superclass for locale-specific data provided by the application.
class LocaleQueryData { } // TODO(ianh): We need a better type here. This doesn't really make sense.
/// Establishes a subtree in which locale queries resolve to the given data.
class LocaleQuery extends InheritedWidget {
/// Creates a widget that provides [LocaleQueryData] to its descendants.
const LocaleQuery({
Key key,
@required this.data,
@required Widget child
}) : assert(child != null),
super(key: key, child: child);
/// The locale data for this subtree.
final LocaleQueryData data;
/// The data from the closest instance of this class that encloses the given context.
///
/// Typical usage is as follows:
///
/// ```dart
/// MyLocaleData data = LocaleQueryData.of(context);
/// ```
static LocaleQueryData of(BuildContext context) {
final LocaleQuery query = context.inheritFromWidgetOfExactType(LocaleQuery);
return query?.data;
}
@override
bool updateShouldNotify(LocaleQuery old) => data != old.data;
@override
void debugFillProperties(DiagnosticPropertiesBuilder description) {
super.debugFillProperties(description);
description.add(new DiagnosticsProperty<LocaleQueryData>('data', data, showName: false));
}
}
This diff is collapsed.
......@@ -45,7 +45,7 @@ export 'src/widgets/image.dart';
export 'src/widgets/image_icon.dart';
export 'src/widgets/implicit_animations.dart';
export 'src/widgets/layout_builder.dart';
export 'src/widgets/locale_query.dart';
export 'src/widgets/localizations.dart';
export 'src/widgets/media_query.dart';
export 'src/widgets/modal_barrier.dart';
export 'src/widgets/navigation_toolbar.dart';
......
......@@ -28,94 +28,88 @@ void main() {
DateTime _selectedDate = new DateTime(2016, DateTime.JULY, 26);
await tester.pumpWidget(
new Overlay(
initialEntries: <OverlayEntry>[
new OverlayEntry(
builder: (BuildContext context) => new StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return new Positioned(
width: 400.0,
child: new SingleChildScrollView(
child: new Material(
child: new MonthPicker(
firstDate: new DateTime(0),
lastDate: new DateTime(9999),
key: _datePickerKey,
selectedDate: _selectedDate,
onChanged: (DateTime value) {
setState(() {
_selectedDate = value;
});
},
),
),
new MaterialApp(
home: new StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return new Container(
width: 400.0,
child: new SingleChildScrollView(
child: new Material(
child: new MonthPicker(
firstDate: new DateTime(0),
lastDate: new DateTime(9999),
key: _datePickerKey,
selectedDate: _selectedDate,
onChanged: (DateTime value) {
setState(() {
_selectedDate = value;
});
},
),
);
},
),
),
],
),
),
),
);
},
),
)
);
expect(_selectedDate, equals(new DateTime(2016, DateTime.JULY, 26)));
await tester.tapAt(const Offset(50.0, 100.0));
expect(_selectedDate, equals(new DateTime(2016, DateTime.JULY, 26)));
await tester.pumpAndSettle();
await tester.pump(const Duration(seconds: 2));
await tester.tapAt(const Offset(300.0, 100.0));
await tester.tap(find.text('1'));
await tester.pumpAndSettle();
expect(_selectedDate, equals(new DateTime(2016, DateTime.JULY, 1)));
await tester.pump(const Duration(seconds: 2));
await tester.tapAt(const Offset(380.0, 20.0));
await tester.pumpAndSettle(const Duration(milliseconds: 100));
await tester.tap(find.byTooltip('Next month'));
await tester.pumpAndSettle();
expect(_selectedDate, equals(new DateTime(2016, DateTime.JULY, 1)));
await tester.tapAt(const Offset(300.0, 100.0));
await tester.tap(find.text('5'));
await tester.pumpAndSettle();
expect(_selectedDate, equals(new DateTime(2016, DateTime.AUGUST, 5)));
await tester.pump(const Duration(seconds: 2));
await tester.drag(find.byKey(_datePickerKey), const Offset(-300.0, 0.0));
await tester.pumpAndSettle(const Duration(milliseconds: 100));
await tester.drag(find.byKey(_datePickerKey), const Offset(-400.0, 0.0));
await tester.pumpAndSettle();
expect(_selectedDate, equals(new DateTime(2016, DateTime.AUGUST, 5)));
await tester.tapAt(const Offset(45.0, 270.0));
await tester.tap(find.text('25'));
await tester.pumpAndSettle();
expect(_selectedDate, equals(new DateTime(2016, DateTime.SEPTEMBER, 25)));
await tester.pump(const Duration(seconds: 2));
await tester.drag(find.byKey(_datePickerKey), const Offset(300.0, 0.0));
await tester.pumpAndSettle(const Duration(milliseconds: 100));
await tester.drag(find.byKey(_datePickerKey), const Offset(800.0, 0.0));
await tester.pumpAndSettle();
expect(_selectedDate, equals(new DateTime(2016, DateTime.SEPTEMBER, 25)));
await tester.tapAt(const Offset(210.0, 180.0));
await tester.tap(find.text('17'));
await tester.pumpAndSettle();
expect(_selectedDate, equals(new DateTime(2016, DateTime.AUGUST, 17)));
});
testWidgets('render picker with intrinsic dimensions', (WidgetTester tester) async {
await tester.pumpWidget(
new Overlay(
initialEntries: <OverlayEntry>[
new OverlayEntry(
builder: (BuildContext context) => new StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return new IntrinsicWidth(
child: new IntrinsicHeight(
child: new Material(
child: new SingleChildScrollView(
child: new MonthPicker(
firstDate: new DateTime(0),
lastDate: new DateTime(9999),
onChanged: (DateTime value) { },
selectedDate: new DateTime(2000, DateTime.JANUARY, 1),
),
),
new MaterialApp(
home: new StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return new IntrinsicWidth(
child: new IntrinsicHeight(
child: new Material(
child: new SingleChildScrollView(
child: new MonthPicker(
firstDate: new DateTime(0),
lastDate: new DateTime(9999),
onChanged: (DateTime value) { },
selectedDate: new DateTime(2000, DateTime.JANUARY, 1),
),
),
);
},
),
),
],
),
),
);
},
),
),
);
await tester.pump(const Duration(seconds: 5));
......
// 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';
class TestLocaleQueryData extends LocaleQueryData {
@override
String toString() => 'Test data';
}
void main() {
testWidgets('LocaleQuery control test', (WidgetTester tester) async {
await tester.pumpWidget(new Container());
expect(LocaleQuery.of(tester.element(find.byType(Container))), isNull);
final LocaleQueryData data = new TestLocaleQueryData();
final Widget widget = new LocaleQuery(
data: data,
child: new Container(),
);
expect(widget, hasOneLineDescription);
expect(widget.toString(), contains('Test data'));
await tester.pumpWidget(widget);
expect(LocaleQuery.of(tester.element(find.byType(Container))), equals(data));
});
}
This diff is collapsed.
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