mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Address previous comments, fix Intent.doNothing. (#41417)
This addresses comments in the original PR (#41245) that introduced Intent.doNothing, adds tests, and fixes an issue with it.
This commit is contained in:
parent
b07056b107
commit
eb3e2f559f
@ -235,9 +235,9 @@ abstract class UndoableAction extends Action {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
@mustCallSuper
|
@mustCallSuper
|
||||||
void invoke(FocusNode node, Intent tag) {
|
void invoke(FocusNode node, Intent intent) {
|
||||||
invocationNode = node;
|
invocationNode = node;
|
||||||
invocationIntent = tag;
|
invocationIntent = intent;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -253,8 +253,8 @@ class SetFocusActionBase extends UndoableAction {
|
|||||||
FocusNode _previousFocus;
|
FocusNode _previousFocus;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void invoke(FocusNode node, Intent tag) {
|
void invoke(FocusNode node, Intent intent) {
|
||||||
super.invoke(node, tag);
|
super.invoke(node, intent);
|
||||||
_previousFocus = WidgetsBinding.instance.focusManager.primaryFocus;
|
_previousFocus = WidgetsBinding.instance.focusManager.primaryFocus;
|
||||||
node.requestFocus();
|
node.requestFocus();
|
||||||
}
|
}
|
||||||
@ -292,8 +292,8 @@ class SetFocusAction extends SetFocusActionBase {
|
|||||||
static const LocalKey key = ValueKey<Type>(SetFocusAction);
|
static const LocalKey key = ValueKey<Type>(SetFocusAction);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void invoke(FocusNode node, Intent tag) {
|
void invoke(FocusNode node, Intent intent) {
|
||||||
super.invoke(node, tag);
|
super.invoke(node, intent);
|
||||||
node.requestFocus();
|
node.requestFocus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -305,8 +305,8 @@ class NextFocusAction extends SetFocusActionBase {
|
|||||||
static const LocalKey key = ValueKey<Type>(NextFocusAction);
|
static const LocalKey key = ValueKey<Type>(NextFocusAction);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void invoke(FocusNode node, Intent tag) {
|
void invoke(FocusNode node, Intent intent) {
|
||||||
super.invoke(node, tag);
|
super.invoke(node, intent);
|
||||||
node.nextFocus();
|
node.nextFocus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -317,8 +317,8 @@ class PreviousFocusAction extends SetFocusActionBase {
|
|||||||
static const LocalKey key = ValueKey<Type>(PreviousFocusAction);
|
static const LocalKey key = ValueKey<Type>(PreviousFocusAction);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void invoke(FocusNode node, Intent tag) {
|
void invoke(FocusNode node, Intent intent) {
|
||||||
super.invoke(node, tag);
|
super.invoke(node, intent);
|
||||||
node.previousFocus();
|
node.previousFocus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -337,9 +337,9 @@ class DirectionalFocusAction extends SetFocusActionBase {
|
|||||||
TraversalDirection direction;
|
TraversalDirection direction;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void invoke(FocusNode node, DirectionalFocusIntent tag) {
|
void invoke(FocusNode node, DirectionalFocusIntent intent) {
|
||||||
super.invoke(node, tag);
|
super.invoke(node, intent);
|
||||||
final DirectionalFocusIntent args = tag;
|
final DirectionalFocusIntent args = intent;
|
||||||
node.focusInDirection(args.direction);
|
node.focusInDirection(args.direction);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,11 +29,10 @@ class Intent extends Diagnosticable {
|
|||||||
|
|
||||||
/// An intent that can't be mapped to an action.
|
/// An intent that can't be mapped to an action.
|
||||||
///
|
///
|
||||||
/// This Intent is prevented from being mapped to an action in the
|
/// This Intent is mapped to an action in the [WidgetsApp] that does nothing,
|
||||||
/// [ActionDispatcher], and as such can be used to indicate that a shortcut
|
/// so that it can be bound to a key in a [Shortcuts] widget in order to
|
||||||
/// should not do anything, allowing a shortcut defined at a higher level to
|
/// disable a key binding made above it in the hierarchy.
|
||||||
/// be disabled at a deeper level in the widget hierarchy.
|
static const Intent doNothing = DoNothingIntent();
|
||||||
static const Intent doNothing = Intent(ValueKey<Type>(Intent));
|
|
||||||
|
|
||||||
/// The key for the action this intent is associated with.
|
/// The key for the action this intent is associated with.
|
||||||
final LocalKey key;
|
final LocalKey key;
|
||||||
@ -95,7 +94,7 @@ abstract class Action extends Diagnosticable {
|
|||||||
/// needed in the action, use [ActionDispatcher.invokeFocusedAction] instead.
|
/// needed in the action, use [ActionDispatcher.invokeFocusedAction] instead.
|
||||||
@protected
|
@protected
|
||||||
@mustCallSuper
|
@mustCallSuper
|
||||||
void invoke(FocusNode node, covariant Intent tag);
|
void invoke(FocusNode node, covariant Intent intent);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||||
@ -134,7 +133,7 @@ class CallbackAction extends Action {
|
|||||||
final OnInvokeCallback onInvoke;
|
final OnInvokeCallback onInvoke;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void invoke(FocusNode node, Intent tag) => onInvoke.call(node, tag);
|
void invoke(FocusNode node, Intent intent) => onInvoke.call(node, intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An action manager that simply invokes the actions given to it.
|
/// An action manager that simply invokes the actions given to it.
|
||||||
@ -224,6 +223,7 @@ class Actions extends InheritedWidget {
|
|||||||
// Stop visiting.
|
// Stop visiting.
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
element.visitAncestorElements(visitAncestorElement);
|
element.visitAncestorElements(visitAncestorElement);
|
||||||
}
|
}
|
||||||
return dispatcher ?? const ActionDispatcher();
|
return dispatcher ?? const ActionDispatcher();
|
||||||
@ -278,9 +278,6 @@ class Actions extends InheritedWidget {
|
|||||||
///
|
///
|
||||||
/// Setting `nullOk` to true means that if no ambient [Actions] widget is
|
/// Setting `nullOk` to true means that if no ambient [Actions] widget is
|
||||||
/// found, then this method will return false instead of throwing.
|
/// found, then this method will return false instead of throwing.
|
||||||
///
|
|
||||||
/// If the `intent` argument is [Intent.doNothing], then this function will
|
|
||||||
/// return false, without looking for a matching action.
|
|
||||||
static bool invoke(
|
static bool invoke(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
Intent intent, {
|
Intent intent, {
|
||||||
@ -292,10 +289,6 @@ class Actions extends InheritedWidget {
|
|||||||
Element actionsElement;
|
Element actionsElement;
|
||||||
Action action;
|
Action action;
|
||||||
|
|
||||||
if (identical(intent, Intent.doNothing)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool visitAncestorElement(Element element) {
|
bool visitAncestorElement(Element element) {
|
||||||
if (element.widget is! Actions) {
|
if (element.widget is! Actions) {
|
||||||
// Continue visiting.
|
// Continue visiting.
|
||||||
@ -358,3 +351,29 @@ class Actions extends InheritedWidget {
|
|||||||
properties.add(DiagnosticsProperty<Map<LocalKey, ActionFactory>>('actions', actions));
|
properties.add(DiagnosticsProperty<Map<LocalKey, ActionFactory>>('actions', actions));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An [Action], that, as the name implies, does nothing.
|
||||||
|
///
|
||||||
|
/// This action is bound to the [Intent.doNothing] intent inside of
|
||||||
|
/// [WidgetsApp.build] so that a [Shortcuts] widget can bind a key to it to
|
||||||
|
/// override another shortcut binding defined above it in the hierarchy.
|
||||||
|
class DoNothingAction extends Action {
|
||||||
|
/// Const constructor for [DoNothingAction].
|
||||||
|
const DoNothingAction() : super(key);
|
||||||
|
|
||||||
|
/// The Key used for the [DoNothingIntent] intent, and registered at the top
|
||||||
|
/// level actions in [WidgetsApp.build].
|
||||||
|
static const LocalKey key = ValueKey<Type>(DoNothingAction);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void invoke(FocusNode node, Intent intent) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An [Intent] that can be used to disable [Shortcuts] key bindings defined
|
||||||
|
/// above a widget in the hierarchy.
|
||||||
|
///
|
||||||
|
/// The [Intent.doNothing] intent is of this type.
|
||||||
|
class DoNothingIntent extends Intent {
|
||||||
|
/// Const constructor for [DoNothingIntent].
|
||||||
|
const DoNothingIntent() : super(DoNothingAction.key);
|
||||||
|
}
|
||||||
|
@ -8,6 +8,7 @@ import 'dart:collection' show HashMap;
|
|||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
|
|
||||||
|
import 'actions.dart';
|
||||||
import 'banner.dart';
|
import 'banner.dart';
|
||||||
import 'basic.dart';
|
import 'basic.dart';
|
||||||
import 'binding.dart';
|
import 'binding.dart';
|
||||||
@ -1194,7 +1195,11 @@ class _WidgetsAppState extends State<WidgetsApp> implements WidgetsBindingObserv
|
|||||||
|
|
||||||
assert(_debugCheckLocalizations(appLocale));
|
assert(_debugCheckLocalizations(appLocale));
|
||||||
|
|
||||||
return DefaultFocusTraversal(
|
return Actions(
|
||||||
|
actions: <LocalKey, ActionFactory>{
|
||||||
|
DoNothingAction.key: () => const DoNothingAction(),
|
||||||
|
},
|
||||||
|
child: DefaultFocusTraversal(
|
||||||
policy: ReadingOrderTraversalPolicy(),
|
policy: ReadingOrderTraversalPolicy(),
|
||||||
child: MediaQuery(
|
child: MediaQuery(
|
||||||
data: MediaQueryData.fromWindow(WidgetsBinding.instance.window),
|
data: MediaQueryData.fromWindow(WidgetsBinding.instance.window),
|
||||||
@ -1204,6 +1209,7 @@ class _WidgetsAppState extends State<WidgetsApp> implements WidgetsBindingObserv
|
|||||||
child: title,
|
child: title,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,22 +37,6 @@ class TestIntent extends Intent {
|
|||||||
const TestIntent() : super(TestAction.key);
|
const TestIntent() : super(TestAction.key);
|
||||||
}
|
}
|
||||||
|
|
||||||
class DoNothingAction extends Action {
|
|
||||||
const DoNothingAction({
|
|
||||||
@required OnInvokeCallback onInvoke,
|
|
||||||
}) : assert(onInvoke != null),
|
|
||||||
super(key);
|
|
||||||
|
|
||||||
static const LocalKey key = ValueKey<Type>(DoNothingAction);
|
|
||||||
|
|
||||||
@override
|
|
||||||
void invoke(FocusNode node, Intent invocation) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
class DoNothingIntent extends Intent {
|
|
||||||
const DoNothingIntent() : super(DoNothingAction.key);
|
|
||||||
}
|
|
||||||
|
|
||||||
class TestShortcutManager extends ShortcutManager {
|
class TestShortcutManager extends ShortcutManager {
|
||||||
TestShortcutManager(this.keys);
|
TestShortcutManager(this.keys);
|
||||||
|
|
||||||
@ -143,8 +127,8 @@ void main() {
|
|||||||
LogicalKeyboardKey.keyD,
|
LogicalKeyboardKey.keyD,
|
||||||
LogicalKeyboardKey.keyC,
|
LogicalKeyboardKey.keyC,
|
||||||
LogicalKeyboardKey.keyB,
|
LogicalKeyboardKey.keyB,
|
||||||
LogicalKeyboardKey.keyA,}
|
LogicalKeyboardKey.keyA,
|
||||||
);
|
});
|
||||||
final Map<LogicalKeySet, String> map = <LogicalKeySet, String>{set1: 'one'};
|
final Map<LogicalKeySet, String> map = <LogicalKeySet, String>{set1: 'one'};
|
||||||
expect(set2 == set3, isTrue);
|
expect(set2 == set3, isTrue);
|
||||||
expect(set2 == set4, isTrue);
|
expect(set2 == set4, isTrue);
|
||||||
@ -192,10 +176,12 @@ void main() {
|
|||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
Actions(
|
Actions(
|
||||||
actions: <LocalKey, ActionFactory>{
|
actions: <LocalKey, ActionFactory>{
|
||||||
TestAction.key: () => TestAction(onInvoke: (FocusNode node, Intent intent) {
|
TestAction.key: () => TestAction(
|
||||||
|
onInvoke: (FocusNode node, Intent intent) {
|
||||||
invoked = true;
|
invoked = true;
|
||||||
return true;
|
return true;
|
||||||
}),
|
},
|
||||||
|
),
|
||||||
},
|
},
|
||||||
child: Shortcuts(
|
child: Shortcuts(
|
||||||
manager: testManager,
|
manager: testManager,
|
||||||
@ -228,14 +214,16 @@ void main() {
|
|||||||
},
|
},
|
||||||
child: Actions(
|
child: Actions(
|
||||||
actions: <LocalKey, ActionFactory>{
|
actions: <LocalKey, ActionFactory>{
|
||||||
TestAction.key: () => TestAction(onInvoke: (FocusNode node, Intent intent) {
|
TestAction.key: () => TestAction(
|
||||||
|
onInvoke: (FocusNode node, Intent intent) {
|
||||||
invoked = true;
|
invoked = true;
|
||||||
return true;
|
return true;
|
||||||
}),
|
},
|
||||||
|
),
|
||||||
},
|
},
|
||||||
child: Shortcuts(
|
child: Shortcuts(
|
||||||
shortcuts: <LogicalKeySet, Intent>{
|
shortcuts: <LogicalKeySet, Intent>{
|
||||||
LogicalKeySet(LogicalKeyboardKey.keyA): const DoNothingIntent(),
|
LogicalKeySet(LogicalKeyboardKey.keyA): Intent.doNothing,
|
||||||
},
|
},
|
||||||
child: Focus(
|
child: Focus(
|
||||||
autofocus: true,
|
autofocus: true,
|
||||||
@ -251,5 +239,45 @@ void main() {
|
|||||||
expect(invoked, isTrue);
|
expect(invoked, isTrue);
|
||||||
expect(pressedKeys, equals(<LogicalKeyboardKey>[LogicalKeyboardKey.shiftLeft]));
|
expect(pressedKeys, equals(<LogicalKeyboardKey>[LogicalKeyboardKey.shiftLeft]));
|
||||||
});
|
});
|
||||||
|
testWidgets('$Shortcuts can disable a shortcut with Intent.doNothing', (WidgetTester tester) async {
|
||||||
|
final GlobalKey containerKey = GlobalKey();
|
||||||
|
final List<LogicalKeyboardKey> pressedKeys = <LogicalKeyboardKey>[];
|
||||||
|
final TestShortcutManager testManager = TestShortcutManager(pressedKeys);
|
||||||
|
bool invoked = false;
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
home: Shortcuts(
|
||||||
|
manager: testManager,
|
||||||
|
shortcuts: <LogicalKeySet, Intent>{
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.shift): const TestIntent(),
|
||||||
|
},
|
||||||
|
child: Actions(
|
||||||
|
actions: <LocalKey, ActionFactory>{
|
||||||
|
TestAction.key: () => TestAction(
|
||||||
|
onInvoke: (FocusNode node, Intent intent) {
|
||||||
|
invoked = true;
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
child: Shortcuts(
|
||||||
|
shortcuts: <LogicalKeySet, Intent>{
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.shift): Intent.doNothing,
|
||||||
|
},
|
||||||
|
child: Focus(
|
||||||
|
autofocus: true,
|
||||||
|
child: Container(key: containerKey, width: 100, height: 100),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await tester.pump();
|
||||||
|
expect(Shortcuts.of(containerKey.currentContext), isNotNull);
|
||||||
|
await tester.sendKeyDownEvent(LogicalKeyboardKey.shiftLeft);
|
||||||
|
expect(invoked, isFalse);
|
||||||
|
expect(pressedKeys, isEmpty);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user