diff --git a/dev/manual_tests/lib/actions.dart b/dev/manual_tests/lib/actions.dart index b76fad18b11..a09d75450e6 100644 --- a/dev/manual_tests/lib/actions.dart +++ b/dev/manual_tests/lib/actions.dart @@ -247,8 +247,8 @@ abstract class UndoableAction extends Action { } } -class UndoableFocusActionBase extends UndoableAction { - UndoableFocusActionBase(LocalKey name) : super(name); +class SetFocusActionBase extends UndoableAction { + SetFocusActionBase(LocalKey name) : super(name); FocusNode _previousFocus; @@ -286,8 +286,10 @@ class UndoableFocusActionBase extends UndoableAction { } } -class UndoableRequestFocusAction extends UndoableFocusActionBase { - UndoableRequestFocusAction() : super(RequestFocusAction.key); +class SetFocusAction extends SetFocusActionBase { + SetFocusAction() : super(key); + + static const LocalKey key = ValueKey(SetFocusAction); @override void invoke(FocusNode node, Intent intent) { @@ -297,8 +299,10 @@ class UndoableRequestFocusAction extends UndoableFocusActionBase { } /// Actions for manipulating focus. -class UndoableNextFocusAction extends UndoableFocusActionBase { - UndoableNextFocusAction() : super(NextFocusAction.key); +class NextFocusAction extends SetFocusActionBase { + NextFocusAction() : super(key); + + static const LocalKey key = ValueKey(NextFocusAction); @override void invoke(FocusNode node, Intent intent) { @@ -307,8 +311,10 @@ class UndoableNextFocusAction extends UndoableFocusActionBase { } } -class UndoablePreviousFocusAction extends UndoableFocusActionBase { - UndoablePreviousFocusAction() : super(PreviousFocusAction.key); +class PreviousFocusAction extends SetFocusActionBase { + PreviousFocusAction() : super(key); + + static const LocalKey key = ValueKey(PreviousFocusAction); @override void invoke(FocusNode node, Intent intent) { @@ -317,8 +323,16 @@ class UndoablePreviousFocusAction extends UndoableFocusActionBase { } } -class UndoableDirectionalFocusAction extends UndoableFocusActionBase { - UndoableDirectionalFocusAction() : super(DirectionalFocusAction.key); +class DirectionalFocusIntent extends Intent { + const DirectionalFocusIntent(this.direction) : super(DirectionalFocusAction.key); + + final TraversalDirection direction; +} + +class DirectionalFocusAction extends SetFocusActionBase { + DirectionalFocusAction() : super(key); + + static const LocalKey key = ValueKey(DirectionalFocusAction); TraversalDirection direction; @@ -352,7 +366,7 @@ class _DemoButtonState extends State { void _handleOnPressed() { print('Button ${widget.name} pressed.'); setState(() { - Actions.invoke(context, const Intent(RequestFocusAction.key), focusNode: _focusNode); + Actions.invoke(context, const Intent(SetFocusAction.key), focusNode: _focusNode); }); } @@ -420,91 +434,101 @@ class _FocusDemoState extends State { @override Widget build(BuildContext context) { final TextTheme textTheme = Theme.of(context).textTheme; - return Actions( - dispatcher: dispatcher, - actions: { - RequestFocusAction.key: () => UndoableRequestFocusAction(), - NextFocusAction.key: () => UndoableNextFocusAction(), - PreviousFocusAction.key: () => UndoablePreviousFocusAction(), - DirectionalFocusAction.key: () => UndoableDirectionalFocusAction(), - kUndoActionKey: () => kUndoAction, - kRedoActionKey: () => kRedoAction, + return Shortcuts( + shortcuts: { + LogicalKeySet(LogicalKeyboardKey.tab): const Intent(NextFocusAction.key), + LogicalKeySet(LogicalKeyboardKey.shift, LogicalKeyboardKey.tab): const Intent(PreviousFocusAction.key), + LogicalKeySet(LogicalKeyboardKey.arrowUp): const DirectionalFocusIntent(TraversalDirection.up), + LogicalKeySet(LogicalKeyboardKey.arrowDown): const DirectionalFocusIntent(TraversalDirection.down), + LogicalKeySet(LogicalKeyboardKey.arrowLeft): const DirectionalFocusIntent(TraversalDirection.left), + LogicalKeySet(LogicalKeyboardKey.arrowRight): const DirectionalFocusIntent(TraversalDirection.right), }, - child: DefaultFocusTraversal( - policy: ReadingOrderTraversalPolicy(), - child: Shortcuts( - shortcuts: { - LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.shift, LogicalKeyboardKey.keyZ): kRedoIntent, - LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyZ): kUndoIntent, - }, - child: FocusScope( - debugLabel: 'Scope', - autofocus: true, - child: DefaultTextStyle( - style: textTheme.display1, - child: Scaffold( - appBar: AppBar( - title: const Text('Actions Demo'), - ), - body: Center( - child: Builder(builder: (BuildContext context) { - return Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: const [ - DemoButton(name: 'One'), - DemoButton(name: 'Two'), - DemoButton(name: 'Three'), - ], - ), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: const [ - DemoButton(name: 'Four'), - DemoButton(name: 'Five'), - DemoButton(name: 'Six'), - ], - ), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: const [ - DemoButton(name: 'Seven'), - DemoButton(name: 'Eight'), - DemoButton(name: 'Nine'), - ], - ), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Padding( - padding: const EdgeInsets.all(8.0), - child: RaisedButton( - child: const Text('UNDO'), - onPressed: canUndo - ? () { - Actions.invoke(context, kUndoIntent); - } - : null, + child: Actions( + dispatcher: dispatcher, + actions: { + SetFocusAction.key: () => SetFocusAction(), + NextFocusAction.key: () => NextFocusAction(), + PreviousFocusAction.key: () => PreviousFocusAction(), + DirectionalFocusAction.key: () => DirectionalFocusAction(), + kUndoActionKey: () => kUndoAction, + kRedoActionKey: () => kRedoAction, + }, + child: DefaultFocusTraversal( + policy: ReadingOrderTraversalPolicy(), + child: Shortcuts( + shortcuts: { + LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.shift, LogicalKeyboardKey.keyZ): kRedoIntent, + LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyZ): kUndoIntent, + }, + child: FocusScope( + debugLabel: 'Scope', + autofocus: true, + child: DefaultTextStyle( + style: textTheme.display1, + child: Scaffold( + appBar: AppBar( + title: const Text('Actions Demo'), + ), + body: Center( + child: Builder(builder: (BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: const [ + DemoButton(name: 'One'), + DemoButton(name: 'Two'), + DemoButton(name: 'Three'), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: const [ + DemoButton(name: 'Four'), + DemoButton(name: 'Five'), + DemoButton(name: 'Six'), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: const [ + DemoButton(name: 'Seven'), + DemoButton(name: 'Eight'), + DemoButton(name: 'Nine'), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: RaisedButton( + child: const Text('UNDO'), + onPressed: canUndo + ? () { + Actions.invoke(context, kUndoIntent); + } + : null, + ), ), - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: RaisedButton( - child: const Text('REDO'), - onPressed: canRedo - ? () { - Actions.invoke(context, kRedoIntent); - } - : null, + Padding( + padding: const EdgeInsets.all(8.0), + child: RaisedButton( + child: const Text('REDO'), + onPressed: canRedo + ? () { + Actions.invoke(context, kRedoIntent); + } + : null, + ), ), - ), - ], - ), - ], - ); - }), + ], + ), + ], + ); + }), + ), ), ), ), diff --git a/packages/flutter/lib/src/material/bottom_navigation_bar.dart b/packages/flutter/lib/src/material/bottom_navigation_bar.dart index f32b2689fbb..2151ccac464 100644 --- a/packages/flutter/lib/src/material/bottom_navigation_bar.dart +++ b/packages/flutter/lib/src/material/bottom_navigation_bar.dart @@ -474,43 +474,45 @@ class _BottomNavigationTile extends StatelessWidget { child: Semantics( container: true, selected: selected, - child: Stack( - children: [ - InkResponse( - onTap: onTap, - child: Padding( - padding: EdgeInsets.only(top: topPadding, bottom: bottomPadding), - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - mainAxisSize: MainAxisSize.min, - children: [ - _TileIcon( - colorTween: colorTween, - animation: animation, - iconSize: iconSize, - selected: selected, - item: item, - selectedIconTheme: selectedIconTheme, - unselectedIconTheme: unselectedIconTheme, - ), - _Label( - colorTween: colorTween, - animation: animation, - item: item, - selectedLabelStyle: selectedLabelStyle, - unselectedLabelStyle: unselectedLabelStyle, - showSelectedLabels: showSelectedLabels, - showUnselectedLabels: showUnselectedLabels, - ), - ], + child: Focus( + child: Stack( + children: [ + InkResponse( + onTap: onTap, + child: Padding( + padding: EdgeInsets.only(top: topPadding, bottom: bottomPadding), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisSize: MainAxisSize.min, + children: [ + _TileIcon( + colorTween: colorTween, + animation: animation, + iconSize: iconSize, + selected: selected, + item: item, + selectedIconTheme: selectedIconTheme, + unselectedIconTheme: unselectedIconTheme, + ), + _Label( + colorTween: colorTween, + animation: animation, + item: item, + selectedLabelStyle: selectedLabelStyle, + unselectedLabelStyle: unselectedLabelStyle, + showSelectedLabels: showSelectedLabels, + showUnselectedLabels: showUnselectedLabels, + ), + ], + ), ), ), - ), - Semantics( - label: indexLabel, - ), - ], + Semantics( + label: indexLabel, + ), + ], + ), ), ), ); diff --git a/packages/flutter/lib/src/material/button.dart b/packages/flutter/lib/src/material/button.dart index 367344bf34d..3f438f1261c 100644 --- a/packages/flutter/lib/src/material/button.dart +++ b/packages/flutter/lib/src/material/button.dart @@ -330,37 +330,39 @@ class _RawMaterialButtonState extends State { final Color effectiveTextColor = MaterialStateProperty.resolveAs(widget.textStyle?.color, _states); final ShapeBorder effectiveShape = MaterialStateProperty.resolveAs(widget.shape, _states); - final Widget result = ConstrainedBox( - constraints: widget.constraints, - child: Material( - elevation: _effectiveElevation, - textStyle: widget.textStyle?.copyWith(color: effectiveTextColor), - shape: effectiveShape, - color: widget.fillColor, - type: widget.fillColor == null ? MaterialType.transparency : MaterialType.button, - animationDuration: widget.animationDuration, - clipBehavior: widget.clipBehavior, - child: InkWell( - focusNode: widget.focusNode, - canRequestFocus: widget.enabled, - onFocusChange: _handleFocusedChanged, - autofocus: widget.autofocus, - onHighlightChanged: _handleHighlightChanged, - splashColor: widget.splashColor, - highlightColor: widget.highlightColor, - focusColor: widget.focusColor, - hoverColor: widget.hoverColor, - onHover: _handleHoveredChanged, - onTap: widget.onPressed, - customBorder: effectiveShape, - child: IconTheme.merge( - data: IconThemeData(color: effectiveTextColor), - child: Container( - padding: widget.padding, - child: Center( - widthFactor: 1.0, - heightFactor: 1.0, - child: widget.child, + final Widget result = Focus( + focusNode: widget.focusNode, + canRequestFocus: widget.enabled, + onFocusChange: _handleFocusedChanged, + autofocus: widget.autofocus, + child: ConstrainedBox( + constraints: widget.constraints, + child: Material( + elevation: _effectiveElevation, + textStyle: widget.textStyle?.copyWith(color: effectiveTextColor), + shape: effectiveShape, + color: widget.fillColor, + type: widget.fillColor == null ? MaterialType.transparency : MaterialType.button, + animationDuration: widget.animationDuration, + clipBehavior: widget.clipBehavior, + child: InkWell( + onHighlightChanged: _handleHighlightChanged, + splashColor: widget.splashColor, + highlightColor: widget.highlightColor, + focusColor: widget.focusColor, + hoverColor: widget.hoverColor, + onHover: _handleHoveredChanged, + onTap: widget.onPressed, + customBorder: effectiveShape, + child: IconTheme.merge( + data: IconThemeData(color: effectiveTextColor), + child: Container( + padding: widget.padding, + child: Center( + widthFactor: 1.0, + heightFactor: 1.0, + child: widget.child, + ), ), ), ), diff --git a/packages/flutter/lib/src/material/chip.dart b/packages/flutter/lib/src/material/chip.dart index 64d0869bd76..669abac8c52 100644 --- a/packages/flutter/lib/src/material/chip.dart +++ b/packages/flutter/lib/src/material/chip.dart @@ -1774,71 +1774,73 @@ class _RawChipState extends State with TickerProviderStateMixin(effectiveLabelStyle?.color, _states); final TextStyle resolvedLabelStyle = effectiveLabelStyle?.copyWith(color: resolvedLabelColor); - Widget result = Material( - elevation: isTapping ? pressElevation : elevation, - shadowColor: widget.selected ? selectedShadowColor : shadowColor, - animationDuration: pressedAnimationDuration, - shape: shape, - clipBehavior: widget.clipBehavior, - child: InkWell( - onFocusChange: _handleFocus, - focusNode: widget.focusNode, - autofocus: widget.autofocus, - canRequestFocus: widget.isEnabled, - onTap: canTap ? _handleTap : null, - onTapDown: canTap ? _handleTapDown : null, - onTapCancel: canTap ? _handleTapCancel : null, - onHover: canTap ? _handleHover : null, - customBorder: shape, - child: AnimatedBuilder( - animation: Listenable.merge([selectController, enableController]), - builder: (BuildContext context, Widget child) { - return Container( - decoration: ShapeDecoration( - shape: shape, - color: getBackgroundColor(chipTheme), + Widget result = Focus( + onFocusChange: _handleFocus, + focusNode: widget.focusNode, + autofocus: widget.autofocus, + canRequestFocus: widget.isEnabled, + child: Material( + elevation: isTapping ? pressElevation : elevation, + shadowColor: widget.selected ? selectedShadowColor : shadowColor, + animationDuration: pressedAnimationDuration, + shape: shape, + clipBehavior: widget.clipBehavior, + child: InkWell( + onTap: canTap ? _handleTap : null, + onTapDown: canTap ? _handleTapDown : null, + onTapCancel: canTap ? _handleTapCancel : null, + onHover: canTap ? _handleHover : null, + customBorder: shape, + child: AnimatedBuilder( + animation: Listenable.merge([selectController, enableController]), + builder: (BuildContext context, Widget child) { + return Container( + decoration: ShapeDecoration( + shape: shape, + color: getBackgroundColor(chipTheme), + ), + child: child, + ); + }, + child: _wrapWithTooltip( + widget.tooltip, + widget.onPressed, + _ChipRenderWidget( + theme: _ChipRenderTheme( + label: DefaultTextStyle( + overflow: TextOverflow.fade, + textAlign: TextAlign.start, + maxLines: 1, + softWrap: false, + style: resolvedLabelStyle, + child: widget.label, + ), + avatar: AnimatedSwitcher( + child: widget.avatar, + duration: _kDrawerDuration, + switchInCurve: Curves.fastOutSlowIn, + ), + deleteIcon: AnimatedSwitcher( + child: _buildDeleteIcon(context, theme, chipTheme), + duration: _kDrawerDuration, + switchInCurve: Curves.fastOutSlowIn, + ), + brightness: chipTheme.brightness, + padding: (widget.padding ?? chipTheme.padding).resolve(textDirection), + labelPadding: (widget.labelPadding ?? chipTheme.labelPadding).resolve(textDirection), + showAvatar: hasAvatar, + showCheckmark: showCheckmark, + checkmarkColor: checkmarkColor, + canTapBody: canTap, + ), + value: widget.selected, + checkmarkAnimation: checkmarkAnimation, + enableAnimation: enableAnimation, + avatarDrawerAnimation: avatarDrawerAnimation, + deleteDrawerAnimation: deleteDrawerAnimation, + isEnabled: widget.isEnabled, + avatarBorder: widget.avatarBorder, ), - child: child, - ); - }, - child: _wrapWithTooltip( - widget.tooltip, - widget.onPressed, - _ChipRenderWidget( - theme: _ChipRenderTheme( - label: DefaultTextStyle( - overflow: TextOverflow.fade, - textAlign: TextAlign.start, - maxLines: 1, - softWrap: false, - style: resolvedLabelStyle, - child: widget.label, - ), - avatar: AnimatedSwitcher( - child: widget.avatar, - duration: _kDrawerDuration, - switchInCurve: Curves.fastOutSlowIn, - ), - deleteIcon: AnimatedSwitcher( - child: _buildDeleteIcon(context, theme, chipTheme), - duration: _kDrawerDuration, - switchInCurve: Curves.fastOutSlowIn, - ), - brightness: chipTheme.brightness, - padding: (widget.padding ?? chipTheme.padding).resolve(textDirection), - labelPadding: (widget.labelPadding ?? chipTheme.labelPadding).resolve(textDirection), - showAvatar: hasAvatar, - showCheckmark: showCheckmark, - checkmarkColor: checkmarkColor, - canTapBody: canTap, - ), - value: widget.selected, - checkmarkAnimation: checkmarkAnimation, - enableAnimation: enableAnimation, - avatarDrawerAnimation: avatarDrawerAnimation, - deleteDrawerAnimation: deleteDrawerAnimation, - isEnabled: widget.isEnabled, - avatarBorder: widget.avatarBorder, ), ), ), diff --git a/packages/flutter/lib/src/material/icon_button.dart b/packages/flutter/lib/src/material/icon_button.dart index 6fe788db1d8..e2ef27ddace 100644 --- a/packages/flutter/lib/src/material/icon_button.dart +++ b/packages/flutter/lib/src/material/icon_button.dart @@ -309,20 +309,22 @@ class IconButton extends StatelessWidget { return Semantics( button: true, enabled: onPressed != null, - child: InkResponse( + child: Focus( focusNode: focusNode, autofocus: autofocus, canRequestFocus: onPressed != null, - onTap: onPressed, - child: result, - focusColor: focusColor ?? Theme.of(context).focusColor, - hoverColor: hoverColor ?? Theme.of(context).hoverColor, - highlightColor: highlightColor ?? Theme.of(context).highlightColor, - splashColor: splashColor ?? Theme.of(context).splashColor, - radius: math.max( - Material.defaultSplashRadius, - (iconSize + math.min(padding.horizontal, padding.vertical)) * 0.7, - // x 0.5 for diameter -> radius and + 40% overflow derived from other Material apps. + child: InkResponse( + onTap: onPressed, + child: result, + focusColor: focusColor ?? Theme.of(context).focusColor, + hoverColor: hoverColor ?? Theme.of(context).hoverColor, + highlightColor: highlightColor ?? Theme.of(context).highlightColor, + splashColor: splashColor ?? Theme.of(context).splashColor, + radius: math.max( + Material.defaultSplashRadius, + (iconSize + math.min(padding.horizontal, padding.vertical)) * 0.7, + // x 0.5 for diameter -> radius and + 40% overflow derived from other Material apps. + ), ), ), ); diff --git a/packages/flutter/lib/src/material/ink_well.dart b/packages/flutter/lib/src/material/ink_well.dart index 01628a69b3a..a69c8ff3dcc 100644 --- a/packages/flutter/lib/src/material/ink_well.dart +++ b/packages/flutter/lib/src/material/ink_well.dart @@ -210,16 +210,10 @@ class InkResponse extends StatefulWidget { this.splashFactory, this.enableFeedback = true, this.excludeFromSemantics = false, - this.focusNode, - this.canRequestFocus = true, - this.onFocusChange, - this.autofocus = false, }) : assert(containedInkWell != null), assert(highlightShape != null), assert(enableFeedback != null), assert(excludeFromSemantics != null), - assert(autofocus != null), - assert(canRequestFocus != null), super(key: key); /// The widget below this widget in the tree. @@ -406,21 +400,6 @@ class InkResponse extends StatefulWidget { /// duplication of information. final bool excludeFromSemantics; - /// Handler called when the focus changes. - /// - /// Called with true if this widget's node gains focus, and false if it loses - /// focus. - final ValueChanged onFocusChange; - - /// {@macro flutter.widgets.Focus.autofocus} - final bool autofocus; - - /// {@macro flutter.widgets.Focus.focusNode} - final FocusNode focusNode; - - /// {@template flutter.widgets.Focus.canRequestFocus} - final bool canRequestFocus; - /// The rectangle to use for the highlight effect and for clipping /// the splash effects if [containedInkWell] is true. /// @@ -483,6 +462,7 @@ enum _HighlightType { class _InkResponseState extends State with AutomaticKeepAliveClientMixin { Set _splashes; InteractiveInkFeature _currentSplash; + FocusNode _focusNode; bool _hovering = false; final Map<_HighlightType, InkHighlight> _highlights = <_HighlightType, InkHighlight>{}; @@ -494,18 +474,27 @@ class _InkResponseState extends State with AutomaticKe WidgetsBinding.instance.focusManager.addHighlightModeListener(_handleFocusHighlightModeChange); } + @override + void didChangeDependencies() { + super.didChangeDependencies(); + _focusNode?.removeListener(_handleFocusUpdate); + _focusNode = Focus.of(context, nullOk: true); + _focusNode?.addListener(_handleFocusUpdate); + } + @override void didUpdateWidget(InkResponse oldWidget) { super.didUpdateWidget(oldWidget); if (_isWidgetEnabled(widget) != _isWidgetEnabled(oldWidget)) { _handleHoverChange(_hovering); - _updateFocusHighlights(); + _handleFocusUpdate(); } } @override void dispose() { WidgetsBinding.instance.focusManager.removeHighlightModeListener(_handleFocusHighlightModeChange); + _focusNode?.removeListener(_handleFocusUpdate); super.dispose(); } @@ -571,7 +560,7 @@ class _InkResponseState extends State with AutomaticKe } assert(value == (_highlights[type] != null && _highlights[type].active)); - switch (type) { + switch(type) { case _HighlightType.pressed: if (widget.onHighlightChanged != null) widget.onHighlightChanged(value); @@ -585,10 +574,10 @@ class _InkResponseState extends State with AutomaticKe } } - InteractiveInkFeature _createInkFeature(Offset globalPosition) { + InteractiveInkFeature _createInkFeature(TapDownDetails details) { final MaterialInkController inkController = Material.of(context); final RenderBox referenceBox = context.findRenderObject(); - final Offset position = referenceBox.globalToLocal(globalPosition); + final Offset position = referenceBox.globalToLocal(details.globalPosition); final Color color = widget.splashColor ?? Theme.of(context).splashColor; final RectCallback rectCallback = widget.containedInkWell ? widget.getRectCallback(referenceBox) : null; final BorderRadius borderRadius = widget.borderRadius; @@ -627,54 +616,31 @@ class _InkResponseState extends State with AutomaticKe return; } setState(() { - _updateFocusHighlights(); + _handleFocusUpdate(); }); } - void _updateFocusHighlights() { + void _handleFocusUpdate() { bool showFocus; switch (WidgetsBinding.instance.focusManager.highlightMode) { case FocusHighlightMode.touch: showFocus = false; break; case FocusHighlightMode.traditional: - showFocus = enabled && _hasFocus; + showFocus = enabled && (Focus.of(context, nullOk: true)?.hasPrimaryFocus ?? false); break; } updateHighlight(_HighlightType.focus, value: showFocus); } - bool _hasFocus = false; - void _handleFocusUpdate(bool hasFocus) { - _hasFocus = hasFocus; - _updateFocusHighlights(); - if (widget.onFocusChange != null) { - widget.onFocusChange(hasFocus); - } - } - void _handleTapDown(TapDownDetails details) { - _startSplash(details: details); - if (widget.onTapDown != null) { - widget.onTapDown(details); - } - } - - void _startSplash({TapDownDetails details, BuildContext context}) { - assert(details != null || context != null); - - Offset globalPosition; - if (context != null) { - final RenderBox referenceBox = context.findRenderObject(); - assert(referenceBox.hasSize, 'InkResponse must be done with layout before starting a splash.'); - globalPosition = referenceBox.localToGlobal(referenceBox.paintBounds.center); - } else { - globalPosition = details.globalPosition; - } - final InteractiveInkFeature splash = _createInkFeature(globalPosition); + final InteractiveInkFeature splash = _createInkFeature(details); _splashes ??= HashSet(); _splashes.add(splash); _currentSplash = splash; + if (widget.onTapDown != null) { + widget.onTapDown(details); + } updateKeepAlive(); updateHighlight(_HighlightType.pressed, value: true); } @@ -756,37 +722,18 @@ class _InkResponseState extends State with AutomaticKe _highlights[type]?.color = getHighlightColorForType(type); } _currentSplash?.color = widget.splashColor ?? Theme.of(context).splashColor; - return Actions( - actions: { - ActivateAction.key: () { - return CallbackAction( - ActivateAction.key, - onInvoke: (FocusNode node, Intent intent) { - _startSplash(context: node.context); - _handleTap(node.context); - }, - ); - }, - }, - child: Focus( - focusNode: widget.focusNode, - canRequestFocus: widget.canRequestFocus, - onFocusChange: _handleFocusUpdate, - autofocus: widget.autofocus, - child: MouseRegion( - onEnter: enabled ? _handleMouseEnter : null, - onExit: enabled ? _handleMouseExit : null, - child: GestureDetector( - onTapDown: enabled ? _handleTapDown : null, - onTap: enabled ? () => _handleTap(context) : null, - onTapCancel: enabled ? _handleTapCancel : null, - onDoubleTap: widget.onDoubleTap != null ? _handleDoubleTap : null, - onLongPress: widget.onLongPress != null ? () => _handleLongPress(context) : null, - behavior: HitTestBehavior.opaque, - excludeFromSemantics: widget.excludeFromSemantics, - child: widget.child, - ), - ), + return MouseRegion( + onEnter: enabled ? _handleMouseEnter : null, + onExit: enabled ? _handleMouseExit : null, + child: GestureDetector( + onTapDown: enabled ? _handleTapDown : null, + onTap: enabled ? () => _handleTap(context) : null, + onTapCancel: enabled ? _handleTapCancel : null, + onDoubleTap: widget.onDoubleTap != null ? _handleDoubleTap : null, + onLongPress: widget.onLongPress != null ? () => _handleLongPress(context) : null, + behavior: HitTestBehavior.opaque, + child: widget.child, + excludeFromSemantics: widget.excludeFromSemantics, ), ); } @@ -907,10 +854,6 @@ class InkWell extends InkResponse { ShapeBorder customBorder, bool enableFeedback = true, bool excludeFromSemantics = false, - FocusNode focusNode, - bool canRequestFocus = true, - ValueChanged onFocusChange, - bool autofocus = false, }) : super( key: key, child: child, @@ -933,9 +876,5 @@ class InkWell extends InkResponse { customBorder: customBorder, enableFeedback: enableFeedback ?? true, excludeFromSemantics: excludeFromSemantics ?? false, - focusNode: focusNode, - canRequestFocus: canRequestFocus ?? true, - onFocusChange: onFocusChange, - autofocus: autofocus ?? false, ); } diff --git a/packages/flutter/lib/src/widgets/actions.dart b/packages/flutter/lib/src/widgets/actions.dart index e642da25816..8008ee04ca9 100644 --- a/packages/flutter/lib/src/widgets/actions.dart +++ b/packages/flutter/lib/src/widgets/actions.dart @@ -344,20 +344,6 @@ class Actions extends InheritedWidget { return oldWidget.dispatcher != dispatcher || oldWidget.actions != actions; } - @override - bool operator ==(dynamic other) { - if (other.runtimeType != runtimeType) { - return false; - } - if (identical(this, other)) { - return true; - } - return !updateShouldNotify(other); - } - - @override - int get hashCode => hashValues(dispatcher, actions); - @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); @@ -382,16 +368,3 @@ class DoNothingAction extends Action { @override void invoke(FocusNode node, Intent intent) { } } - -/// An action that invokes the currently focused control. -/// -/// This is an abstract class that serves as a base class for actions that -/// activate a control. It is bound to [LogicalKeyboardKey.enter] in the default -/// keyboard map in [WidgetsApp]. -abstract class ActivateAction extends Action { - /// Creates a [ActivateAction] with a fixed [key]; - const ActivateAction() : super(key); - - /// The [LocalKey] that uniquely identifies this action. - static const LocalKey key = ValueKey(ActivateAction); -} diff --git a/packages/flutter/lib/src/widgets/app.dart b/packages/flutter/lib/src/widgets/app.dart index c923c1e4171..9b1e990d24e 100644 --- a/packages/flutter/lib/src/widgets/app.dart +++ b/packages/flutter/lib/src/widgets/app.dart @@ -7,7 +7,6 @@ import 'dart:collection' show HashMap; import 'package:flutter/foundation.dart'; import 'package:flutter/rendering.dart'; -import 'package:flutter/services.dart'; import 'actions.dart'; import 'banner.dart'; @@ -21,7 +20,6 @@ import 'navigator.dart'; import 'pages.dart'; import 'performance_overlay.dart'; import 'semantics_debugger.dart'; -import 'shortcuts.dart'; import 'text.dart'; import 'title.dart'; import 'widget_inspector.dart'; @@ -1197,33 +1195,18 @@ class _WidgetsAppState extends State implements WidgetsBindingObserv assert(_debugCheckLocalizations(appLocale)); - return Shortcuts( - shortcuts: { - LogicalKeySet(LogicalKeyboardKey.tab): const Intent(NextFocusAction.key), - LogicalKeySet(LogicalKeyboardKey.shift, LogicalKeyboardKey.tab): const Intent(PreviousFocusAction.key), - LogicalKeySet(LogicalKeyboardKey.arrowLeft): const DirectionalFocusIntent(TraversalDirection.left), - LogicalKeySet(LogicalKeyboardKey.arrowRight): const DirectionalFocusIntent(TraversalDirection.right), - LogicalKeySet(LogicalKeyboardKey.arrowDown): const DirectionalFocusIntent(TraversalDirection.down), - LogicalKeySet(LogicalKeyboardKey.arrowUp): const DirectionalFocusIntent(TraversalDirection.up), - LogicalKeySet(LogicalKeyboardKey.enter): const Intent(ActivateAction.key), + return Actions( + actions: { + DoNothingAction.key: () => const DoNothingAction(), }, - child: Actions( - actions: { - DoNothingAction.key: () => const DoNothingAction(), - RequestFocusAction.key: () => RequestFocusAction(), - NextFocusAction.key: () => NextFocusAction(), - PreviousFocusAction.key: () => PreviousFocusAction(), - DirectionalFocusAction.key: () => DirectionalFocusAction(), - }, - child: DefaultFocusTraversal( - policy: ReadingOrderTraversalPolicy(), - child: MediaQuery( - data: MediaQueryData.fromWindow(WidgetsBinding.instance.window), - child: Localizations( - locale: appLocale, - delegates: _localizationsDelegates.toList(), - child: title, - ), + child: DefaultFocusTraversal( + policy: ReadingOrderTraversalPolicy(), + child: MediaQuery( + data: MediaQueryData.fromWindow(WidgetsBinding.instance.window), + child: Localizations( + locale: appLocale, + delegates: _localizationsDelegates.toList(), + child: title, ), ), ), diff --git a/packages/flutter/lib/src/widgets/focus_scope.dart b/packages/flutter/lib/src/widgets/focus_scope.dart index 760e97c4c94..f829d05de76 100644 --- a/packages/flutter/lib/src/widgets/focus_scope.dart +++ b/packages/flutter/lib/src/widgets/focus_scope.dart @@ -146,11 +146,10 @@ class Focus extends StatefulWidget { this.onFocusChange, this.onKey, this.debugLabel, - this.canRequestFocus = true, + this.canRequestFocus, this.skipTraversal, }) : assert(child != null), assert(autofocus != null), - assert(canRequestFocus != null), super(key: key); /// A debug label for this widget. @@ -187,7 +186,7 @@ class Focus extends StatefulWidget { /// Handler called when the focus changes. /// - /// Called with true if this widget's node gains focus, and false if it loses + /// Called with true if this node gains focus, and false if it loses /// focus. final ValueChanged onFocusChange; @@ -231,7 +230,6 @@ class Focus extends StatefulWidget { /// still be focused explicitly. final bool skipTraversal; - /// {@template flutter.widgets.Focus.canRequestFocus} /// If true, this widget may request the primary focus. /// /// Defaults to true. Set to false if you want the [FocusNode] this widget @@ -251,7 +249,6 @@ class Focus extends StatefulWidget { /// its descendants. /// - [FocusTraversalPolicy], a class that can be extended to describe a /// traversal policy. - /// {@endtemplate} final bool canRequestFocus; /// Returns the [focusNode] of the [Focus] that most tightly encloses the diff --git a/packages/flutter/lib/src/widgets/focus_traversal.dart b/packages/flutter/lib/src/widgets/focus_traversal.dart index e0a85893432..e063d81a0ed 100644 --- a/packages/flutter/lib/src/widgets/focus_traversal.dart +++ b/packages/flutter/lib/src/widgets/focus_traversal.dart @@ -2,12 +2,9 @@ // 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/foundation.dart'; import 'package:flutter/painting.dart'; -import 'actions.dart'; import 'basic.dart'; import 'binding.dart'; import 'focus_manager.dart'; @@ -793,138 +790,3 @@ class DefaultFocusTraversal extends InheritedWidget { @override bool updateShouldNotify(DefaultFocusTraversal oldWidget) => policy != oldWidget.policy; } - -// A base class for all of the default actions that request focus for a node. -class _RequestFocusActionBase extends Action { - _RequestFocusActionBase(LocalKey name) : super(name); - - FocusNode _previousFocus; - - @override - void invoke(FocusNode node, Intent tag) { - _previousFocus = WidgetsBinding.instance.focusManager.primaryFocus; - node.requestFocus(); - } - - @override - void debugFillProperties(DiagnosticPropertiesBuilder properties) { - super.debugFillProperties(properties); - properties.add(DiagnosticsProperty('previous', _previousFocus)); - } -} - -/// An [Action] that requests the focus on the node it is invoked on. -/// -/// This action can be used to request focus for a particular node, by calling -/// [Action.invoke] like so: -/// -/// ```dart -/// Actions.invoke(context, const Intent(RequestFocusAction.key), focusNode: _focusNode); -/// ``` -/// -/// Where the `_focusNode` is the node for which the focus will be requested. -/// -/// The difference between requesting focus in this way versus calling -/// [_focusNode.requestFocus] directly is that it will use the [Action] -/// registered in the nearest [Actions] widget associated with [key] to make the -/// request, rather than just requesting focus directly. This allows the action -/// to have additional side effects, like logging, or undo and redo -/// functionality. -/// -/// However, this [RequestFocusAction] is the default action associated with the -/// [key] in the [WidgetsApp], and it simply requests focus and has no side -/// effects. -class RequestFocusAction extends _RequestFocusActionBase { - /// Creates a [RequestFocusAction] with a fixed [key]. - RequestFocusAction() : super(key); - - /// The [LocalKey] that uniquely identifies this action to an [Intent]. - static const LocalKey key = ValueKey(RequestFocusAction); - - @override - void invoke(FocusNode node, Intent tag) { - super.invoke(node, tag); - node.requestFocus(); - } -} - -/// An [Action] that moves the focus to the next focusable node in the focus -/// order. -/// -/// This action is the default action registered for the [key], and by default -/// is bound to the [LogicalKeyboardKey.tab] key in the [WidgetsApp]. -class NextFocusAction extends _RequestFocusActionBase { - /// Creates a [NextFocusAction] with a fixed [key]; - NextFocusAction() : super(key); - - /// The [LocalKey] that uniquely identifies this action to an [Intent]. - static const LocalKey key = ValueKey(NextFocusAction); - - @override - void invoke(FocusNode node, Intent tag) { - super.invoke(node, tag); - node.nextFocus(); - } -} - -/// An [Action] that moves the focus to the previous focusable node in the focus -/// order. -/// -/// This action is the default action registered for the [key], and by default -/// is bound to a combination of the [LogicalKeyboardKey.tab] key and the -/// [LogicalKeyboardKey.shift] key in the [WidgetsApp]. -class PreviousFocusAction extends _RequestFocusActionBase { - /// Creates a [PreviousFocusAction] with a fixed [key]; - PreviousFocusAction() : super(key); - - /// The [LocalKey] that uniquely identifies this action to an [Intent]. - static const LocalKey key = ValueKey(PreviousFocusAction); - - @override - void invoke(FocusNode node, Intent tag) { - super.invoke(node, tag); - node.previousFocus(); - } -} - -/// An [Intent] that represents moving to the next focusable node in the given -/// [direction]. -/// -/// This is the [Intent] bound by default to the [LogicalKeyboardKey.arrowUp], -/// [LogicalKeyboardKey.arrowDown], [LogicalKeyboardKey.arrowLeft], and -/// [LogicalKeyboardKey.arrowRight] keys in the [WidgetsApp], with the -/// appropriate associated directions. -class DirectionalFocusIntent extends Intent { - /// Creates a [DirectionalFocusIntent] with a fixed [key], and the given - /// [direction]. - const DirectionalFocusIntent(this.direction) : super(DirectionalFocusAction.key); - - /// The direction in which to look for the next focusable node when the - /// associated [DirectionalFocusAction] is invoked. - final TraversalDirection direction; -} - -/// An [Action] that moves the focus to the focusable node in the given -/// [direction] configured by the associated [DirectionalFocusIntent]. -/// -/// This is the [Action] associated with the [key] and bound by default to the -/// [LogicalKeyboardKey.arrowUp], [LogicalKeyboardKey.arrowDown], -/// [LogicalKeyboardKey.arrowLeft], and [LogicalKeyboardKey.arrowRight] keys in -/// the [WidgetsApp], with the appropriate associated directions. -class DirectionalFocusAction extends _RequestFocusActionBase { - /// Creates a [DirectionalFocusAction] with a fixed [key]; - DirectionalFocusAction() : super(key); - - /// The [LocalKey] that uniquely identifies this action to [DirectionalFocusIntent]. - static const LocalKey key = ValueKey(DirectionalFocusAction); - - /// The direction in which to look for the next focusable node when invoked. - TraversalDirection direction; - - @override - void invoke(FocusNode node, DirectionalFocusIntent tag) { - super.invoke(node, tag); - final DirectionalFocusIntent args = tag; - node.focusInDirection(args.direction); - } -} diff --git a/packages/flutter/test/material/ink_paint_test.dart b/packages/flutter/test/material/ink_paint_test.dart index 643f81455c0..03cd7c10521 100644 --- a/packages/flutter/test/material/ink_paint_test.dart +++ b/packages/flutter/test/material/ink_paint_test.dart @@ -4,7 +4,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import '../rendering/mock_canvas.dart'; @@ -128,21 +127,17 @@ void main() { ..translate(x: 0.0, y: 0.0) ..translate(x: tapDownOffset.dx, y: tapDownOffset.dy) ..something((Symbol method, List arguments) { - if (method != #drawCircle) { + if (method != #drawCircle) return false; - } final Offset center = arguments[0]; final double radius = arguments[1]; final Paint paint = arguments[2]; - if (offsetsAreClose(center, expectedCenter) && - radiiAreClose(radius, expectedRadius) && - paint.color.alpha == expectedAlpha) { + if (offsetsAreClose(center, expectedCenter) && radiiAreClose(radius, expectedRadius) && paint.color.alpha == expectedAlpha) return true; - } throw ''' Expected: center == $expectedCenter, radius == $expectedRadius, alpha == $expectedAlpha Found: center == $center radius == $radius alpha == ${paint.color.alpha}'''; - }, + } ); } @@ -256,102 +251,6 @@ void main() { await gesture.up(); }, skip: isBrowser); - testWidgets('The InkWell widget renders an ActivateAction-induced ink ripple', (WidgetTester tester) async { - const Color highlightColor = Color(0xAAFF0000); - const Color splashColor = Color(0xB40000FF); - final BorderRadius borderRadius = BorderRadius.circular(6.0); - - final FocusNode focusNode = FocusNode(debugLabel: 'Test Node'); - await tester.pumpWidget( - Shortcuts( - shortcuts: { - LogicalKeySet(LogicalKeyboardKey.enter): const Intent(ActivateAction.key), - }, - child: Directionality( - textDirection: TextDirection.ltr, - child: Material( - child: Center( - child: Container( - width: 100.0, - height: 100.0, - child: InkWell( - borderRadius: borderRadius, - highlightColor: highlightColor, - splashColor: splashColor, - focusNode: focusNode, - onTap: () { }, - radius: 100.0, - splashFactory: InkRipple.splashFactory, - ), - ), - ), - ), - ), - ), - ); - - final Offset topLeft = tester.getTopLeft(find.byType(InkWell)); - final Offset inkWellCenter = tester.getCenter(find.byType(InkWell)) - topLeft; - - // Now activate it with a keypress. - focusNode.requestFocus(); - await tester.pumpAndSettle(); - - await tester.sendKeyEvent(LogicalKeyboardKey.enter); - await tester.pump(); - - final RenderBox box = Material.of(tester.element(find.byType(InkWell))) as dynamic; - - bool offsetsAreClose(Offset a, Offset b) => (a - b).distance < 1.0; - bool radiiAreClose(double a, double b) => (a - b).abs() < 1.0; - - PaintPattern ripplePattern(double expectedRadius, int expectedAlpha) { - return paints - ..translate(x: 0.0, y: 0.0) - ..translate(x: topLeft.dx, y: topLeft.dy) - ..something((Symbol method, List arguments) { - if (method != #drawCircle) { - return false; - } - final Offset center = arguments[0]; - final double radius = arguments[1]; - final Paint paint = arguments[2]; - if (offsetsAreClose(center, inkWellCenter) && - radiiAreClose(radius, expectedRadius) && - paint.color.alpha == expectedAlpha) { - return true; - } - throw ''' - Expected: center == $inkWellCenter, radius == $expectedRadius, alpha == $expectedAlpha - Found: center == $center radius == $radius alpha == ${paint.color.alpha}'''; - }, - ); - } - - // ripplePattern always add a translation of topLeft. - expect(box, ripplePattern(30.0, 0)); - - // The ripple fades in for 75ms. During that time its alpha is eased from - // 0 to the splashColor's alpha value. - await tester.pump(const Duration(milliseconds: 50)); - expect(box, ripplePattern(56.0, 120)); - - // At 75ms the ripple has faded in: it's alpha matches the splashColor's - // alpha. - await tester.pump(const Duration(milliseconds: 25)); - expect(box, ripplePattern(73.0, 180)); - - // At this point the splash radius has expanded to its limit: 5 past the - // ink well's radius parameter. The fade-out is about to start. - // The fade-out begins at 225ms = 50ms + 25ms + 150ms. - await tester.pump(const Duration(milliseconds: 150)); - expect(box, ripplePattern(105.0, 180)); - - // After another 150ms the fade-out is complete. - await tester.pump(const Duration(milliseconds: 150)); - expect(box, ripplePattern(105.0, 0)); - }); - testWidgets('Cancel an InkRipple that was disposed when its animation ended', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/14391 await tester.pumpWidget( @@ -432,4 +331,5 @@ void main() { throw 'Expected: paint.color.alpha == 0, found: ${paint.color.alpha}'; })); }); + } diff --git a/packages/flutter/test/material/ink_well_test.dart b/packages/flutter/test/material/ink_well_test.dart index dfad562e235..9b4a8f93b47 100644 --- a/packages/flutter/test/material/ink_well_test.dart +++ b/packages/flutter/test/material/ink_well_test.dart @@ -103,9 +103,9 @@ void main() { splashColor: const Color(0xffff0000), focusColor: const Color(0xff0000ff), highlightColor: const Color(0xf00fffff), - onTap: () { }, - onLongPress: () { }, - onHover: (bool hover) { }, + onTap: () {}, + onLongPress: () {}, + onHover: (bool hover) {}, ), ), ), @@ -123,29 +123,29 @@ void main() { testWidgets('ink response changes color on focus', (WidgetTester tester) async { WidgetsBinding.instance.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; final FocusNode focusNode = FocusNode(debugLabel: 'Ink Focus'); - await tester.pumpWidget( - Material( - child: Directionality( - textDirection: TextDirection.ltr, - child: Center( + await tester.pumpWidget(Material( + child: Directionality( + textDirection: TextDirection.ltr, + child: Center( + child: Focus( + focusNode: focusNode, child: Container( width: 100, height: 100, child: InkWell( - focusNode: focusNode, hoverColor: const Color(0xff00ff00), splashColor: const Color(0xffff0000), focusColor: const Color(0xff0000ff), highlightColor: const Color(0xf00fffff), - onTap: () { }, - onLongPress: () { }, - onHover: (bool hover) { }, + onTap: () {}, + onLongPress: () {}, + onHover: (bool hover) {}, ), ), ), ), ), - ); + )); await tester.pumpAndSettle(); final RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures'); expect(inkFeatures, paintsExactlyCountTimes(#rect, 0)); @@ -172,9 +172,9 @@ void main() { splashColor: const Color(0xffff0000), focusColor: const Color(0xff0000ff), highlightColor: const Color(0xf00fffff), - onTap: () { }, - onLongPress: () { }, - onHover: (bool hover) { }, + onTap: () {}, + onLongPress: () {}, + onHover: (bool hover) {}, ), ), ), @@ -206,8 +206,8 @@ void main() { textDirection: TextDirection.ltr, child: Center( child: InkWell( - onTap: () { }, - onLongPress: () { }, + onTap: () {}, + onLongPress: () {}, ), ), ), @@ -234,8 +234,8 @@ void main() { textDirection: TextDirection.ltr, child: Center( child: InkWell( - onTap: () { }, - onLongPress: () { }, + onTap: () {}, + onLongPress: () {}, enableFeedback: false, ), ), @@ -301,7 +301,7 @@ void main() { textDirection: TextDirection.ltr, child: Material( child: InkWell( - onTap: () { }, + onTap: () {}, child: const Text('Button'), ), ), @@ -312,7 +312,7 @@ void main() { textDirection: TextDirection.ltr, child: Material( child: InkWell( - onTap: () { }, + onTap: () {}, child: const Text('Button'), excludeFromSemantics: true, ), diff --git a/packages/flutter/test/material/raw_material_button_test.dart b/packages/flutter/test/material/raw_material_button_test.dart index 074f3da4468..bed44b050aa 100644 --- a/packages/flutter/test/material/raw_material_button_test.dart +++ b/packages/flutter/test/material/raw_material_button_test.dart @@ -5,77 +5,12 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; -import 'package:flutter/src/services/keyboard_key.dart'; import 'package:flutter_test/flutter_test.dart'; import '../rendering/mock_canvas.dart'; import '../widgets/semantics_tester.dart'; void main() { - testWidgets('RawMaterialButton responds when tapped', (WidgetTester tester) async { - bool pressed = false; - const Color splashColor = Color(0xff00ff00); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: Center( - child: RawMaterialButton( - splashColor: splashColor, - onPressed: () { pressed = true; }, - child: const Text('BUTTON'), - ), - ), - ), - ); - - await tester.tap(find.text('BUTTON')); - await tester.pump(const Duration(milliseconds: 10)); - - final RenderBox splash = Material.of(tester.element(find.byType(InkWell))) as dynamic; - expect(splash, paints..circle(color: splashColor)); - - await tester.pumpAndSettle(); - - expect(pressed, isTrue); - }); - - testWidgets('RawMaterialButton responds to shortcut when activated', (WidgetTester tester) async { - bool pressed = false; - final FocusNode focusNode = FocusNode(debugLabel: 'Test Button'); - const Color splashColor = Color(0xff00ff00); - await tester.pumpWidget( - Shortcuts( - shortcuts: { - LogicalKeySet(LogicalKeyboardKey.enter): const Intent(ActivateAction.key), - }, - child: Directionality( - textDirection: TextDirection.ltr, - child: Center( - child: RawMaterialButton( - splashColor: splashColor, - focusNode: focusNode, - onPressed: () { pressed = true; }, - child: const Text('BUTTON'), - ), - ), - ), - ), - ); - - focusNode.requestFocus(); - await tester.pump(); - - await tester.sendKeyEvent(LogicalKeyboardKey.enter); - await tester.pump(const Duration(milliseconds: 10)); - - final RenderBox splash = Material.of(tester.element(find.byType(InkWell))) as dynamic; - expect(splash, paints..circle(color: splashColor)); - - await tester.pumpAndSettle(); - - expect(pressed, isTrue); - }); - testWidgets('materialTapTargetSize.padded expands hit test area', (WidgetTester tester) async { int pressed = 0; diff --git a/packages/flutter/test/widgets/actions_test.dart b/packages/flutter/test/widgets/actions_test.dart index 9533598c072..1a7454f0548 100644 --- a/packages/flutter/test/widgets/actions_test.dart +++ b/packages/flutter/test/widgets/actions_test.dart @@ -324,11 +324,11 @@ void main() { ).debugFillProperties(builder); final List description = builder.properties - .where((DiagnosticsNode node) { - return !node.isFiltered(DiagnosticLevel.info); - }) - .map((DiagnosticsNode node) => node.toString()) - .toList(); + .where((DiagnosticsNode node) { + return !node.isFiltered(DiagnosticLevel.info); + }) + .map((DiagnosticsNode node) => node.toString()) + .toList(); expect(description[0], equalsIgnoringHashCodes('dispatcher: ActionDispatcher#00000')); expect(description[1], equals('actions: {[<\'bar\'>]: Closure: () => TestAction}')); diff --git a/packages/flutter/test/widgets/focus_traversal_test.dart b/packages/flutter/test/widgets/focus_traversal_test.dart index bfc7824b299..b2ce03bb47e 100644 --- a/packages/flutter/test/widgets/focus_traversal_test.dart +++ b/packages/flutter/test/widgets/focus_traversal_test.dart @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter/painting.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; @@ -916,112 +914,5 @@ void main() { expect(focusCenter.hasFocus, isFalse); expect(focusTop.hasFocus, isTrue); }); - testWidgets('Focus traversal actions are invoked when shortcuts are used.', (WidgetTester tester) async { - final GlobalKey upperLeftKey = GlobalKey(debugLabel: 'upperLeftKey'); - final GlobalKey upperRightKey = GlobalKey(debugLabel: 'upperRightKey'); - final GlobalKey lowerLeftKey = GlobalKey(debugLabel: 'lowerLeftKey'); - final GlobalKey lowerRightKey = GlobalKey(debugLabel: 'lowerRightKey'); - - await tester.pumpWidget( - WidgetsApp( - color: const Color(0xFFFFFFFF), - onGenerateRoute: (RouteSettings settings) { - return TestRoute( - child: Directionality( - textDirection: TextDirection.ltr, - child: FocusScope( - debugLabel: 'scope', - child: Column( - children: [ - Row( - children: [ - Focus( - autofocus: true, - debugLabel: 'upperLeft', - child: Container(width: 100, height: 100, key: upperLeftKey), - ), - Focus( - debugLabel: 'upperRight', - child: Container(width: 100, height: 100, key: upperRightKey), - ), - ], - ), - Row( - children: [ - Focus( - debugLabel: 'lowerLeft', - child: Container(width: 100, height: 100, key: lowerLeftKey), - ), - Focus( - debugLabel: 'lowerRight', - child: Container(width: 100, height: 100, key: lowerRightKey), - ), - ], - ), - ], - ), - ), - ), - ); - }, - ), - ); - - // Initial focus happens. - expect(Focus.of(upperLeftKey.currentContext).hasPrimaryFocus, isTrue); - await tester.sendKeyEvent(LogicalKeyboardKey.tab); - expect(Focus.of(upperRightKey.currentContext).hasPrimaryFocus, isTrue); - // Initial focus happens. - await tester.sendKeyEvent(LogicalKeyboardKey.tab); - expect(Focus.of(lowerLeftKey.currentContext).hasPrimaryFocus, isTrue); - // Initial focus happens. - await tester.sendKeyEvent(LogicalKeyboardKey.tab); - expect(Focus.of(lowerRightKey.currentContext).hasPrimaryFocus, isTrue); - // Initial focus happens. - await tester.sendKeyEvent(LogicalKeyboardKey.tab); - expect(Focus.of(upperLeftKey.currentContext).hasPrimaryFocus, isTrue); - - await tester.sendKeyDownEvent(LogicalKeyboardKey.shift); - await tester.sendKeyEvent(LogicalKeyboardKey.tab); - await tester.sendKeyUpEvent(LogicalKeyboardKey.shift); - expect(Focus.of(lowerRightKey.currentContext).hasPrimaryFocus, isTrue); - // Initial focus happens. - await tester.sendKeyDownEvent(LogicalKeyboardKey.shift); - await tester.sendKeyEvent(LogicalKeyboardKey.tab); - await tester.sendKeyUpEvent(LogicalKeyboardKey.shift); - expect(Focus.of(lowerLeftKey.currentContext).hasPrimaryFocus, isTrue); - // Initial focus happens. - await tester.sendKeyDownEvent(LogicalKeyboardKey.shift); - await tester.sendKeyEvent(LogicalKeyboardKey.tab); - await tester.sendKeyUpEvent(LogicalKeyboardKey.shift); - expect(Focus.of(upperRightKey.currentContext).hasPrimaryFocus, isTrue); - // Initial focus happens. - await tester.sendKeyDownEvent(LogicalKeyboardKey.shift); - await tester.sendKeyEvent(LogicalKeyboardKey.tab); - await tester.sendKeyUpEvent(LogicalKeyboardKey.shift); - expect(Focus.of(upperLeftKey.currentContext).hasPrimaryFocus, isTrue); - - // Traverse in a direction - await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight); - expect(Focus.of(upperRightKey.currentContext).hasPrimaryFocus, isTrue); - // Initial focus happens. - await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown); - expect(Focus.of(lowerRightKey.currentContext).hasPrimaryFocus, isTrue); - // Initial focus happens. - await tester.sendKeyEvent(LogicalKeyboardKey.arrowLeft); - expect(Focus.of(lowerLeftKey.currentContext).hasPrimaryFocus, isTrue); - // Initial focus happens. - await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp); - expect(Focus.of(upperLeftKey.currentContext).hasPrimaryFocus, isTrue); - }); }); } - -class TestRoute extends PageRouteBuilder { - TestRoute({Widget child}) - : super( - pageBuilder: (BuildContext _, Animation __, Animation ___) { - return child; - }, - ); -}