mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Semantics object support for edge triggered semantics (#16081)
Semantics object support for edge triggered semantics
This commit is contained in:
parent
d05bc9c0e3
commit
034a663d33
@ -96,7 +96,7 @@ class _RecipeGridPageState extends State<RecipeGridPage> {
|
||||
_buildBody(context, statusBarHeight),
|
||||
],
|
||||
),
|
||||
)
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@ -215,30 +215,33 @@ class _PestoLogoState extends State<PestoLogo> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return new Transform(
|
||||
transform: new Matrix4.identity()..scale(widget.height / kLogoHeight),
|
||||
alignment: Alignment.topCenter,
|
||||
child: new SizedBox(
|
||||
width: kLogoWidth,
|
||||
child: new Stack(
|
||||
overflow: Overflow.visible,
|
||||
children: <Widget>[
|
||||
new Positioned.fromRect(
|
||||
rect: _imageRectTween.lerp(widget.t),
|
||||
child: new Image.asset(
|
||||
_kSmallLogoImage,
|
||||
package: _kGalleryAssetsPackage,
|
||||
fit: BoxFit.contain,
|
||||
return new Semantics(
|
||||
namesRoute: true,
|
||||
child: new Transform(
|
||||
transform: new Matrix4.identity()..scale(widget.height / kLogoHeight),
|
||||
alignment: Alignment.topCenter,
|
||||
child: new SizedBox(
|
||||
width: kLogoWidth,
|
||||
child: new Stack(
|
||||
overflow: Overflow.visible,
|
||||
children: <Widget>[
|
||||
new Positioned.fromRect(
|
||||
rect: _imageRectTween.lerp(widget.t),
|
||||
child: new Image.asset(
|
||||
_kSmallLogoImage,
|
||||
package: _kGalleryAssetsPackage,
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
),
|
||||
),
|
||||
new Positioned.fromRect(
|
||||
rect: _textRectTween.lerp(widget.t),
|
||||
child: new Opacity(
|
||||
opacity: _textOpacity.transform(widget.t),
|
||||
child: new Text('PESTO', style: titleStyle, textAlign: TextAlign.center),
|
||||
new Positioned.fromRect(
|
||||
rect: _textRectTween.lerp(widget.t),
|
||||
child: new Opacity(
|
||||
opacity: _textOpacity.transform(widget.t),
|
||||
child: new Text('PESTO', style: titleStyle, textAlign: TextAlign.center),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@ -237,7 +237,11 @@ class CupertinoPageRoute<T> extends PageRoute<T> {
|
||||
|
||||
@override
|
||||
Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
|
||||
final Widget result = builder(context);
|
||||
final Widget result = new Semantics(
|
||||
scopesRoute: true,
|
||||
explicitChildNodes: true,
|
||||
child: builder(context),
|
||||
);
|
||||
assert(() {
|
||||
if (result == null) {
|
||||
throw new FlutterError(
|
||||
|
||||
@ -383,6 +383,15 @@ class _AppBarState extends State<AppBar> {
|
||||
|
||||
Widget title = widget.title;
|
||||
if (title != null) {
|
||||
switch (defaultTargetPlatform) {
|
||||
case TargetPlatform.android:
|
||||
case TargetPlatform.fuchsia:
|
||||
title = new Semantics(namesRoute: true, child: title);
|
||||
break;
|
||||
case TargetPlatform.iOS:
|
||||
break;
|
||||
}
|
||||
|
||||
title = new DefaultTextStyle(
|
||||
style: centerStyle,
|
||||
softWrap: false,
|
||||
|
||||
@ -166,6 +166,7 @@ class AlertDialog extends StatelessWidget {
|
||||
this.content,
|
||||
this.contentPadding: const EdgeInsets.fromLTRB(24.0, 20.0, 24.0, 24.0),
|
||||
this.actions,
|
||||
this.semanticLabel,
|
||||
}) : assert(contentPadding != null),
|
||||
super(key: key);
|
||||
|
||||
@ -216,18 +217,41 @@ class AlertDialog extends StatelessWidget {
|
||||
/// from the [actions].
|
||||
final List<Widget> actions;
|
||||
|
||||
/// The semantic label of the dialog used by accessibility frameworks to
|
||||
/// announce screen transitions when the dialog is opened and closed.
|
||||
///
|
||||
/// If this label is not provided, a semantic label will be infered from the
|
||||
/// [title] if it is not null. If there is no title, the label will be taken
|
||||
/// from [MaterialLocalizations.alertDialogLabel].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [SemanticsConfiguration.isRouteName], for a description of how this
|
||||
/// value is used.
|
||||
final String semanticLabel;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final List<Widget> children = <Widget>[];
|
||||
String label = semanticLabel;
|
||||
|
||||
if (title != null) {
|
||||
children.add(new Padding(
|
||||
padding: titlePadding ?? new EdgeInsets.fromLTRB(24.0, 24.0, 24.0, content == null ? 20.0 : 0.0),
|
||||
child: new DefaultTextStyle(
|
||||
style: Theme.of(context).textTheme.title,
|
||||
child: title,
|
||||
child: new Semantics(child: title, namesRoute: true),
|
||||
),
|
||||
));
|
||||
} else {
|
||||
switch (defaultTargetPlatform) {
|
||||
case TargetPlatform.iOS:
|
||||
label = semanticLabel;
|
||||
break;
|
||||
case TargetPlatform.android:
|
||||
case TargetPlatform.fuchsia:
|
||||
label = semanticLabel ?? MaterialLocalizations.of(context)?.alertDialogLabel;
|
||||
}
|
||||
}
|
||||
|
||||
if (content != null) {
|
||||
@ -250,15 +274,22 @@ class AlertDialog extends StatelessWidget {
|
||||
));
|
||||
}
|
||||
|
||||
return new Dialog(
|
||||
child: new IntrinsicWidth(
|
||||
child: new Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: children,
|
||||
),
|
||||
Widget dialogChild = new IntrinsicWidth(
|
||||
child: new Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: children,
|
||||
),
|
||||
);
|
||||
|
||||
if (label != null)
|
||||
dialogChild = new Semantics(
|
||||
namesRoute: true,
|
||||
label: label,
|
||||
child: dialogChild
|
||||
);
|
||||
|
||||
return new Dialog(child: dialogChild);
|
||||
}
|
||||
}
|
||||
|
||||
@ -402,6 +433,7 @@ class SimpleDialog extends StatelessWidget {
|
||||
this.titlePadding: const EdgeInsets.fromLTRB(24.0, 24.0, 24.0, 0.0),
|
||||
this.children,
|
||||
this.contentPadding: const EdgeInsets.fromLTRB(0.0, 12.0, 0.0, 16.0),
|
||||
this.semanticLabel,
|
||||
}) : assert(titlePadding != null),
|
||||
assert(contentPadding != null),
|
||||
super(key: key);
|
||||
@ -443,18 +475,41 @@ class SimpleDialog extends StatelessWidget {
|
||||
/// the top padding ends up being 24 pixels.
|
||||
final EdgeInsetsGeometry contentPadding;
|
||||
|
||||
/// The semantic label of the dialog used by accessibility frameworks to
|
||||
/// announce screen transitions when the dialog is opened and closed.
|
||||
///
|
||||
/// If this label is not provided, a semantic label will be infered from the
|
||||
/// [title] if it is not null. If there is no title, the label will be taken
|
||||
/// from [MaterialLocalizations.dialogLabel].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [SemanticsConfiguration.isRouteName], for a description of how this
|
||||
/// value is used.
|
||||
final String semanticLabel;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final List<Widget> body = <Widget>[];
|
||||
String label = semanticLabel;
|
||||
|
||||
if (title != null) {
|
||||
body.add(new Padding(
|
||||
padding: titlePadding,
|
||||
child: new DefaultTextStyle(
|
||||
style: Theme.of(context).textTheme.title,
|
||||
child: title,
|
||||
child: new Semantics(namesRoute: true, child: title),
|
||||
)
|
||||
));
|
||||
} else {
|
||||
switch (defaultTargetPlatform) {
|
||||
case TargetPlatform.iOS:
|
||||
label = semanticLabel;
|
||||
break;
|
||||
case TargetPlatform.android:
|
||||
case TargetPlatform.fuchsia:
|
||||
label = semanticLabel ?? MaterialLocalizations.of(context)?.dialogLabel;
|
||||
}
|
||||
}
|
||||
|
||||
if (children != null) {
|
||||
@ -466,19 +521,25 @@ class SimpleDialog extends StatelessWidget {
|
||||
));
|
||||
}
|
||||
|
||||
return new Dialog(
|
||||
child: new IntrinsicWidth(
|
||||
stepWidth: 56.0,
|
||||
child: new ConstrainedBox(
|
||||
constraints: const BoxConstraints(minWidth: 280.0),
|
||||
child: new Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: body,
|
||||
)
|
||||
)
|
||||
)
|
||||
Widget dialogChild = new IntrinsicWidth(
|
||||
stepWidth: 56.0,
|
||||
child: new ConstrainedBox(
|
||||
constraints: const BoxConstraints(minWidth: 280.0),
|
||||
child: new Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: body,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
if (label != null)
|
||||
dialogChild = new Semantics(
|
||||
namesRoute: true,
|
||||
label: label,
|
||||
child: dialogChild,
|
||||
);
|
||||
return new Dialog(child: dialogChild);
|
||||
}
|
||||
}
|
||||
|
||||
@ -514,7 +575,14 @@ class _DialogRoute<T> extends PopupRoute<T> {
|
||||
return new SafeArea(
|
||||
child: new Builder(
|
||||
builder: (BuildContext context) {
|
||||
return theme != null ? new Theme(data: theme, child: child) : child;
|
||||
final Widget annotatedChild = new Semantics(
|
||||
child: child,
|
||||
scopesRoute: true,
|
||||
explicitChildNodes: true,
|
||||
);
|
||||
return theme != null
|
||||
? new Theme(data: theme, child: annotatedChild)
|
||||
: annotatedChild;
|
||||
}
|
||||
),
|
||||
);
|
||||
@ -570,9 +638,9 @@ Future<T> showDialog<T>({
|
||||
) Widget child,
|
||||
WidgetBuilder builder,
|
||||
}) {
|
||||
assert(child == null || builder == null); // ignore: deprecated_member_use
|
||||
assert(child == null || builder == null);
|
||||
return Navigator.of(context, rootNavigator: true).push(new _DialogRoute<T>(
|
||||
child: child ?? new Builder(builder: builder), // ignore: deprecated_member_use
|
||||
child: child ?? new Builder(builder: builder),
|
||||
theme: Theme.of(context, shadowThemeOnly: true),
|
||||
barrierDismissible: barrierDismissible,
|
||||
barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel,
|
||||
|
||||
@ -8,6 +8,7 @@ import 'package:flutter/widgets.dart';
|
||||
import 'colors.dart';
|
||||
import 'list_tile.dart';
|
||||
import 'material.dart';
|
||||
import 'material_localizations.dart';
|
||||
|
||||
/// The possible alignments of a [Drawer].
|
||||
enum DrawerAlignment {
|
||||
@ -85,6 +86,7 @@ class Drawer extends StatelessWidget {
|
||||
Key key,
|
||||
this.elevation: 16.0,
|
||||
this.child,
|
||||
this.semanticLabel,
|
||||
}) : super(key: key);
|
||||
|
||||
/// The z-coordinate at which to place this drawer. This controls the size of
|
||||
@ -100,13 +102,40 @@ class Drawer extends StatelessWidget {
|
||||
/// {@macro flutter.widgets.child}
|
||||
final Widget child;
|
||||
|
||||
/// The semantic label of the dialog used by accessibility frameworks to
|
||||
/// announce screen transitions when the drawer is opened and closed.
|
||||
///
|
||||
/// If this label is not provided, it will default to
|
||||
/// [MaterialLocalizations.drawerLabel].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [SemanticsConfiguration.namesRoute], for a description of how this
|
||||
/// value is used.
|
||||
final String semanticLabel;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return new ConstrainedBox(
|
||||
constraints: const BoxConstraints.expand(width: _kWidth),
|
||||
child: new Material(
|
||||
elevation: elevation,
|
||||
child: child,
|
||||
String label = semanticLabel;
|
||||
switch (defaultTargetPlatform) {
|
||||
case TargetPlatform.iOS:
|
||||
label = semanticLabel;
|
||||
break;
|
||||
case TargetPlatform.android:
|
||||
case TargetPlatform.fuchsia:
|
||||
label = semanticLabel ?? MaterialLocalizations.of(context)?.drawerLabel;
|
||||
}
|
||||
return new Semantics(
|
||||
scopesRoute: true,
|
||||
namesRoute: true,
|
||||
explicitChildNodes: true,
|
||||
label: label,
|
||||
child: new ConstrainedBox(
|
||||
constraints: const BoxConstraints.expand(width: _kWidth),
|
||||
child: new Material(
|
||||
elevation: elevation,
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@ -141,6 +141,19 @@ class _FlexibleSpaceBarState extends State<FlexibleSpaceBar> {
|
||||
}
|
||||
|
||||
if (widget.title != null) {
|
||||
Widget title;
|
||||
switch (defaultTargetPlatform) {
|
||||
case TargetPlatform.iOS:
|
||||
title = widget.title;
|
||||
break;
|
||||
case TargetPlatform.fuchsia:
|
||||
case TargetPlatform.android:
|
||||
title = new Semantics(
|
||||
namesRoute: true,
|
||||
child: widget.title,
|
||||
);
|
||||
}
|
||||
|
||||
final ThemeData theme = Theme.of(context);
|
||||
final double opacity = settings.toolbarOpacity;
|
||||
if (opacity > 0.0) {
|
||||
@ -163,7 +176,10 @@ class _FlexibleSpaceBarState extends State<FlexibleSpaceBar> {
|
||||
transform: scaleTransform,
|
||||
child: new Align(
|
||||
alignment: titleAlignment,
|
||||
child: new DefaultTextStyle(style: titleStyle, child: widget.title)
|
||||
child: new DefaultTextStyle(
|
||||
style: titleStyle,
|
||||
child: title,
|
||||
)
|
||||
)
|
||||
)
|
||||
));
|
||||
|
||||
@ -57,7 +57,7 @@ class _MountainViewPageTransition extends StatelessWidget {
|
||||
/// The transition is adaptive to the platform and on iOS, the page slides in
|
||||
/// from the right and exits in reverse. The page also shifts to the left in
|
||||
/// parallax when another page enters to cover it. (These directions are flipped
|
||||
/// in environements with a right-to-left reading direction.)
|
||||
/// in environments with a right-to-left reading direction.)
|
||||
///
|
||||
/// By default, when a modal route is replaced by another, the previous route
|
||||
/// remains in memory. To free all the resources when this is not necessary, set
|
||||
@ -151,7 +151,11 @@ class MaterialPageRoute<T> extends PageRoute<T> {
|
||||
|
||||
@override
|
||||
Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
|
||||
final Widget result = builder(context);
|
||||
final Widget result = new Semantics(
|
||||
scopesRoute: true,
|
||||
explicitChildNodes: true,
|
||||
child: builder(context),
|
||||
);
|
||||
assert(() {
|
||||
if (result == null) {
|
||||
throw new FlutterError(
|
||||
|
||||
@ -417,10 +417,12 @@ class _CheckedPopupMenuItemState<T> extends PopupMenuItemState<T, CheckedPopupMe
|
||||
class _PopupMenu<T> extends StatelessWidget {
|
||||
const _PopupMenu({
|
||||
Key key,
|
||||
this.route
|
||||
this.route,
|
||||
this.semanticLabel,
|
||||
}) : super(key: key);
|
||||
|
||||
final _PopupMenuRoute<T> route;
|
||||
final String semanticLabel;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -479,7 +481,13 @@ class _PopupMenu<T> extends StatelessWidget {
|
||||
alignment: AlignmentDirectional.topEnd,
|
||||
widthFactor: width.evaluate(route.animation),
|
||||
heightFactor: height.evaluate(route.animation),
|
||||
child: child,
|
||||
child: new Semantics(
|
||||
scopesRoute: true,
|
||||
namesRoute: true,
|
||||
explicitChildNodes: true,
|
||||
label: semanticLabel,
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
@ -577,6 +585,7 @@ class _PopupMenuRoute<T> extends PopupRoute<T> {
|
||||
this.elevation,
|
||||
this.theme,
|
||||
this.barrierLabel,
|
||||
this.semanticLabel,
|
||||
});
|
||||
|
||||
final RelativeRect position;
|
||||
@ -584,6 +593,7 @@ class _PopupMenuRoute<T> extends PopupRoute<T> {
|
||||
final dynamic initialValue;
|
||||
final double elevation;
|
||||
final ThemeData theme;
|
||||
final String semanticLabel;
|
||||
|
||||
@override
|
||||
Animation<double> createAnimation() {
|
||||
@ -620,7 +630,7 @@ class _PopupMenuRoute<T> extends PopupRoute<T> {
|
||||
}
|
||||
}
|
||||
|
||||
Widget menu = new _PopupMenu<T>(route: this);
|
||||
Widget menu = new _PopupMenu<T>(route: this, semanticLabel: semanticLabel);
|
||||
if (theme != null)
|
||||
menu = new Theme(data: theme, child: menu);
|
||||
|
||||
@ -680,7 +690,12 @@ class _PopupMenuRoute<T> extends PopupRoute<T> {
|
||||
/// The `context` argument is used to look up the [Navigator] and [Theme] for
|
||||
/// the menu. It is only used when the method is called. Its corresponding
|
||||
/// widget can be safely removed from the tree before the popup menu is closed.
|
||||
///
|
||||
///
|
||||
/// The `semanticLabel` argument is used by accessibility frameworks to
|
||||
/// announce screen transitions when the menu is opened and closed. If this
|
||||
/// label is not provided, it will default to
|
||||
/// [MaterialLocalizations.popupMenuLabel].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [PopupMenuItem], a popup menu entry for a single value.
|
||||
@ -688,20 +703,34 @@ class _PopupMenuRoute<T> extends PopupRoute<T> {
|
||||
/// * [CheckedPopupMenuItem], a popup menu item with a checkmark.
|
||||
/// * [PopupMenuButton], which provides an [IconButton] that shows a menu by
|
||||
/// calling this method automatically.
|
||||
/// * [SemanticsConfiguration.namesRoute], for a description of edge triggered
|
||||
/// semantics.
|
||||
Future<T> showMenu<T>({
|
||||
@required BuildContext context,
|
||||
RelativeRect position,
|
||||
@required List<PopupMenuEntry<T>> items,
|
||||
T initialValue,
|
||||
double elevation: 8.0,
|
||||
String semanticLabel,
|
||||
}) {
|
||||
assert(context != null);
|
||||
assert(items != null && items.isNotEmpty);
|
||||
String label = semanticLabel;
|
||||
switch (defaultTargetPlatform) {
|
||||
case TargetPlatform.iOS:
|
||||
label = semanticLabel;
|
||||
break;
|
||||
case TargetPlatform.android:
|
||||
case TargetPlatform.fuchsia:
|
||||
label = semanticLabel ?? MaterialLocalizations.of(context)?.popupMenuLabel;
|
||||
}
|
||||
|
||||
return Navigator.push(context, new _PopupMenuRoute<T>(
|
||||
position: position,
|
||||
items: items,
|
||||
initialValue: initialValue,
|
||||
elevation: elevation,
|
||||
semanticLabel: label,
|
||||
theme: Theme.of(context, shadowThemeOnly: true),
|
||||
barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel,
|
||||
));
|
||||
|
||||
@ -837,6 +837,12 @@ class RenderCustomPaint extends RenderProxyBox {
|
||||
if (properties.header != null) {
|
||||
config.isHeader = properties.header;
|
||||
}
|
||||
if (properties.scopesRoute != null) {
|
||||
config.scopesRoute = properties.scopesRoute;
|
||||
}
|
||||
if (properties.namesRoute != null) {
|
||||
config.namesRoute = properties.namesRoute;
|
||||
}
|
||||
if (properties.label != null) {
|
||||
config.label = properties.label;
|
||||
}
|
||||
|
||||
@ -3019,6 +3019,8 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
|
||||
bool focused,
|
||||
bool inMutuallyExclusiveGroup,
|
||||
bool obscured,
|
||||
bool scopesRoute,
|
||||
bool namesRoute,
|
||||
String label,
|
||||
String value,
|
||||
String increasedValue,
|
||||
@ -3054,6 +3056,8 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
|
||||
_focused = focused,
|
||||
_inMutuallyExclusiveGroup = inMutuallyExclusiveGroup,
|
||||
_obscured = obscured,
|
||||
_scopesRoute = scopesRoute,
|
||||
_namesRoute = namesRoute,
|
||||
_label = label,
|
||||
_value = value,
|
||||
_increasedValue = increasedValue,
|
||||
@ -3213,6 +3217,26 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
|
||||
markNeedsSemanticsUpdate();
|
||||
}
|
||||
|
||||
/// If non-null, sets the [SemanticsNode.scopesRoute] semantic to the give value.
|
||||
bool get scopesRoute => _scopesRoute;
|
||||
bool _scopesRoute;
|
||||
set scopesRoute(bool value) {
|
||||
if (scopesRoute == value)
|
||||
return;
|
||||
_scopesRoute = value;
|
||||
markNeedsSemanticsUpdate();
|
||||
}
|
||||
|
||||
/// If non-null, sets the [SemanticsNode.namesRoute] semantic to the give value.
|
||||
bool get namesRoute => _namesRoute;
|
||||
bool _namesRoute;
|
||||
set namesRoute(bool value) {
|
||||
if (_namesRoute == value)
|
||||
return;
|
||||
_namesRoute = value;
|
||||
markNeedsSemanticsUpdate();
|
||||
}
|
||||
|
||||
/// If non-null, sets the [SemanticsNode.label] semantic to the given value.
|
||||
///
|
||||
/// The reading direction is given by [textDirection].
|
||||
@ -3633,6 +3657,8 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
|
||||
super.describeSemanticsConfiguration(config);
|
||||
config.isSemanticBoundary = container;
|
||||
config.explicitChildNodes = explicitChildNodes;
|
||||
assert((scopesRoute == true && explicitChildNodes == true) || scopesRoute != true,
|
||||
'explicitChildNodes must be set to true if scopes route is true');
|
||||
|
||||
if (enabled != null)
|
||||
config.isEnabled = enabled;
|
||||
@ -3662,6 +3688,10 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
|
||||
config.decreasedValue = decreasedValue;
|
||||
if (hint != null)
|
||||
config.hint = hint;
|
||||
if (scopesRoute != null)
|
||||
config.scopesRoute = scopesRoute;
|
||||
if (namesRoute != null)
|
||||
config.namesRoute = namesRoute;
|
||||
if (textDirection != null)
|
||||
config.textDirection = textDirection;
|
||||
if (sortKey != null)
|
||||
|
||||
@ -320,6 +320,8 @@ class SemanticsProperties extends DiagnosticableTree {
|
||||
this.focused,
|
||||
this.inMutuallyExclusiveGroup,
|
||||
this.obscured,
|
||||
this.scopesRoute,
|
||||
this.namesRoute,
|
||||
this.label,
|
||||
this.value,
|
||||
this.increasedValue,
|
||||
@ -407,6 +409,25 @@ class SemanticsProperties extends DiagnosticableTree {
|
||||
/// Doing so instructs screen readers to not read out the [value].
|
||||
final bool obscured;
|
||||
|
||||
/// If non-null, whether the node corresponds to the root of a subtree for
|
||||
/// which a route name should be announced.
|
||||
///
|
||||
/// Generally, this is set in combination with [explicitChildNodes], since
|
||||
/// nodes with this flag are not considered focusable by Android or iOS.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [SemanticsFlag.scopesRoute] for a description of how the announced
|
||||
/// value is selected.
|
||||
final bool scopesRoute;
|
||||
|
||||
/// If non-null, whether the node contains the semantic label for a route.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [SemanticsFlag.namesRoute] for a description of how the name is used.
|
||||
final bool namesRoute;
|
||||
|
||||
/// Provides a textual description of the widget.
|
||||
///
|
||||
/// If a label is provided, there must either by an ambient [Directionality]
|
||||
@ -2336,6 +2357,25 @@ class SemanticsConfiguration {
|
||||
_hasBeenAnnotated = true;
|
||||
}
|
||||
|
||||
/// Whether the semantics node is the root of a subtree for which values
|
||||
/// should be announced.
|
||||
///
|
||||
/// See also:
|
||||
/// * [SemanticsFlag.scopesRoute], for a full description of route scoping.
|
||||
bool get scopesRoute => _hasFlag(SemanticsFlag.scopesRoute);
|
||||
set scopesRoute(bool value) {
|
||||
_setFlag(SemanticsFlag.scopesRoute, value);
|
||||
}
|
||||
|
||||
/// Whether the semantics node contains the label of a route.
|
||||
///
|
||||
/// See also:
|
||||
/// * [SemanticsFlag.namesRoute], for a full description of route naming.
|
||||
bool get namesRoute => _hasFlag(SemanticsFlag.namesRoute);
|
||||
set namesRoute(bool value) {
|
||||
_setFlag(SemanticsFlag.namesRoute, value);
|
||||
}
|
||||
|
||||
/// The reading direction for the text in [label], [value], [hint],
|
||||
/// [increasedValue], and [decreasedValue].
|
||||
TextDirection get textDirection => _textDirection;
|
||||
|
||||
@ -4896,6 +4896,8 @@ class Semantics extends SingleChildRenderObjectWidget {
|
||||
bool focused,
|
||||
bool inMutuallyExclusiveGroup,
|
||||
bool obscured,
|
||||
bool scopesRoute,
|
||||
bool namesRoute,
|
||||
String label,
|
||||
String value,
|
||||
String increasedValue,
|
||||
@ -4934,6 +4936,8 @@ class Semantics extends SingleChildRenderObjectWidget {
|
||||
focused: focused,
|
||||
inMutuallyExclusiveGroup: inMutuallyExclusiveGroup,
|
||||
obscured: obscured,
|
||||
scopesRoute: scopesRoute,
|
||||
namesRoute: namesRoute,
|
||||
label: label,
|
||||
value: value,
|
||||
increasedValue: increasedValue,
|
||||
@ -4995,6 +4999,10 @@ class Semantics extends SingleChildRenderObjectWidget {
|
||||
/// information to the semantic tree is to introduce new explicit
|
||||
/// [SemanticNode]s to the tree.
|
||||
///
|
||||
/// If the semantics properties of this node include
|
||||
/// [SemanticsProperties.scopesRoute] set to true, then [explicitChildNodes]
|
||||
/// must be true also.
|
||||
///
|
||||
/// This setting is often used in combination with [SemanticsConfiguration.isSemanticBoundary]
|
||||
/// to create semantic boundaries that are either writable or not for children.
|
||||
final bool explicitChildNodes;
|
||||
@ -5013,6 +5021,8 @@ class Semantics extends SingleChildRenderObjectWidget {
|
||||
focused: properties.focused,
|
||||
inMutuallyExclusiveGroup: properties.inMutuallyExclusiveGroup,
|
||||
obscured: properties.obscured,
|
||||
scopesRoute: properties.scopesRoute,
|
||||
namesRoute: properties.namesRoute,
|
||||
label: properties.label,
|
||||
value: properties.value,
|
||||
increasedValue: properties.increasedValue,
|
||||
@ -5064,6 +5074,8 @@ class Semantics extends SingleChildRenderObjectWidget {
|
||||
..increasedValue = properties.increasedValue
|
||||
..decreasedValue = properties.decreasedValue
|
||||
..hint = properties.hint
|
||||
..scopesRoute = properties.scopesRoute
|
||||
..namesRoute = properties.namesRoute
|
||||
..textDirection = _getTextDirection(context)
|
||||
..sortKey = properties.sortKey
|
||||
..onTap = properties.onTap
|
||||
|
||||
@ -387,6 +387,9 @@ void _tests() {
|
||||
final SemanticsTester semantics = new SemanticsTester(tester);
|
||||
await preparePicker(tester, (Future<DateTime> date) async {
|
||||
final TestSemantics expected = new TestSemantics(
|
||||
flags: <SemanticsFlag>[
|
||||
SemanticsFlag.scopesRoute,
|
||||
],
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
actions: <SemanticsAction>[SemanticsAction.tap],
|
||||
@ -616,7 +619,7 @@ void _tests() {
|
||||
);
|
||||
|
||||
expect(semantics, hasSemantics(
|
||||
expected,
|
||||
new TestSemantics.root(children: <TestSemantics>[expected]),
|
||||
ignoreId: true,
|
||||
ignoreTransform: true,
|
||||
ignoreRect: true,
|
||||
|
||||
@ -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:ui';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:matcher/matcher.dart';
|
||||
@ -332,4 +334,51 @@ void main() {
|
||||
new Rect.fromLTRB(40.0, 24.0, 800.0 - 40.0, 600.0 - 24.0),
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('Dialog widget contains route semantics from title', (WidgetTester tester) async {
|
||||
final SemanticsTester semantics = new SemanticsTester(tester);
|
||||
await tester.pumpWidget(
|
||||
new MaterialApp(
|
||||
home: new Material(
|
||||
child: new Builder(
|
||||
builder: (BuildContext context) {
|
||||
return new Center(
|
||||
child: new RaisedButton(
|
||||
child: const Text('X'),
|
||||
onPressed: () {
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return const AlertDialog(
|
||||
title: const Text('Title'),
|
||||
content: const Text('Y'),
|
||||
actions: const <Widget>[],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(semantics, isNot(includesNodeWith(
|
||||
label: 'Title',
|
||||
flags: <SemanticsFlag>[SemanticsFlag.namesRoute]
|
||||
)));
|
||||
|
||||
await tester.tap(find.text('X'));
|
||||
await tester.pump(); // start animation
|
||||
await tester.pump(const Duration(seconds: 1));
|
||||
|
||||
expect(semantics, includesNodeWith(
|
||||
label: 'Title',
|
||||
flags: <SemanticsFlag>[SemanticsFlag.namesRoute],
|
||||
));
|
||||
|
||||
semantics.dispose();
|
||||
});
|
||||
}
|
||||
|
||||
@ -281,14 +281,21 @@ void main() {
|
||||
expect(semantics, hasSemantics(new TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
label: 'Add Photo',
|
||||
actions: <SemanticsAction>[
|
||||
SemanticsAction.tap
|
||||
],
|
||||
flags: <SemanticsFlag>[
|
||||
SemanticsFlag.isButton,
|
||||
SemanticsFlag.hasEnabledState,
|
||||
SemanticsFlag.isEnabled,
|
||||
SemanticsFlag.scopesRoute,
|
||||
],
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
label: 'Add Photo',
|
||||
actions: <SemanticsAction>[
|
||||
SemanticsAction.tap
|
||||
],
|
||||
flags: <SemanticsFlag>[
|
||||
SemanticsFlag.isButton,
|
||||
SemanticsFlag.hasEnabledState,
|
||||
SemanticsFlag.isEnabled,
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
|
||||
@ -2,11 +2,13 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:ui' show window;
|
||||
import 'dart:ui' show window, SemanticsFlag;
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../widgets/semantics_tester.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('Navigator.push works within a PopupMenuButton', (WidgetTester tester) async {
|
||||
final Key targetKey = new UniqueKey();
|
||||
@ -427,6 +429,39 @@ void main() {
|
||||
|
||||
expect(MediaQuery.of(popupContext).padding, EdgeInsets.zero);
|
||||
});
|
||||
|
||||
testWidgets('PopupMenu includes route semantics', (WidgetTester tester) async {
|
||||
final SemanticsTester semantics = new SemanticsTester(tester);
|
||||
await tester.pumpWidget(new MaterialApp(
|
||||
home: new Material(
|
||||
child: new PopupMenuButton<int>(
|
||||
itemBuilder: (BuildContext context) {
|
||||
return <PopupMenuItem<int>>[
|
||||
const PopupMenuItem<int>(value: 2, child: const Text('2')),
|
||||
const PopupMenuItem<int>(value: 3, child: const Text('3')),
|
||||
];
|
||||
},
|
||||
child: const SizedBox(
|
||||
height: 100.0,
|
||||
width: 100.0,
|
||||
child: const Text('XXX'),
|
||||
),
|
||||
),
|
||||
),
|
||||
));
|
||||
await tester.tap(find.text('XXX'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(semantics, includesNodeWith(
|
||||
label: 'Popup menu',
|
||||
flags: <SemanticsFlag>[
|
||||
SemanticsFlag.namesRoute,
|
||||
SemanticsFlag.scopesRoute,
|
||||
],
|
||||
));
|
||||
|
||||
semantics.dispose();
|
||||
});
|
||||
}
|
||||
|
||||
class TestApp extends StatefulWidget {
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import 'dart:ui';
|
||||
|
||||
// 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.
|
||||
@ -486,12 +488,13 @@ void main() {
|
||||
);
|
||||
|
||||
final TestSemantics expected = new TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
id: 1,
|
||||
label: tooltipText,
|
||||
),
|
||||
]
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
id: 1,
|
||||
label: 'TIP',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
]
|
||||
);
|
||||
|
||||
expect(semantics, hasSemantics(expected, ignoreTransform: true, ignoreRect: true));
|
||||
@ -619,8 +622,13 @@ void main() {
|
||||
expect(semantics, hasSemantics(new TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
label: 'Foo\nBar',
|
||||
textDirection: TextDirection.ltr,
|
||||
flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
label: 'Foo\nBar',
|
||||
textDirection: TextDirection.ltr,
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
), ignoreRect: true, ignoreId: true, ignoreTransform: true));
|
||||
@ -646,8 +654,13 @@ void main() {
|
||||
expect(semantics, hasSemantics(new TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
label: 'Bar',
|
||||
textDirection: TextDirection.ltr,
|
||||
flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
label: 'Bar',
|
||||
textDirection: TextDirection.ltr,
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
), ignoreRect: true, ignoreId: true, ignoreTransform: true));
|
||||
|
||||
@ -335,26 +335,31 @@ void main() {
|
||||
new TestSemantics(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
label: 'Signed in\nname\nemail',
|
||||
textDirection: TextDirection.ltr,
|
||||
flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
label: r'B',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
new TestSemantics(
|
||||
label: r'C',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
new TestSemantics(
|
||||
label: r'D',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
new TestSemantics(
|
||||
flags: <SemanticsFlag>[SemanticsFlag.isButton],
|
||||
actions: <SemanticsAction>[SemanticsAction.tap],
|
||||
label: r'Show accounts',
|
||||
label: 'Signed in\nname\nemail',
|
||||
textDirection: TextDirection.ltr,
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
label: r'B',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
new TestSemantics(
|
||||
label: r'C',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
new TestSemantics(
|
||||
label: r'D',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
new TestSemantics(
|
||||
flags: <SemanticsFlag>[SemanticsFlag.isButton],
|
||||
actions: <SemanticsAction>[SemanticsAction.tap],
|
||||
label: r'Show accounts',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -382,20 +387,25 @@ void main() {
|
||||
new TestSemantics(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
label: 'Signed in',
|
||||
textDirection: TextDirection.ltr,
|
||||
flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
label: r'B',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
new TestSemantics(
|
||||
label: r'C',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
new TestSemantics(
|
||||
label: r'D',
|
||||
label: 'Signed in',
|
||||
textDirection: TextDirection.ltr,
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
label: r'B',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
new TestSemantics(
|
||||
label: r'C',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
new TestSemantics(
|
||||
label: r'D',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@ -416,15 +416,13 @@ void _defineTests() {
|
||||
inMutuallyExclusiveGroup: true,
|
||||
header: true,
|
||||
obscured: true,
|
||||
scopesRoute: true,
|
||||
namesRoute: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
));
|
||||
|
||||
// TODO(jonahwilliams): remove when rolling edge semantic support for framework.
|
||||
final List<SemanticsFlag> flags = SemanticsFlag.values.values
|
||||
.where((SemanticsFlag flag) => flag != SemanticsFlag.scopesRoute && flag != SemanticsFlag.namesRoute)
|
||||
.toList();
|
||||
final TestSemantics expectedSemantics = new TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
@ -433,7 +431,7 @@ void _defineTests() {
|
||||
new TestSemantics.rootChild(
|
||||
id: 2,
|
||||
rect: TestSemantics.fullScreen,
|
||||
flags: flags,
|
||||
flags: SemanticsFlag.values.values.toList(),
|
||||
),
|
||||
]
|
||||
),
|
||||
|
||||
@ -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:ui';
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@ -284,4 +286,40 @@ void main() {
|
||||
|
||||
semantics.dispose();
|
||||
});
|
||||
|
||||
testWidgets('Drawer contains route semantics flags', (WidgetTester tester) async {
|
||||
final SemanticsTester semantics = new SemanticsTester(tester);
|
||||
final GlobalKey<ScaffoldState> scaffoldKey = new GlobalKey<ScaffoldState>();
|
||||
|
||||
await tester.pumpWidget(
|
||||
new MaterialApp(
|
||||
home: new Builder(
|
||||
builder: (BuildContext context) {
|
||||
return new Scaffold(
|
||||
key: scaffoldKey,
|
||||
drawer: const Drawer(),
|
||||
body: new Container(),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Open the drawer.
|
||||
scaffoldKey.currentState.openDrawer();
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 100));
|
||||
|
||||
expect(semantics, includesNodeWith(
|
||||
label: 'Navigation menu',
|
||||
flags: <SemanticsFlag>[
|
||||
SemanticsFlag.scopesRoute,
|
||||
SemanticsFlag.namesRoute,
|
||||
],
|
||||
));
|
||||
|
||||
semantics.dispose();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -593,12 +593,17 @@ void main() {
|
||||
|
||||
expect(semantics, hasSemantics(new TestSemantics(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
flags: <SemanticsFlag>[SemanticsFlag.isTextField, SemanticsFlag.isObscured],
|
||||
value: expectedValue,
|
||||
textDirection: TextDirection.ltr,
|
||||
nextNodeId: -1,
|
||||
previousNodeId: -1,
|
||||
new TestSemantics.rootChild(
|
||||
flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
flags: <SemanticsFlag>[SemanticsFlag.isTextField, SemanticsFlag.isObscured],
|
||||
value: expectedValue,
|
||||
textDirection: TextDirection.ltr,
|
||||
nextNodeId: -1,
|
||||
previousNodeId: -1,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
), ignoreTransform: true, ignoreRect: true, ignoreId: true));
|
||||
@ -713,26 +718,32 @@ void main() {
|
||||
await tester.pump();
|
||||
|
||||
final SemanticsOwner owner = tester.binding.pipelineOwner.semanticsOwner;
|
||||
const int expectedNodeId = 2;
|
||||
const int expectedNodeId = 3;
|
||||
|
||||
expect(semantics, hasSemantics(new TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
id: expectedNodeId,
|
||||
flags: <SemanticsFlag>[
|
||||
SemanticsFlag.isTextField,
|
||||
SemanticsFlag.isFocused
|
||||
id: 1,
|
||||
flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
id: expectedNodeId,
|
||||
flags: <SemanticsFlag>[
|
||||
SemanticsFlag.isTextField,
|
||||
SemanticsFlag.isFocused
|
||||
],
|
||||
actions: <SemanticsAction>[
|
||||
SemanticsAction.moveCursorBackwardByCharacter,
|
||||
SemanticsAction.setSelection,
|
||||
SemanticsAction.copy,
|
||||
SemanticsAction.cut,
|
||||
SemanticsAction.paste
|
||||
],
|
||||
value: 'test',
|
||||
textSelection: new TextSelection.collapsed(offset: controller.text.length),
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
],
|
||||
actions: <SemanticsAction>[
|
||||
SemanticsAction.moveCursorBackwardByCharacter,
|
||||
SemanticsAction.setSelection,
|
||||
SemanticsAction.copy,
|
||||
SemanticsAction.cut,
|
||||
SemanticsAction.paste
|
||||
],
|
||||
value: 'test',
|
||||
textSelection: new TextSelection.collapsed(offset: controller.text.length),
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
],
|
||||
), ignoreRect: true, ignoreTransform: true));
|
||||
|
||||
@ -2,9 +2,13 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'semantics_tester.dart';
|
||||
|
||||
class FirstWidget extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -769,4 +773,50 @@ void main() {
|
||||
await tester.pumpAndSettle(const Duration(milliseconds: 10));
|
||||
expect(log, <String>['building B', 'building C', 'found C', 'building D', 'building C', 'found C']);
|
||||
});
|
||||
|
||||
testWidgets('route semantics', (WidgetTester tester) async {
|
||||
final SemanticsTester semantics = new SemanticsTester(tester);
|
||||
final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
|
||||
'/': (BuildContext context) => new OnTapPage(id: '1', onTap: () { Navigator.pushNamed(context, '/A'); }),
|
||||
'/A': (BuildContext context) => new OnTapPage(id: '2', onTap: () { Navigator.pushNamed(context, '/B/C'); }),
|
||||
'/B/C': (BuildContext context) => const OnTapPage(id: '3'),
|
||||
};
|
||||
|
||||
await tester.pumpWidget(new MaterialApp(routes: routes));
|
||||
|
||||
expect(semantics, includesNodeWith(
|
||||
flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
|
||||
));
|
||||
expect(semantics, includesNodeWith(
|
||||
label: 'Page 1',
|
||||
flags: <SemanticsFlag>[SemanticsFlag.namesRoute],
|
||||
));
|
||||
|
||||
await tester.tap(find.text('1')); // pushNamed('/A')
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(seconds: 1));
|
||||
|
||||
expect(semantics, includesNodeWith(
|
||||
flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
|
||||
));
|
||||
expect(semantics, includesNodeWith(
|
||||
label: 'Page 2',
|
||||
flags: <SemanticsFlag>[SemanticsFlag.namesRoute],
|
||||
));
|
||||
|
||||
await tester.tap(find.text('2')); // pushNamed('/B/C')
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(seconds: 1));
|
||||
|
||||
expect(semantics, includesNodeWith(
|
||||
flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
|
||||
));
|
||||
expect(semantics, includesNodeWith(
|
||||
label: 'Page 3',
|
||||
flags: <SemanticsFlag>[SemanticsFlag.namesRoute],
|
||||
));
|
||||
|
||||
|
||||
semantics.dispose();
|
||||
});
|
||||
}
|
||||
|
||||
@ -471,17 +471,17 @@ void main() {
|
||||
inMutuallyExclusiveGroup: true,
|
||||
header: true,
|
||||
obscured: true,
|
||||
scopesRoute: true,
|
||||
namesRoute: true,
|
||||
explicitChildNodes: true,
|
||||
)
|
||||
);
|
||||
// TODO(jonahwilliams): remove when adding engine support for edge semantics
|
||||
final List<SemanticsFlag> flags = SemanticsFlag.values.values
|
||||
.where((SemanticsFlag flag) => flag != SemanticsFlag.scopesRoute && flag != SemanticsFlag.namesRoute)
|
||||
.toList();
|
||||
|
||||
final TestSemantics expectedSemantics = new TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
rect: TestSemantics.fullScreen,
|
||||
flags: flags,
|
||||
flags: SemanticsFlag.values.values.toList(),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
@ -106,29 +106,35 @@ void _tests() {
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
id: 1,
|
||||
flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
id: 4,
|
||||
id: 2,
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
id: 2,
|
||||
tags: <SemanticsTag>[const SemanticsTag('RenderViewport.twoPane')],
|
||||
label: 'Plain text',
|
||||
textDirection: TextDirection.ltr,
|
||||
nextNodeId: 3,
|
||||
),
|
||||
new TestSemantics(
|
||||
id: 3,
|
||||
tags: <SemanticsTag>[const SemanticsTag('RenderViewport.twoPane')],
|
||||
flags: <SemanticsFlag>[SemanticsFlag.hasCheckedState, SemanticsFlag.isChecked, SemanticsFlag.isSelected],
|
||||
actions: <SemanticsAction>[SemanticsAction.tap, SemanticsAction.decrease],
|
||||
label: 'Interactive text',
|
||||
value: 'test-value',
|
||||
increasedValue: 'test-increasedValue',
|
||||
decreasedValue: 'test-decreasedValue',
|
||||
hint: 'test-hint',
|
||||
textDirection: TextDirection.rtl,
|
||||
previousNodeId: 2,
|
||||
id: 5,
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
id: 3,
|
||||
tags: <SemanticsTag>[const SemanticsTag('RenderViewport.twoPane')],
|
||||
label: 'Plain text',
|
||||
textDirection: TextDirection.ltr,
|
||||
nextNodeId: 4,
|
||||
),
|
||||
new TestSemantics(
|
||||
id: 4,
|
||||
tags: <SemanticsTag>[const SemanticsTag('RenderViewport.twoPane')],
|
||||
flags: <SemanticsFlag>[SemanticsFlag.hasCheckedState, SemanticsFlag.isChecked, SemanticsFlag.isSelected],
|
||||
actions: <SemanticsAction>[SemanticsAction.tap, SemanticsAction.decrease],
|
||||
label: 'Interactive text',
|
||||
value: 'test-value',
|
||||
increasedValue: 'test-increasedValue',
|
||||
decreasedValue: 'test-decreasedValue',
|
||||
hint: 'test-hint',
|
||||
textDirection: TextDirection.rtl,
|
||||
previousNodeId: 3,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@ -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:ui';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
@ -53,12 +55,19 @@ void main() {
|
||||
expect(semantics, hasSemantics(new TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
rect: new Rect.fromLTWH(0.0, 0.0, 800.0, 600.0),
|
||||
id: 2,
|
||||
label: 'Hello!',
|
||||
textDirection: TextDirection.ltr,
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 10.0, 10.0),
|
||||
transform: new Matrix4.translationValues(395.0, 295.0, 0.0),
|
||||
)
|
||||
flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
id: 3,
|
||||
label: 'Hello!',
|
||||
textDirection: TextDirection.ltr,
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 10.0, 10.0),
|
||||
transform: new Matrix4.translationValues(395.0, 295.0, 0.0),
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
)));
|
||||
|
||||
|
||||
@ -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:ui';
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
@ -72,6 +74,7 @@ void main() {
|
||||
),
|
||||
new TestSemantics(
|
||||
id: 4,
|
||||
flags: <SemanticsFlag>[SemanticsFlag.namesRoute],
|
||||
label: r'Semantics Test with Slivers',
|
||||
textDirection: TextDirection.ltr,
|
||||
nextNodeId: 2,
|
||||
@ -126,6 +129,7 @@ void main() {
|
||||
),
|
||||
new TestSemantics(
|
||||
id: 4,
|
||||
flags: <SemanticsFlag>[SemanticsFlag.namesRoute],
|
||||
label: r'Semantics Test with Slivers',
|
||||
textDirection: TextDirection.ltr,
|
||||
previousNodeId: 5,
|
||||
@ -176,6 +180,7 @@ void main() {
|
||||
),
|
||||
new TestSemantics(
|
||||
id: 4,
|
||||
flags: <SemanticsFlag>[SemanticsFlag.namesRoute],
|
||||
label: r'Semantics Test with Slivers',
|
||||
textDirection: TextDirection.ltr,
|
||||
nextNodeId: 2,
|
||||
@ -412,6 +417,7 @@ void main() {
|
||||
new TestSemantics(
|
||||
id: 22,
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 120.0, 20.0),
|
||||
flags: <SemanticsFlag>[SemanticsFlag.namesRoute],
|
||||
tags: <SemanticsTag>[RenderViewport.excludeFromScrolling],
|
||||
label: 'AppBar',
|
||||
previousNodeId: 23,
|
||||
@ -502,6 +508,7 @@ void main() {
|
||||
id: 28,
|
||||
previousNodeId: 29,
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 120.0, 20.0),
|
||||
flags: <SemanticsFlag>[SemanticsFlag.namesRoute],
|
||||
tags: <SemanticsTag>[RenderViewport.excludeFromScrolling],
|
||||
label: 'AppBar'
|
||||
),
|
||||
@ -594,6 +601,7 @@ void main() {
|
||||
previousNodeId: 35,
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 120.0, 20.0),
|
||||
transform: new Matrix4.translation(new Vector3(0.0, 544.0, 0.0)),
|
||||
flags: <SemanticsFlag>[SemanticsFlag.namesRoute],
|
||||
tags: <SemanticsTag>[RenderViewport.excludeFromScrolling],
|
||||
label: 'AppBar'
|
||||
),
|
||||
@ -685,6 +693,7 @@ void main() {
|
||||
previousNodeId: 41,
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 120.0, 20.0),
|
||||
transform: new Matrix4.translation(new Vector3(0.0, 544.0, 0.0)),
|
||||
flags: <SemanticsFlag>[SemanticsFlag.namesRoute],
|
||||
tags: <SemanticsTag>[RenderViewport.excludeFromScrolling],
|
||||
label: 'AppBar'
|
||||
),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user