Commit f235a2c1 authored by Ian Hickson's avatar Ian Hickson Committed by GitHub

RTL: Padding, Flex (#11709)

* Introduce a Directionality inherited widget which sets the ambient LTR vs RTL mode (defaulting to null, which means you cannot use directionality-influenced values).

* Make it possible to configure Padding (including Container.padding and Container.margin) using a directionality-agnostic EdgeInsets variant.

* Provide textDirection and verticalDirection controls on Row and Column to make them RTL-aware.

* Introduce a variant of FractionalOffset based on the EdgeInsets variant. Not yet actually used.

* Fix all the tests that depended on Row defaulting to LTR.
parent 3dd1a05a
......@@ -9,7 +9,10 @@ import '../widgets/gestures.dart';
void main() {
testWidgets('Tap on center change color', (WidgetTester tester) async {
await tester.pumpWidget(new GestureDemo());
await tester.pumpWidget(new Directionality(
textDirection: TextDirection.ltr,
child: new GestureDemo(),
));
final Finder finder = find.byType(GestureDemo);
MaterialColor getSwatch() => tester.state<GestureDemoState>(finder).swatch;
......
......@@ -26,11 +26,14 @@ const TextStyle _errorTextStyle = const TextStyle(
decorationStyle: TextDecorationStyle.double
);
// Delegate that fetches the default (English) strings.
class _MaterialLocalizationsDelegate extends LocalizationsDelegate<MaterialLocalizations> {
const _MaterialLocalizationsDelegate();
@override
Future<MaterialLocalizations> load(Locale locale) => MaterialLocalizations.load(locale);
Future<MaterialLocalizations> load(Locale locale) {
return new SynchronousFuture<MaterialLocalizations>(const MaterialLocalizations());
}
@override
bool shouldReload(_MaterialLocalizationsDelegate old) => false;
......@@ -317,8 +320,8 @@ class _MaterialAppState extends State<MaterialApp> {
// Combine the Localizations for Material with the ones contributed
// by the localizationsDelegates parameter, if any.
Iterable<LocalizationsDelegate<dynamic>> _createLocalizationsDelegates() sync* {
yield const _MaterialLocalizationsDelegate();
Iterable<LocalizationsDelegate<dynamic>> get _localizationsDelegates sync* {
yield const _MaterialLocalizationsDelegate(); // TODO(ianh): make this configurable
if (widget.localizationsDelegates != null)
yield* widget.localizationsDelegates;
}
......@@ -397,7 +400,7 @@ class _MaterialAppState extends State<MaterialApp> {
onGenerateRoute: _onGenerateRoute,
onUnknownRoute: _onUnknownRoute,
locale: widget.locale,
localizationsDelegates: _createLocalizationsDelegates(),
localizationsDelegates: _localizationsDelegates,
showPerformanceOverlay: widget.showPerformanceOverlay,
checkerboardRasterCacheImages: widget.checkerboardRasterCacheImages,
checkerboardOffscreenLayers: widget.checkerboardOffscreenLayers,
......
......@@ -106,16 +106,16 @@ class Chip extends StatelessWidget {
Widget build(BuildContext context) {
assert(debugCheckHasMaterial(context));
final bool deletable = onDeleted != null;
double leftPadding = 12.0;
double rightPadding = 12.0;
double startPadding = 12.0;
double endPadding = 12.0;
final List<Widget> children = <Widget>[];
if (avatar != null) {
leftPadding = 0.0;
startPadding = 0.0;
children.add(new ExcludeSemantics(
child: new Container(
margin: const EdgeInsets.only(right: 8.0),
margin: const EdgeInsetsDirectional.only(end: 8.0),
width: _kAvatarDiamater,
height: _kAvatarDiamater,
child: avatar,
......@@ -131,7 +131,7 @@ class Chip extends StatelessWidget {
));
if (deletable) {
rightPadding = 0.0;
endPadding = 0.0;
children.add(new GestureDetector(
onTap: Feedback.wrapForTap(onDeleted, context),
child: new Tooltip(
......@@ -152,7 +152,7 @@ class Chip extends StatelessWidget {
container: true,
child: new Container(
height: _kChipHeight,
padding: new EdgeInsets.only(left: leftPadding, right: rightPadding),
padding: new EdgeInsetsDirectional.only(start: startPadding, end: endPadding),
decoration: new BoxDecoration(
color: backgroundColor ?? Colors.grey.shade300,
borderRadius: new BorderRadius.circular(16.0),
......
......@@ -2,29 +2,35 @@
// 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.
/// Interface for localized resource values for the material widgets.
///
/// This class is just a placeholder, it only provides English values.
/// This class provides a default placeholder implementation that returns
/// hard-coded American English values.
class MaterialLocalizations {
const MaterialLocalizations._(this.locale) : assert(locale != null);
/// Create a placeholder object for the localized resources of material widgets
/// which only provides American English strings.
const MaterialLocalizations();
/// The locale for which the values of this class's localized resources
/// have been translated.
final Locale locale;
Locale get locale => const Locale('en', 'US');
/// 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 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';
/// The `MaterialLocalizations` from the closest [Localizations] instance
/// that encloses the given context.
......@@ -34,25 +40,11 @@ class 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';
}
......@@ -150,6 +150,8 @@ class _TabLabelBarRenderer extends RenderFlex {
MainAxisSize mainAxisSize,
MainAxisAlignment mainAxisAlignment,
CrossAxisAlignment crossAxisAlignment,
TextDirection textDirection,
VerticalDirection verticalDirection,
TextBaseline textBaseline,
@required this.onPerformLayout,
}) : assert(onPerformLayout != null),
......@@ -159,6 +161,8 @@ class _TabLabelBarRenderer extends RenderFlex {
mainAxisSize: mainAxisSize,
mainAxisAlignment: mainAxisAlignment,
crossAxisAlignment: crossAxisAlignment,
textDirection: textDirection,
verticalDirection: verticalDirection,
textBaseline: textBaseline,
);
......@@ -188,6 +192,8 @@ class _TabLabelBar extends Flex {
Key key,
MainAxisAlignment mainAxisAlignment,
CrossAxisAlignment crossAxisAlignment,
TextDirection textDirection,
VerticalDirection verticalDirection: VerticalDirection.down,
List<Widget> children: const <Widget>[],
this.onPerformLayout,
}) : super(
......@@ -197,6 +203,8 @@ class _TabLabelBar extends Flex {
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
textDirection: textDirection,
verticalDirection: verticalDirection,
);
final ValueChanged<List<double>> onPerformLayout;
......@@ -208,6 +216,8 @@ class _TabLabelBar extends Flex {
mainAxisAlignment: mainAxisAlignment,
mainAxisSize: mainAxisSize,
crossAxisAlignment: crossAxisAlignment,
textDirection: getEffectiveTextDirection(context),
verticalDirection: verticalDirection,
textBaseline: textBaseline,
onPerformLayout: onPerformLayout,
);
......
......@@ -90,3 +90,70 @@ enum RenderComparison {
/// change in a render object.
layout,
}
/// The two cardinal directions in two dimensions.
///
/// The axis is always relative to the current coordinate space. This means, for
/// example, that a [horizontal] axis might actually be diagonally from top
/// right to bottom left, due to some local [Transform] applied to the scene.
///
/// See also:
///
/// * [AxisDirection], which is a directional version of this enum (with values
/// light left and right, rather than just horizontal).
/// * [TextDirection], which disambiguates between left-to-right horizontal
/// content and right-to-left horizontal content.
enum Axis {
/// Left and right.
///
/// See also:
///
/// * [TextDirection], which disambiguates between left-to-right horizontal
/// content and right-to-left horizontal content.
horizontal,
/// Up and down.
vertical,
}
/// Returns the opposite of the given [Axis].
///
/// Specifically, returns [Axis.horizontal] for [Axis.vertical], and
/// vice versa.
///
/// See also:
///
/// * [flipAxisDirection], which does the same thing for [AxisDirection] values.
Axis flipAxis(Axis direction) {
assert(direction != null);
switch (direction) {
case Axis.horizontal:
return Axis.vertical;
case Axis.vertical:
return Axis.horizontal;
}
return null;
}
/// A direction in which boxes flow vertically.
///
/// This is used by the flex algorithm (e.g. [Column]) to decide in which
/// direction to draw boxes.
///
/// This is also used to disambiguate `start` and `end` values (e.g.
/// [MainAxisAlignment.start] or [CrossAxisAlignment.end]).
///
/// See also:
///
/// * [TextDirection], which controls the same thing but horizontally.
enum VerticalDirection {
/// Boxes should start at the bottom and be stacked vertically towards the top.
///
/// The "start" is at the bottom, the "end" is at the top.
up,
/// Boxes should start at the top and be stacked vertically towards the bottom.
///
/// The "start" is at the top, the "end" is at the bottom.
down,
}
......@@ -2149,6 +2149,11 @@ abstract class RenderBoxContainerDefaultsMixin<ChildType extends RenderBox, Pare
///
/// Stops walking once after the first child reports that it contains the
/// given point. Returns whether any children contain the given point.
///
/// See also:
///
/// * [defaultPaint], which paints the children appropriate for this
/// hit-testing strategy.
bool defaultHitTestChildren(HitTestResult result, { Offset position }) {
// the x, y parameters have the top left of the node's box as the origin
ChildType child = lastChild;
......@@ -2162,6 +2167,11 @@ abstract class RenderBoxContainerDefaultsMixin<ChildType extends RenderBox, Pare
}
/// Paints each child by walking the child list forwards.
///
/// See also:
///
/// * [defaultHitTestChildren], which implements hit-testing of the children
/// in a manner appropriate for this painting strategy.
void defaultPaint(PaintingContext context, Offset offset) {
ChildType child = firstChild;
while (child != null) {
......
......@@ -43,7 +43,7 @@ class AbstractNode {
int get depth => _depth;
int _depth = 0;
/// Adjust the [depth] of the given [child] to be greated than this node's own
/// Adjust the [depth] of the given [child] to be greater than this node's own
/// [depth].
///
/// Only call this method from overrides of [redepthChildren].
......
......@@ -91,29 +91,58 @@ class RenderPadding extends RenderShiftedBox {
///
/// The [padding] argument must not be null and must have non-negative insets.
RenderPadding({
@required EdgeInsets padding,
RenderBox child
@required EdgeInsetsGeometry padding,
TextDirection textDirection,
RenderBox child,
}) : assert(padding != null),
assert(padding.isNonNegative),
_textDirection = textDirection,
_padding = padding,
super(child);
super(child) {
_applyUpdate();
}
// The resolved absolute insets.
EdgeInsets _resolvedPadding;
void _applyUpdate() {
final EdgeInsets resolvedPadding = padding.resolve(textDirection);
assert(resolvedPadding.isNonNegative);
if (resolvedPadding != _resolvedPadding) {
_resolvedPadding = resolvedPadding;
markNeedsLayout();
}
}
/// The amount to pad the child in each dimension.
EdgeInsets get padding => _padding;
EdgeInsets _padding;
set padding(EdgeInsets value) {
///
/// If this is set to an [EdgeInsetsDirectional] object, then [textDirection]
/// must be non-null.
EdgeInsetsGeometry get padding => _padding;
EdgeInsetsGeometry _padding;
set padding(EdgeInsetsGeometry value) {
assert(value != null);
assert(value.isNonNegative);
if (_padding == value)
return;
_padding = value;
markNeedsLayout();
_applyUpdate();
}
/// The text direction with which to resolve [padding].
TextDirection get textDirection => _textDirection;
TextDirection _textDirection;
set textDirection(TextDirection value) {
if (_textDirection == value)
return;
_textDirection = value;
_applyUpdate();
}
@override
double computeMinIntrinsicWidth(double height) {
final double totalHorizontalPadding = padding.left + padding.right;
final double totalVerticalPadding = padding.top + padding.bottom;
final double totalHorizontalPadding = _resolvedPadding.left + _resolvedPadding.right;
final double totalVerticalPadding = _resolvedPadding.top + _resolvedPadding.bottom;
if (child != null) // next line relies on double.INFINITY absorption
return child.getMinIntrinsicWidth(math.max(0.0, height - totalVerticalPadding)) + totalHorizontalPadding;
return totalHorizontalPadding;
......@@ -121,8 +150,8 @@ class RenderPadding extends RenderShiftedBox {
@override
double computeMaxIntrinsicWidth(double height) {
final double totalHorizontalPadding = padding.left + padding.right;
final double totalVerticalPadding = padding.top + padding.bottom;
final double totalHorizontalPadding = _resolvedPadding.left + _resolvedPadding.right;
final double totalVerticalPadding = _resolvedPadding.top + _resolvedPadding.bottom;
if (child != null) // next line relies on double.INFINITY absorption
return child.getMaxIntrinsicWidth(math.max(0.0, height - totalVerticalPadding)) + totalHorizontalPadding;
return totalHorizontalPadding;
......@@ -130,8 +159,8 @@ class RenderPadding extends RenderShiftedBox {
@override
double computeMinIntrinsicHeight(double width) {
final double totalHorizontalPadding = padding.left + padding.right;
final double totalVerticalPadding = padding.top + padding.bottom;
final double totalHorizontalPadding = _resolvedPadding.left + _resolvedPadding.right;
final double totalVerticalPadding = _resolvedPadding.top + _resolvedPadding.bottom;
if (child != null) // next line relies on double.INFINITY absorption
return child.getMinIntrinsicHeight(math.max(0.0, width - totalHorizontalPadding)) + totalVerticalPadding;
return totalVerticalPadding;
......@@ -139,8 +168,8 @@ class RenderPadding extends RenderShiftedBox {
@override
double computeMaxIntrinsicHeight(double width) {
final double totalHorizontalPadding = padding.left + padding.right;
final double totalVerticalPadding = padding.top + padding.bottom;
final double totalHorizontalPadding = _resolvedPadding.left + _resolvedPadding.right;
final double totalVerticalPadding = _resolvedPadding.top + _resolvedPadding.bottom;
if (child != null) // next line relies on double.INFINITY absorption
return child.getMaxIntrinsicHeight(math.max(0.0, width - totalHorizontalPadding)) + totalVerticalPadding;
return totalVerticalPadding;
......@@ -148,21 +177,21 @@ class RenderPadding extends RenderShiftedBox {
@override
void performLayout() {
assert(padding != null);
assert(_resolvedPadding != null);
if (child == null) {
size = constraints.constrain(new Size(
padding.left + padding.right,
padding.top + padding.bottom
_resolvedPadding.left + _resolvedPadding.right,
_resolvedPadding.top + _resolvedPadding.bottom
));
return;
}
final BoxConstraints innerConstraints = constraints.deflate(padding);
final BoxConstraints innerConstraints = constraints.deflate(_resolvedPadding);
child.layout(innerConstraints, parentUsesSize: true);
final BoxParentData childParentData = child.parentData;
childParentData.offset = new Offset(padding.left, padding.top);
childParentData.offset = new Offset(_resolvedPadding.left, _resolvedPadding.top);
size = constraints.constrain(new Size(
padding.left + child.size.width + padding.right,
padding.top + child.size.height + padding.bottom
_resolvedPadding.left + child.size.width + _resolvedPadding.right,
_resolvedPadding.top + child.size.height + _resolvedPadding.bottom
));
}
......@@ -171,7 +200,7 @@ class RenderPadding extends RenderShiftedBox {
super.debugPaintSize(context, offset);
assert(() {
final Rect outerRect = offset & size;
debugPaintPadding(context.canvas, outerRect, child != null ? padding.deflateRect(outerRect) : null);
debugPaintPadding(context.canvas, outerRect, child != null ? _resolvedPadding.deflateRect(outerRect) : null);
return true;
});
}
......@@ -179,7 +208,8 @@ class RenderPadding extends RenderShiftedBox {
@override
void debugFillProperties(DiagnosticPropertiesBuilder description) {
super.debugFillProperties(description);
description.add(new DiagnosticsProperty<EdgeInsets>('padding', padding));
description.add(new DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding));
description.add(new EnumProperty<TextDirection>('textDirection', textDirection));
}
}
......
......@@ -94,6 +94,10 @@ Axis axisDirectionToAxis(AxisDirection axisDirection) {
/// Specifically, returns [AxisDirection.up] for [AxisDirection.down] (and
/// vice versa), as well as [AxisDirection.left] for [AxisDirection.right] (and
/// vice versa).
///
/// See also:
///
/// * [flipAxis], which does the same thing for [Axis] values.
AxisDirection flipAxisDirection(AxisDirection axisDirection) {
assert(axisDirection != null);
switch (axisDirection) {
......
......@@ -23,6 +23,19 @@ import 'widget_inspector.dart';
export 'dart:ui' show Locale;
// Delegate that fetches the default (English) strings.
class _WidgetsLocalizationsDelegate extends LocalizationsDelegate<WidgetsLocalizations> {
const _WidgetsLocalizationsDelegate();
@override
Future<WidgetsLocalizations> load(Locale locale) {
return new SynchronousFuture<WidgetsLocalizations>(const WidgetsLocalizations());
}
@override
bool shouldReload(_WidgetsLocalizationsDelegate old) => false;
}
/// A convenience class that wraps a number of widgets that are commonly
/// required for an application.
///
......@@ -269,6 +282,14 @@ class _WidgetsAppState extends State<WidgetsApp> implements WidgetsBindingObserv
}
}
// Combine the Localizations for Widgets with the ones contributed
// by the localizationsDelegates parameter, if any.
Iterable<LocalizationsDelegate<dynamic>> get _localizationsDelegates sync* {
yield const _WidgetsLocalizationsDelegate(); // TODO(ianh): make this configurable
if (widget.localizationsDelegates != null)
yield* widget.localizationsDelegates;
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) { }
......@@ -281,7 +302,7 @@ class _WidgetsAppState extends State<WidgetsApp> implements WidgetsBindingObserv
data: new MediaQueryData.fromWindow(ui.window),
child: new Localizations(
locale: widget.locale ?? _locale,
delegates: widget.localizationsDelegates,
delegates: _localizationsDelegates.toList(),
child: new Title(
title: widget.title,
color: widget.color,
......
This diff is collapsed.
......@@ -282,7 +282,7 @@ class Container extends StatelessWidget {
/// Empty space to inscribe inside the [decoration]. The [child], if any, is
/// placed inside this padding.
final EdgeInsets padding;
final EdgeInsetsGeometry padding;
/// The decoration to paint behind the [child].
///
......@@ -303,18 +303,18 @@ class Container extends StatelessWidget {
final BoxConstraints constraints;
/// Empty space to surround the [decoration] and [child].
final EdgeInsets margin;
final EdgeInsetsGeometry margin;
/// The transformation matrix to apply before painting the container.
final Matrix4 transform;
EdgeInsets get _paddingIncludingDecoration {
EdgeInsetsGeometry get _paddingIncludingDecoration {
if (decoration == null || decoration.padding == null)
return padding;
final EdgeInsets decorationPadding = decoration.padding;
final EdgeInsetsGeometry decorationPadding = decoration.padding;
if (padding == null)
return decorationPadding;
return padding + decorationPadding;
return padding.add(decorationPadding);
}
@override
......@@ -332,7 +332,7 @@ class Container extends StatelessWidget {
if (alignment != null)
current = new Align(alignment: alignment, child: current);
final EdgeInsets effectivePadding = _paddingIncludingDecoration;
final EdgeInsetsGeometry effectivePadding = _paddingIncludingDecoration;
if (effectivePadding != null)
current = new Padding(padding: effectivePadding, child: current);
......@@ -363,11 +363,11 @@ class Container extends StatelessWidget {
void debugFillProperties(DiagnosticPropertiesBuilder description) {
super.debugFillProperties(description);
description.add(new DiagnosticsProperty<FractionalOffset>('alignment', alignment, showName: false, defaultValue: null));
description.add(new DiagnosticsProperty<EdgeInsets>('padding', padding, defaultValue: null));
description.add(new DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding, defaultValue: null));
description.add(new DiagnosticsProperty<Decoration>('bg', decoration, defaultValue: null));
description.add(new DiagnosticsProperty<Decoration>('fg', foregroundDecoration, defaultValue: null));
description.add(new DiagnosticsProperty<BoxConstraints>('constraints', constraints, defaultValue: null));
description.add(new DiagnosticsProperty<EdgeInsets>('margin', margin, defaultValue: null));
description.add(new DiagnosticsProperty<EdgeInsetsGeometry>('margin', margin, defaultValue: null));
description.add(new ObjectFlagProperty<Matrix4>.has('transform', transform));
}
}
......@@ -7,6 +7,7 @@ import 'dart:ui' show Locale;
import 'package:flutter/foundation.dart';
import 'basic.dart';
import 'binding.dart';
import 'container.dart';
import 'framework.dart';
......@@ -90,6 +91,47 @@ abstract class LocalizationsDelegate<T> {
/// rebuilt. If it returns true then dependent widgets will be rebuilt
/// after [load] has completed.
bool shouldReload(covariant LocalizationsDelegate<T> old);
@override
String toString() => '$runtimeType';
}
/// Interface for localized resource values for the lowest levels of the Flutter
/// framework.
///
/// In particular, this maps locales to a specific [Directionality] using the
/// [textDirection] property.
///
/// This class provides a default placeholder implementation that returns
/// hard-coded American English values.
class WidgetsLocalizations {
/// Create a placeholder object for the localized resources of the lowest
/// levels of the Flutter framework which only provides values for American
/// English.
const WidgetsLocalizations();
/// The locale for which the values of this class's localized resources
/// have been translated.
Locale get locale => const Locale('en', 'US');
/// The reading direction for text in this locale.
TextDirection get textDirection => TextDirection.ltr;
/// The `WidgetsLocalizations` from the closest [Localizations] instance
/// that encloses the given context.
///
/// This method is just a convenient shorthand for:
/// `Localizations.of<WidgetsLocalizations>(context, WidgetsLocalizations)`.
///
/// References to the localized resources defined by this class are typically
/// written in terms of this method. For example:
///
/// ```dart
/// textDirection: WidgetsLocalizations.of(context).textDirection,
/// ```
static WidgetsLocalizations of(BuildContext context) {
return Localizations.of<WidgetsLocalizations>(context, WidgetsLocalizations);
}
}
class _LocalizationsScope extends InheritedWidget {
......@@ -158,7 +200,7 @@ class _LocalizationsScope extends InheritedWidget {
///
/// This class is effectively an [InheritedWidget]. If it's rebuilt with
/// a new `locale` or a different list of delegates or any of its
/// delegates' [LocalizationDelegate.shouldReload()] methods returns true,
/// delegates' [LocalizationsDelegate.shouldReload()] methods returns true,
/// then widgets that have created a dependency by calling
/// `Localizations.of(context)` will be rebuilt after the resources
/// for the new locale have been loaded.
......@@ -199,21 +241,25 @@ class _LocalizationsScope extends InheritedWidget {
/// One could choose another approach for loading localized resources and looking them up while
/// still conforming to the structure of this example.
class Localizations extends StatefulWidget {
/// Create a widget from which ambient localizations (translated strings)
/// can be obtained.
Localizations({
Key key,
@required this.locale,
this.delegates,
this.child
}) : super(key: key) {
assert(locale != null);
@required this.delegates,
this.child,
}) : assert(locale != null),
assert(delegates != null),
super(key: key) {
assert(delegates.any((LocalizationsDelegate<dynamic> delegate) => delegate is LocalizationsDelegate<WidgetsLocalizations>));
}
/// The resources returned by [Localizations.of] will be specific to this locale.
final Locale locale;
/// This list collectively defines the localized resources objects that can
/// be retrieved with [ Localizations.of].
final Iterable<LocalizationsDelegate<dynamic>> delegates;
/// be retrieved with [Localizations.of].
final List<LocalizationsDelegate<dynamic>> delegates;
/// The widget below this widget in the tree.
final Widget child;
......@@ -247,6 +293,13 @@ class Localizations extends StatefulWidget {
@override
_LocalizationsState createState() => new _LocalizationsState();
@override
void debugFillProperties(DiagnosticPropertiesBuilder description) {
super.debugFillProperties(description);
description.add(new DiagnosticsProperty<Locale>('locale', locale));
description.add(new IterableProperty<LocalizationsDelegate<dynamic>>('delegates', delegates));
}
}
class _LocalizationsState extends State<Localizations> {
......@@ -329,18 +382,29 @@ class _LocalizationsState extends State<Localizations> {
T resourcesFor<T>(Type type) {
assert(type != null);
final dynamic resources = _typeToResources[type];
final T resources = _typeToResources[type];
assert(resources.runtimeType == type);
return resources;
}
TextDirection get _textDirection {
final WidgetsLocalizations resources = _typeToResources[WidgetsLocalizations];
assert(resources != null);
return resources.textDirection;
}
@override
Widget build(BuildContext context) {
if (_locale == null)
return new Container();
return new _LocalizationsScope(
key: _localizedResourcesScopeKey,
locale: widget.locale,
locale: _locale,
localizationsState: this,
child: _locale != null ? widget.child : new Container(),
child: new Directionality(
textDirection: _textDirection,
child: widget.child,
),
);
}
}
......@@ -264,7 +264,7 @@ class ScrollController extends ChangeNotifier {
description.add('no clients');
} else if (_positions.length == 1) {
// Don't actually list the client itself, since its toString may refer to us.
description.add('one client, offset ${offset.toStringAsFixed(1)}');
description.add('one client, offset ${offset?.toStringAsFixed(1)}');
} else {
description.add('${_positions.length} clients');
}
......
......@@ -7,10 +7,19 @@ import 'package:flutter_test/flutter_test.dart';
import '../services/mocks_for_image_cache.dart';
Future<Null> pumpWidgetWithBoilerplate(WidgetTester tester, Widget widget) async {
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.ltr,
child: widget,
),
);
}
void main() {
testWidgets('Need at least 2 tabs', (WidgetTester tester) async {
try {
await tester.pumpWidget(new CupertinoTabBar(
await pumpWidgetWithBoilerplate(tester, new CupertinoTabBar(
items: <BottomNavigationBarItem>[
const BottomNavigationBarItem(
icon: const ImageIcon(const TestImageProvider(24, 24)),
......@@ -19,13 +28,14 @@ void main() {
],
));
fail('Should not be possible to create a tab bar with just one item');
} on AssertionError {
} on AssertionError catch (e) {
expect(e.toString(), contains('items.length'));
// Exception expected.
}
});
testWidgets('Active and inactive colors', (WidgetTester tester) async {
await tester.pumpWidget(new CupertinoTabBar(
await pumpWidgetWithBoilerplate(tester, new CupertinoTabBar(
items: <BottomNavigationBarItem>[
const BottomNavigationBarItem(
icon: const ImageIcon(const TestImageProvider(24, 24)),
......@@ -55,7 +65,7 @@ void main() {
});
testWidgets('Opaque background does not add blur effects', (WidgetTester tester) async {
await tester.pumpWidget(new CupertinoTabBar(
await pumpWidgetWithBoilerplate(tester, new CupertinoTabBar(
items: <BottomNavigationBarItem>[
const BottomNavigationBarItem(
icon: const ImageIcon(const TestImageProvider(24, 24)),
......@@ -70,7 +80,7 @@ void main() {
expect(find.byType(BackdropFilter), findsOneWidget);
await tester.pumpWidget(new CupertinoTabBar(
await pumpWidgetWithBoilerplate(tester, new CupertinoTabBar(
items: <BottomNavigationBarItem>[
const BottomNavigationBarItem(
icon: const ImageIcon(const TestImageProvider(24, 24)),
......@@ -90,7 +100,7 @@ void main() {
testWidgets('Tap callback', (WidgetTester tester) async {
int callbackTab;
await tester.pumpWidget(new CupertinoTabBar(
await pumpWidgetWithBoilerplate(tester, new CupertinoTabBar(
items: <BottomNavigationBarItem>[
const BottomNavigationBarItem(
icon: const ImageIcon(const TestImageProvider(24, 24)),
......
......@@ -67,7 +67,10 @@ void main() {
testWidgets('About box logic defaults to executable name for app name', (WidgetTester tester) async {
await tester.pumpWidget(
const Material(child: const AboutListTile()),
const Directionality(
textDirection: TextDirection.ltr,
child: const Material(child: const AboutListTile()),
),
);
expect(find.text('About flutter_tester'), findsOneWidget);
});
......
......@@ -8,31 +8,34 @@ import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
Widget buildSliverAppBarApp({ bool floating, bool pinned, double expandedHeight, bool snap: false }) {
return new MediaQuery(
data: const MediaQueryData(),
child: new Scaffold(
body: new DefaultTabController(
length: 3,
child: new CustomScrollView(
primary: true,
slivers: <Widget>[
new SliverAppBar(
title: const Text('AppBar Title'),
floating: floating,
pinned: pinned,
expandedHeight: expandedHeight,
snap: snap,
bottom: new TabBar(
tabs: <String>['A','B','C'].map((String t) => new Tab(text: 'TAB $t')).toList(),
return new Directionality(
textDirection: TextDirection.ltr,
child: new MediaQuery(
data: const MediaQueryData(),
child: new Scaffold(
body: new DefaultTabController(
length: 3,
child: new CustomScrollView(
primary: true,
slivers: <Widget>[
new SliverAppBar(
title: const Text('AppBar Title'),
floating: floating,
pinned: pinned,
expandedHeight: expandedHeight,
snap: snap,
bottom: new TabBar(
tabs: <String>['A','B','C'].map((String t) => new Tab(text: 'TAB $t')).toList(),
),
),
),
new SliverToBoxAdapter(
child: new Container(
height: 1200.0,
color: Colors.orange[400],
new SliverToBoxAdapter(
child: new Container(
height: 1200.0,
color: Colors.orange[400],
),
),
),
],
],
),
),
),
),
......@@ -699,11 +702,14 @@ void main() {
const MediaQueryData topPadding100 = const MediaQueryData(padding: const EdgeInsets.only(top: 100.0));
await tester.pumpWidget(
new MediaQuery(
data: topPadding100,
child: new Scaffold(
primary: false,
appBar: new AppBar(),
new Directionality(
textDirection: TextDirection.ltr,
child: new MediaQuery(
data: topPadding100,
child: new Scaffold(
primary: false,
appBar: new AppBar(),
),
),
),
);
......@@ -711,11 +717,14 @@ void main() {
expect(appBarHeight(tester), kToolbarHeight);
await tester.pumpWidget(
new MediaQuery(
data: topPadding100,
child: new Scaffold(
primary: true,
appBar: new AppBar(title: const Text('title'))
new Directionality(
textDirection: TextDirection.ltr,
child: new MediaQuery(
data: topPadding100,
child: new Scaffold(
primary: true,
appBar: new AppBar(title: const Text('title'))
),
),
),
);
......@@ -724,14 +733,17 @@ void main() {
expect(appBarHeight(tester), kToolbarHeight + 100.0);
await tester.pumpWidget(
new MediaQuery(
data: topPadding100,
child: new Scaffold(
primary: false,
appBar: new AppBar(
bottom: new PreferredSize(
preferredSize: const Size.fromHeight(200.0),
child: new Container(),
new Directionality(
textDirection: TextDirection.ltr,
child: new MediaQuery(
data: topPadding100,
child: new Scaffold(
primary: false,
appBar: new AppBar(
bottom: new PreferredSize(
preferredSize: const Size.fromHeight(200.0),
child: new Container(),
),
),
),
),
......@@ -741,14 +753,17 @@ void main() {
expect(appBarHeight(tester), kToolbarHeight + 200.0);
await tester.pumpWidget(
new MediaQuery(
data: topPadding100,
child: new Scaffold(
primary: true,
appBar: new AppBar(
bottom: new PreferredSize(
preferredSize: const Size.fromHeight(200.0),
child: new Container(),
new Directionality(
textDirection: TextDirection.ltr,
child: new MediaQuery(
data: topPadding100,
child: new Scaffold(
primary: true,
appBar: new AppBar(
bottom: new PreferredSize(
preferredSize: const Size.fromHeight(200.0),
child: new Container(),
),
),
),
),
......@@ -758,11 +773,14 @@ void main() {
expect(appBarHeight(tester), kToolbarHeight + 100.0 + 200.0);
await tester.pumpWidget(
new MediaQuery(
data: topPadding100,
child: new AppBar(
primary: false,
title: const Text('title'),
new Directionality(
textDirection: TextDirection.ltr,
child: new MediaQuery(
data: topPadding100,
child: new AppBar(
primary: false,
title: const Text('title'),
),
),
),
);
......
......@@ -6,7 +6,12 @@ import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('ButtonBar default control', (WidgetTester tester) async {
await tester.pumpWidget(const Center(child: const ButtonBar()));
testWidgets('ButtonBar default control smoketest', (WidgetTester tester) async {
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.ltr,
child: const ButtonBar(),
),
);
});
}
......@@ -107,29 +107,31 @@ void main() {
feedback.dispose();
});
testWidgets(
'Chip does not constrain size of label widget if it does not exceed '
'the available space', (WidgetTester tester) async {
testWidgets('Chip does not constrain size of label widget if it does not exceed '
'the available space', (WidgetTester tester) async {
const double labelWidth = 50.0;
const double labelHeight = 30.0;
final Key labelKey = new UniqueKey();
await tester.pumpWidget(
new Material(
child: new Center(
child: new Container(
width: 500.0,
height: 500.0,
child: new Column(
children: <Widget>[
new Chip(
label: new Container(
key: labelKey,
width: labelWidth,
height: labelHeight,
new Directionality(
textDirection: TextDirection.ltr,
child: new Material(
child: new Center(
child: new Container(
width: 500.0,
height: 500.0,
child: new Column(
children: <Widget>[
new Chip(
label: new Container(
key: labelKey,
width: labelWidth,
height: labelHeight,
),
),
),
],
],
),
),
),
),
......@@ -141,15 +143,13 @@ void main() {
expect(labelSize.height, labelHeight);
});
testWidgets(
'Chip constrains the size of the label widget when it exceeds the '
'available space', (WidgetTester tester) async {
testWidgets('Chip constrains the size of the label widget when it exceeds the '
'available space', (WidgetTester tester) async {
await _testConstrainedLabel(tester);
});
testWidgets(
'Chip constrains the size of the label widget when it exceeds the '
'available space and the avatar is present', (WidgetTester tester) async {
testWidgets('Chip constrains the size of the label widget when it exceeds the '
'available space and the avatar is present', (WidgetTester tester) async {
await _testConstrainedLabel(
tester,
avatar: const CircleAvatar(
......@@ -158,20 +158,16 @@ void main() {
);
});
testWidgets(
'Chip constrains the size of the label widget when it exceeds the '
'available space and the delete icon is present',
(WidgetTester tester) async {
testWidgets('Chip constrains the size of the label widget when it exceeds the '
'available space and the delete icon is present', (WidgetTester tester) async {
await _testConstrainedLabel(
tester,
onDeleted: () {},
);
});
testWidgets(
'Chip constrains the size of the label widget when it exceeds the '
'available space and both avatar and delete icons are present',
(WidgetTester tester) async {
testWidgets('Chip constrains the size of the label widget when it exceeds the '
'available space and both avatar and delete icons are present', (WidgetTester tester) async {
await _testConstrainedLabel(
tester,
avatar: const CircleAvatar(
......@@ -223,4 +219,104 @@ void main() {
expect(tester.getSize(find.byType(Text)), const Size(40.0, 10.0));
expect(tester.getSize(find.byType(Chip)), const Size(800.0, 32.0));
});
testWidgets('Chip supports RTL', (WidgetTester tester) async {
final Widget test = new Overlay(
initialEntries: <OverlayEntry>[
new OverlayEntry(
builder: (BuildContext context) {
return new Material(
child: new Center(
child: new Chip(
onDeleted: () { },
label: new Text('ABC'),
),
),
);
},
),
],
);
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.rtl,
child: test,
),
);
expect(tester.getCenter(find.text('ABC')).dx, greaterThan(tester.getCenter(find.byType(Icon)).dx));
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.ltr,
child: test,
),
);
expect(tester.getCenter(find.text('ABC')).dx, lessThan(tester.getCenter(find.byType(Icon)).dx));
});
testWidgets('Chip padding - LTR', (WidgetTester tester) async {
final GlobalKey keyA = new GlobalKey();
final GlobalKey keyB = new GlobalKey();
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.ltr,
child: new Overlay(
initialEntries: <OverlayEntry>[
new OverlayEntry(
builder: (BuildContext context) {
return new Material(
child: new Center(
child: new Chip(
avatar: new Placeholder(key: keyA),
label: new Placeholder(key: keyB),
onDeleted: () { },
),
),
);
},
),
],
),
),
);
expect(tester.getTopLeft(find.byKey(keyA)), const Offset(0.0, 284.0));
expect(tester.getBottomRight(find.byKey(keyA)), const Offset(32.0, 316.0));
expect(tester.getTopLeft(find.byKey(keyB)), const Offset(40.0, 284.0));
expect(tester.getBottomRight(find.byKey(keyB)), const Offset(774.0, 316.0));
expect(tester.getTopLeft(find.byType(Icon)), const Offset(778.0, 291.0));
expect(tester.getBottomRight(find.byType(Icon)), const Offset(796.0, 309.0));
});
testWidgets('Chip padding - RTL', (WidgetTester tester) async {
final GlobalKey keyA = new GlobalKey();
final GlobalKey keyB = new GlobalKey();
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.rtl,
child: new Overlay(
initialEntries: <OverlayEntry>[
new OverlayEntry(
builder: (BuildContext context) {
return new Material(
child: new Center(
child: new Chip(
avatar: new Placeholder(key: keyA),
label: new Placeholder(key: keyB),
onDeleted: () { },
),
),
);
},
),
],
),
),
);
expect(tester.getTopRight(find.byKey(keyA)), const Offset(800.0 - 0.0, 284.0));
expect(tester.getBottomLeft(find.byKey(keyA)), const Offset(800.0 - 32.0, 316.0));
expect(tester.getTopRight(find.byKey(keyB)), const Offset(800.0 - 40.0, 284.0));
expect(tester.getBottomLeft(find.byKey(keyB)), const Offset(800.0 - 774.0, 316.0));
expect(tester.getTopRight(find.byType(Icon)), const Offset(800.0 - 778.0, 291.0));
expect(tester.getBottomLeft(find.byType(Icon)), const Offset(800.0 - 796.0, 309.0));
});
}
......@@ -10,10 +10,17 @@ import 'package:flutter/rendering.dart';
import '../widgets/semantics_tester.dart';
Widget wrap({ Widget child }) {
return new Directionality(
textDirection: TextDirection.ltr,
child: new Material(child: child),
);
}
void main() {
testWidgets('CheckboxListTile control test', (WidgetTester tester) async {
final List<dynamic> log = <dynamic>[];
await tester.pumpWidget(new Material(
await tester.pumpWidget(wrap(
child: new CheckboxListTile(
value: true,
onChanged: (bool value) { log.add(value); },
......@@ -28,7 +35,7 @@ void main() {
testWidgets('RadioListTile control test', (WidgetTester tester) async {
final List<dynamic> log = <dynamic>[];
await tester.pumpWidget(new Material(
await tester.pumpWidget(wrap(
child: new RadioListTile<bool>(
value: true,
groupValue: false,
......@@ -44,7 +51,7 @@ void main() {
testWidgets('SwitchListTile control test', (WidgetTester tester) async {
final List<dynamic> log = <dynamic>[];
await tester.pumpWidget(new Material(
await tester.pumpWidget(wrap(
child: new SwitchListTile(
value: true,
onChanged: (bool value) { log.add(value); },
......@@ -59,7 +66,7 @@ void main() {
testWidgets('SwitchListTile control test', (WidgetTester tester) async {
final SemanticsTester semantics = new SemanticsTester(tester);
await tester.pumpWidget(new Material(
await tester.pumpWidget(wrap(
child: new Column(
children: <Widget>[
new SwitchListTile(
......
......@@ -11,22 +11,24 @@ void main() {
bool isExpanded;
await tester.pumpWidget(
new SingleChildScrollView(
child: new ExpansionPanelList(
expansionCallback: (int _index, bool _isExpanded) {
index = _index;
isExpanded = _isExpanded;
},
children: <ExpansionPanel>[
new ExpansionPanel(
headerBuilder: (BuildContext context, bool isExpanded) {
return new Text(isExpanded ? 'B' : 'A');
},
body: const SizedBox(height: 100.0)
)
]
)
)
new MaterialApp(
home: new SingleChildScrollView(
child: new ExpansionPanelList(
expansionCallback: (int _index, bool _isExpanded) {
index = _index;
isExpanded = _isExpanded;
},
children: <ExpansionPanel>[
new ExpansionPanel(
headerBuilder: (BuildContext context, bool isExpanded) {
return new Text(isExpanded ? 'B' : 'A');
},
body: const SizedBox(height: 100.0),
),
],
),
),
),
);
expect(find.text('A'), findsOneWidget);
......@@ -42,28 +44,28 @@ void main() {
// now expand the child panel
await tester.pumpWidget(
new SingleChildScrollView(
child: new ExpansionPanelList(
expansionCallback: (int _index, bool _isExpanded) {
index = _index;
isExpanded = _isExpanded;
},
children: <ExpansionPanel>[
new ExpansionPanel(
headerBuilder: (BuildContext context, bool isExpanded) {
return new Text(isExpanded ? 'B' : 'A');
},
body: const SizedBox(height: 100.0),
isExpanded: true // this is the addition
)
]
)
)
new MaterialApp(
home: new SingleChildScrollView(
child: new ExpansionPanelList(
expansionCallback: (int _index, bool _isExpanded) {
index = _index;
isExpanded = _isExpanded;
},
children: <ExpansionPanel>[
new ExpansionPanel(
headerBuilder: (BuildContext context, bool isExpanded) {
return new Text(isExpanded ? 'B' : 'A');
},
body: const SizedBox(height: 100.0),
isExpanded: true, // this is the addition
),
],
),
),
),
);
await tester.pump(const Duration(milliseconds: 200));
expect(find.text('A'), findsNothing);
expect(find.text('B'), findsOneWidget);
box = tester.renderObject(find.byType(ExpansionPanelList));
......
......@@ -10,23 +10,25 @@ void main() {
final Key headerKey = new UniqueKey();
final Key footerKey = new UniqueKey();
await tester.pumpWidget(new GridTile(
header: new GridTileBar(
key: headerKey,
leading: const Icon(Icons.thumb_up),
title: const Text('Header'),
subtitle: const Text('Subtitle'),
trailing: const Icon(Icons.thumb_up),
),
child: new DecoratedBox(
decoration: new BoxDecoration(
color: Colors.green[500],
await tester.pumpWidget(new MaterialApp(
home: new GridTile(
header: new GridTileBar(
key: headerKey,
leading: const Icon(Icons.thumb_up),
title: const Text('Header'),
subtitle: const Text('Subtitle'),
trailing: const Icon(Icons.thumb_up),
),
child: new DecoratedBox(
decoration: new BoxDecoration(
color: Colors.green[500],
),
),
footer: new GridTileBar(
key: footerKey,
title: const Text('Footer'),
backgroundColor: Colors.black38,
),
),
footer: new GridTileBar(
key: footerKey,
title: const Text('Footer'),
backgroundColor: Colors.black38,
),
));
......
......@@ -98,15 +98,18 @@ void main() {
'test default icon buttons can be stretched if specified',
(WidgetTester tester) async {
await tester.pumpWidget(
new Material(
child: new Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget> [
new IconButton(
onPressed: mockOnPressedFunction,
icon: const Icon(Icons.ac_unit),
),
],
new Directionality(
textDirection: TextDirection.ltr,
child: new Material(
child: new Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget> [
new IconButton(
onPressed: mockOnPressedFunction,
icon: const Icon(Icons.ac_unit),
),
],
),
),
),
);
......
......@@ -9,37 +9,43 @@ void main() {
testWidgets('InputDecorator always expands horizontally', (WidgetTester tester) async {
final Key key = new UniqueKey();
await tester.pumpWidget(new Material(
child: new Center(
child: new InputDecorator(
decoration: const InputDecoration(),
child: new Container(key: key, width: 50.0, height: 60.0, color: Colors.blue),
await tester.pumpWidget(new MaterialApp(
home: new Material(
child: new Center(
child: new InputDecorator(
decoration: const InputDecoration(),
child: new Container(key: key, width: 50.0, height: 60.0, color: Colors.blue),
),
),
),
));
expect(tester.element(find.byKey(key)).size, equals(const Size(800.0, 60.0)));
await tester.pumpWidget(new Material(
child: new Center(
child: new InputDecorator(
decoration: const InputDecoration(
icon: const Icon(Icons.add_shopping_cart),
await tester.pumpWidget(new MaterialApp(
home: new Material(
child: new Center(
child: new InputDecorator(
decoration: const InputDecoration(
icon: const Icon(Icons.add_shopping_cart),
),
child: new Container(key: key, width: 50.0, height: 60.0, color: Colors.blue),
),
child: new Container(key: key, width: 50.0, height: 60.0, color: Colors.blue),
),
),
));
expect(tester.element(find.byKey(key)).size, equals(const Size(752.0, 60.0)));
await tester.pumpWidget(new Material(
child: new Center(
child: new InputDecorator(
decoration: const InputDecoration.collapsed(
hintText: 'Hint text',
await tester.pumpWidget(new MaterialApp(
home: new Material(
child: new Center(
child: new InputDecorator(
decoration: const InputDecoration.collapsed(
hintText: 'Hint text',
),
child: new Container(key: key, width: 50.0, height: 60.0, color: Colors.blue),
),
child: new Container(key: key, width: 50.0, height: 60.0, color: Colors.blue),
),
),
));
......
......@@ -9,32 +9,35 @@ const Color kSelectedColor = const Color(0xFF00FF00);
const Color kUnselectedColor = Colors.transparent;
Widget buildFrame(TabController tabController, { Color color, Color selectedColor, double indicatorSize: 12.0 }) {
return new Theme(
data: new ThemeData(accentColor: kSelectedColor),
child: new SizedBox.expand(
child: new Center(
child: new SizedBox(
width: 400.0,
height: 400.0,
child: new Column(
children: <Widget>[
new TabPageSelector(
controller: tabController,
color: color,
selectedColor: selectedColor,
indicatorSize: indicatorSize,
),
new Flexible(
child: new TabBarView(
return new Directionality(
textDirection: TextDirection.ltr,
child: new Theme(
data: new ThemeData(accentColor: kSelectedColor),
child: new SizedBox.expand(
child: new Center(
child: new SizedBox(
width: 400.0,
height: 400.0,
child: new Column(
children: <Widget>[
new TabPageSelector(
controller: tabController,
children: <Widget>[
const Center(child: const Text('0')),
const Center(child: const Text('1')),
const Center(child: const Text('2')),
],
color: color,
selectedColor: selectedColor,
indicatorSize: indicatorSize,
),
),
],
new Flexible(
child: new TabBarView(
controller: tabController,
children: <Widget>[
const Center(child: const Text('0')),
const Center(child: const Text('1')),
const Center(child: const Text('2')),
],
),
),
],
),
),
),
),
......
......@@ -14,6 +14,15 @@ import '../rendering/mock_canvas.dart';
import '../rendering/recording_canvas.dart';
import '../widgets/semantics_tester.dart';
Widget boilerplate({ Widget child }) {
return new Directionality(
textDirection: TextDirection.ltr,
child: new Material(
child: child,
),
);
}
class StateMarker extends StatefulWidget {
const StateMarker({ Key key, this.child }) : super(key: key);
......@@ -41,7 +50,7 @@ Widget buildFrame({
bool isScrollable: false,
Color indicatorColor,
}) {
return new Material(
return boilerplate(
child: new DefaultTabController(
initialIndex: tabs.indexOf(value),
length: tabs.length,
......@@ -253,7 +262,7 @@ void main() {
String value = tabs[0];
Widget builder() {
return new Material(
return boilerplate(
child: new DefaultTabController(
initialIndex: tabs.indexOf(value),
length: tabs.length,
......@@ -633,7 +642,7 @@ void main() {
Color secondColor;
await tester.pumpWidget(
new Material(
boilerplate(
child: new TabBar(
controller: controller,
labelColor: Colors.green[500],
......@@ -667,7 +676,7 @@ void main() {
);
await tester.pumpWidget(
new Material(
boilerplate(
child: new TabBarView(
controller: controller,
children: <Widget>[ const Text('First'), const Text('Second') ],
......@@ -765,7 +774,7 @@ void main() {
);
Widget buildFrame() {
return new Material(
return boilerplate(
child: new TabBar(
key: new UniqueKey(),
controller: controller,
......@@ -893,7 +902,7 @@ void main() {
);
await tester.pumpWidget(
new Material(
boilerplate(
child: new TabBar(
isScrollable: true,
controller: controller,
......@@ -927,7 +936,7 @@ void main() {
);
await tester.pumpWidget(
new Material(
boilerplate(
child: new Column(
children: <Widget>[
new TabBar(
......@@ -985,7 +994,7 @@ void main() {
);
await tester.pumpWidget(
new Material(
boilerplate(
child: new Semantics(
container: true,
child: new TabBar(
......@@ -1034,7 +1043,7 @@ void main() {
);
await tester.pumpWidget(
new Material(
boilerplate(
child: new Column(
children: <Widget>[
new TabBar(
......@@ -1074,7 +1083,7 @@ void main() {
);
await tester.pumpWidget(
new Material(
boilerplate(
child: new Column(
children: <Widget>[
new TabBar(
......@@ -1129,7 +1138,7 @@ void main() {
);
await tester.pumpWidget(
new Material(
boilerplate(
child: new Column(
children: <Widget>[
new TabBar(
......
......@@ -167,7 +167,7 @@ void main() {
],
).toString(),
equals(
'LinearGradient(FractionalOffset(0.0, 0.0), FractionalOffset(0.0, 1.0), [Color(0x33333333), Color(0x66666666)], null, TileMode.clamp)',
'LinearGradient(FractionalOffset.topLeft, FractionalOffset.bottomLeft, [Color(0x33333333), Color(0x66666666)], null, TileMode.clamp)',
),
);
});
......
......@@ -36,7 +36,7 @@ void main() {
' │ parentData: <none>\n'
' │ constraints: BoxConstraints(w=800.0, h=600.0)\n'
' │ size: Size(800.0, 600.0)\n'
' │ alignment: FractionalOffset(0.5, 0.5)\n'
' │ alignment: FractionalOffset.center\n'
' │ minWidth: 0.0\n'
' │ maxWidth: Infinity\n'
' │ minHeight: 0.0\n'
......@@ -122,7 +122,7 @@ void main() {
' │ parentData: <none>\n'
' │ constraints: BoxConstraints(w=800.0, h=600.0)\n'
' │ size: Size(800.0, 600.0)\n'
' │ alignment: FractionalOffset(0.5, 0.5)\n'
' │ alignment: FractionalOffset.center\n'
' │ minWidth: 10.0\n'
' │ maxWidth: 500.0\n'
' │ minHeight: 0.0\n'
......@@ -158,7 +158,7 @@ void main() {
' │ parentData: <none>\n'
' │ constraints: BoxConstraints(w=800.0, h=600.0)\n'
' │ size: Size(800.0, 600.0)\n'
' │ alignment: FractionalOffset(0.5, 0.5)\n'
' │ alignment: FractionalOffset.center\n'
' │ minWidth: 10.0\n'
' │ maxWidth: use parent maxWidth constraint\n'
' │ minHeight: use parent minHeight constraint\n'
......
......@@ -36,7 +36,7 @@ void main() {
RenderBox child1, child2;
bool movedChild1 = false;
bool movedChild2 = false;
final RenderFlex block = new RenderFlex();
final RenderFlex block = new RenderFlex(textDirection: TextDirection.ltr);
block.add(child1 = new RenderLayoutTestBox(() { movedChild1 = true; }));
block.add(child2 = new RenderLayoutTestBox(() { movedChild2 = true; }));
......
......@@ -19,6 +19,7 @@ Widget buildFrame(ScrollPhysics physics) {
height: 650.0,
child: new Column(
crossAxisAlignment: CrossAxisAlignment.start,
textDirection: TextDirection.ltr,
children: <Widget>[
const SizedBox(height: 100.0, child: const Text('top')),
new Expanded(child: new Container()),
......
......@@ -59,7 +59,8 @@ void main() {
' │ parentData: offset=Offset(0.0, 0.0) (can use size)\n'
' │ constraints: BoxConstraints(0.0<=w<=800.0, 0.0<=h<=600.0)\n'
' │ size: Size(63.0, 88.0)\n'
' │ padding: EdgeInsets(5.0, 5.0, 5.0, 5.0)\n'
' │ padding: EdgeInsets.all(5.0)\n'
' │ textDirection: null\n'
' │\n'
' └─child: RenderConstrainedBox#00000 relayoutBoundary=up2\n'
' │ creator: ConstrainedBox ← Padding ← Container ← Align ← [root]\n'
......@@ -98,7 +99,8 @@ void main() {
' │ parentData: <none> (can use size)\n'
' │ constraints: BoxConstraints(w=53.0, h=78.0)\n'
' │ size: Size(53.0, 78.0)\n'
' │ padding: EdgeInsets(7.0, 7.0, 7.0, 7.0)\n'
' │ padding: EdgeInsets.all(7.0)\n'
' │ textDirection: null\n'
' │\n'
' └─child: RenderPositionedBox#00000\n'
' │ creator: Align ← Padding ← DecoratedBox ← DecoratedBox ←\n'
......@@ -106,7 +108,7 @@ void main() {
' │ parentData: offset=Offset(7.0, 7.0) (can use size)\n'
' │ constraints: BoxConstraints(w=39.0, h=64.0)\n'
' │ size: Size(39.0, 64.0)\n'
' │ alignment: FractionalOffset(1.0, 1.0)\n'
' │ alignment: FractionalOffset.bottomRight\n'
' │ widthFactor: expand\n'
' │ heightFactor: expand\n'
' │\n'
......
......@@ -47,6 +47,7 @@ void main() {
testWidgets('Flexible defaults to loose', (WidgetTester tester) async {
await tester.pumpWidget(
new Row(
textDirection: TextDirection.ltr,
children: <Widget>[
const Flexible(child: const SizedBox(width: 100.0, height: 200.0)),
],
......@@ -60,6 +61,7 @@ void main() {
testWidgets('Can pass null for flex', (WidgetTester tester) async {
await tester.pumpWidget(
new Row(
textDirection: TextDirection.ltr,
children: <Widget>[
const Expanded(flex: null, child: const Text('one')),
const Flexible(flex: null, child: const Text('two')),
......
......@@ -145,6 +145,7 @@ void main() {
node: parentFocusScope,
autofocus: true,
child: new Row(
textDirection: TextDirection.ltr,
children: <Widget>[
const TestFocusable(
no: 'a',
......@@ -193,6 +194,7 @@ void main() {
new FocusScope(
node: parentFocusScope,
child: new Row(
textDirection: TextDirection.ltr,
children: <Widget>[
const TestFocusable(
no: 'a',
......@@ -218,6 +220,7 @@ void main() {
new FocusScope(
node: parentFocusScope,
child: new Row(
textDirection: TextDirection.ltr,
children: <Widget>[
const TestFocusable(
no: 'a',
......
......@@ -486,7 +486,7 @@ void main() {
expect(
element.toStringDeep(),
equalsIgnoringHashCodes(
'Column-[GlobalKey#00000](renderObject: RenderFlex#00000)\n'
'Column-[GlobalKey#00000](direction: vertical, mainAxisAlignment: start, crossAxisAlignment: center, renderObject: RenderFlex#00000)\n'
'├Container\n'
'│└LimitedBox(maxWidth: 0.0, maxHeight: 0.0, renderObject: RenderLimitedBox#00000 relayoutBoundary=up1)\n'
'│ └ConstrainedBox(BoxConstraints(biggest), renderObject: RenderConstrainedBox#00000 relayoutBoundary=up2)\n'
......
......@@ -24,10 +24,13 @@ void main() {
});
testWidgets('GlobalKey children of two nodes', (WidgetTester tester) async {
await tester.pumpWidget(new Row(children: <Widget>[
new Container(child: new Container(key: const GlobalObjectKey(0))),
new Container(child: new Container(key: const GlobalObjectKey(0))),
]));
await tester.pumpWidget(new Row(
textDirection: TextDirection.ltr,
children: <Widget>[
new Container(child: new Container(key: const GlobalObjectKey(0))),
new Container(child: new Container(key: const GlobalObjectKey(0))),
],
));
final dynamic error = tester.takeException();
expect(error, isFlutterError);
expect(error.toString(), startsWith('Multiple widgets used the same GlobalKey.\n'));
......@@ -38,10 +41,13 @@ void main() {
});
testWidgets('GlobalKey children of two different nodes', (WidgetTester tester) async {
await tester.pumpWidget(new Row(children: <Widget>[
new Container(child: new Container(key: const GlobalObjectKey(0))),
new Container(key: const Key('x'), child: new Container(key: const GlobalObjectKey(0))),
]));
await tester.pumpWidget(new Row(
textDirection: TextDirection.ltr,
children: <Widget>[
new Container(child: new Container(key: const GlobalObjectKey(0))),
new Container(key: const Key('x'), child: new Container(key: const GlobalObjectKey(0))),
],
));
final dynamic error = tester.takeException();
expect(error, isFlutterError);
expect(error.toString(), startsWith('Multiple widgets used the same GlobalKey.\n'));
......@@ -55,17 +61,20 @@ void main() {
testWidgets('GlobalKey children of two nodes', (WidgetTester tester) async {
StateSetter nestedSetState;
bool flag = false;
await tester.pumpWidget(new Row(children: <Widget>[
new Container(child: new Container(key: const GlobalObjectKey(0))),
new Container(child: new StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
nestedSetState = setState;
if (flag)
return new Container(key: const GlobalObjectKey(0));
return new Container();
},
)),
]));
await tester.pumpWidget(new Row(
textDirection: TextDirection.ltr,
children: <Widget>[
new Container(child: new Container(key: const GlobalObjectKey(0))),
new Container(child: new StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
nestedSetState = setState;
if (flag)
return new Container(key: const GlobalObjectKey(0));
return new Container();
},
)),
],
));
nestedSetState(() { flag = true; });
await tester.pump();
final dynamic error = tester.takeException();
......
......@@ -235,6 +235,7 @@ void main() {
// of the Image changes and the MediaQuery widgets do not.
await tester.pumpWidget(
new Row(
textDirection: TextDirection.ltr,
children: <Widget> [
new MediaQuery(
key: mediaQueryKey2,
......@@ -263,6 +264,7 @@ void main() {
await tester.pumpWidget(
new Row(
textDirection: TextDirection.ltr,
children: <Widget> [
new MediaQuery(
key: mediaQueryKey2,
......
......@@ -29,6 +29,7 @@ class SizeChangerState extends State<SizeChanger> {
@override
Widget build(BuildContext context) {
return new Row(
textDirection: TextDirection.ltr,
children: <Widget>[
new SizedBox(
height: _flag ? 50.0 : 100.0,
......
......@@ -42,7 +42,7 @@ void main() {
.where((DiagnosticsNode n) => !n.hidden)
.map((DiagnosticsNode n) => n.toString()).toList();
expect(description, <String>[
'alignment: FractionalOffset(0.5, 0.5)',
'alignment: FractionalOffset.center',
'minWidth: 1.0',
'maxWidth: 2.0',
'minHeight: 3.0',
......
......@@ -43,7 +43,7 @@ void main() {
' ╎ │ size)\n'
' ╎ │ constraints: BoxConstraints(w=800.0, h=600.0)\n'
' ╎ │ size: Size(800.0, 600.0)\n'
' ╎ │ alignment: FractionalOffset(0.0, 0.0)\n'
' ╎ │ alignment: FractionalOffset.topLeft\n'
' ╎ │ fit: expand\n'
' ╎ │ overflow: clip\n'
' ╎ │\n'
......@@ -112,7 +112,7 @@ void main() {
' ╎ │ size)\n'
' ╎ │ constraints: BoxConstraints(w=800.0, h=600.0)\n'
' ╎ │ size: Size(800.0, 600.0)\n'
' ╎ │ alignment: FractionalOffset(0.0, 0.0)\n'
' ╎ │ alignment: FractionalOffset.topLeft\n'
' ╎ │ fit: expand\n'
' ╎ │ overflow: clip\n'
' ╎ │\n'
......
This diff is collapsed.
This diff is collapsed.
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