Unverified Commit eb3e2f55 authored by Greg Spencer's avatar Greg Spencer Committed by GitHub

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.
parent b07056b1
......@@ -235,9 +235,9 @@ abstract class UndoableAction extends Action {
@override
@mustCallSuper
void invoke(FocusNode node, Intent tag) {
void invoke(FocusNode node, Intent intent) {
invocationNode = node;
invocationIntent = tag;
invocationIntent = intent;
}
@override
......@@ -253,8 +253,8 @@ class SetFocusActionBase extends UndoableAction {
FocusNode _previousFocus;
@override
void invoke(FocusNode node, Intent tag) {
super.invoke(node, tag);
void invoke(FocusNode node, Intent intent) {
super.invoke(node, intent);
_previousFocus = WidgetsBinding.instance.focusManager.primaryFocus;
node.requestFocus();
}
......@@ -292,8 +292,8 @@ class SetFocusAction extends SetFocusActionBase {
static const LocalKey key = ValueKey<Type>(SetFocusAction);
@override
void invoke(FocusNode node, Intent tag) {
super.invoke(node, tag);
void invoke(FocusNode node, Intent intent) {
super.invoke(node, intent);
node.requestFocus();
}
}
......@@ -305,8 +305,8 @@ class NextFocusAction extends SetFocusActionBase {
static const LocalKey key = ValueKey<Type>(NextFocusAction);
@override
void invoke(FocusNode node, Intent tag) {
super.invoke(node, tag);
void invoke(FocusNode node, Intent intent) {
super.invoke(node, intent);
node.nextFocus();
}
}
......@@ -317,8 +317,8 @@ class PreviousFocusAction extends SetFocusActionBase {
static const LocalKey key = ValueKey<Type>(PreviousFocusAction);
@override
void invoke(FocusNode node, Intent tag) {
super.invoke(node, tag);
void invoke(FocusNode node, Intent intent) {
super.invoke(node, intent);
node.previousFocus();
}
}
......@@ -337,9 +337,9 @@ class DirectionalFocusAction extends SetFocusActionBase {
TraversalDirection direction;
@override
void invoke(FocusNode node, DirectionalFocusIntent tag) {
super.invoke(node, tag);
final DirectionalFocusIntent args = tag;
void invoke(FocusNode node, DirectionalFocusIntent intent) {
super.invoke(node, intent);
final DirectionalFocusIntent args = intent;
node.focusInDirection(args.direction);
}
}
......
......@@ -29,11 +29,10 @@ class Intent extends Diagnosticable {
/// An intent that can't be mapped to an action.
///
/// This Intent is prevented from being mapped to an action in the
/// [ActionDispatcher], and as such can be used to indicate that a shortcut
/// should not do anything, allowing a shortcut defined at a higher level to
/// be disabled at a deeper level in the widget hierarchy.
static const Intent doNothing = Intent(ValueKey<Type>(Intent));
/// This Intent is mapped to an action in the [WidgetsApp] that does nothing,
/// so that it can be bound to a key in a [Shortcuts] widget in order to
/// disable a key binding made above it in the hierarchy.
static const Intent doNothing = DoNothingIntent();
/// The key for the action this intent is associated with.
final LocalKey key;
......@@ -95,7 +94,7 @@ abstract class Action extends Diagnosticable {
/// needed in the action, use [ActionDispatcher.invokeFocusedAction] instead.
@protected
@mustCallSuper
void invoke(FocusNode node, covariant Intent tag);
void invoke(FocusNode node, covariant Intent intent);
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
......@@ -134,7 +133,7 @@ class CallbackAction extends Action {
final OnInvokeCallback onInvoke;
@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.
......@@ -224,6 +223,7 @@ class Actions extends InheritedWidget {
// Stop visiting.
return false;
}
element.visitAncestorElements(visitAncestorElement);
}
return dispatcher ?? const ActionDispatcher();
......@@ -278,9 +278,6 @@ class Actions extends InheritedWidget {
///
/// Setting `nullOk` to true means that if no ambient [Actions] widget is
/// 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(
BuildContext context,
Intent intent, {
......@@ -292,10 +289,6 @@ class Actions extends InheritedWidget {
Element actionsElement;
Action action;
if (identical(intent, Intent.doNothing)) {
return false;
}
bool visitAncestorElement(Element element) {
if (element.widget is! Actions) {
// Continue visiting.
......@@ -358,3 +351,29 @@ class Actions extends InheritedWidget {
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/rendering.dart';
import 'actions.dart';
import 'banner.dart';
import 'basic.dart';
import 'binding.dart';
......@@ -1194,14 +1195,19 @@ class _WidgetsAppState extends State<WidgetsApp> implements WidgetsBindingObserv
assert(_debugCheckLocalizations(appLocale));
return DefaultFocusTraversal(
policy: ReadingOrderTraversalPolicy(),
child: MediaQuery(
data: MediaQueryData.fromWindow(WidgetsBinding.instance.window),
child: Localizations(
locale: appLocale,
delegates: _localizationsDelegates.toList(),
child: title,
return Actions(
actions: <LocalKey, ActionFactory>{
DoNothingAction.key: () => const DoNothingAction(),
},
child: DefaultFocusTraversal(
policy: ReadingOrderTraversalPolicy(),
child: MediaQuery(
data: MediaQueryData.fromWindow(WidgetsBinding.instance.window),
child: Localizations(
locale: appLocale,
delegates: _localizationsDelegates.toList(),
child: title,
),
),
),
);
......
......@@ -302,11 +302,11 @@ void main() {
).debugFillProperties(builder);
final List<String> 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: {}'));
......
......@@ -37,22 +37,6 @@ class TestIntent extends Intent {
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 {
TestShortcutManager(this.keys);
......@@ -143,8 +127,8 @@ void main() {
LogicalKeyboardKey.keyD,
LogicalKeyboardKey.keyC,
LogicalKeyboardKey.keyB,
LogicalKeyboardKey.keyA,}
);
LogicalKeyboardKey.keyA,
});
final Map<LogicalKeySet, String> map = <LogicalKeySet, String>{set1: 'one'};
expect(set2 == set3, isTrue);
expect(set2 == set4, isTrue);
......@@ -192,10 +176,12 @@ void main() {
await tester.pumpWidget(
Actions(
actions: <LocalKey, ActionFactory>{
TestAction.key: () => TestAction(onInvoke: (FocusNode node, Intent intent) {
invoked = true;
return true;
}),
TestAction.key: () => TestAction(
onInvoke: (FocusNode node, Intent intent) {
invoked = true;
return true;
},
),
},
child: Shortcuts(
manager: testManager,
......@@ -228,14 +214,16 @@ void main() {
},
child: Actions(
actions: <LocalKey, ActionFactory>{
TestAction.key: () => TestAction(onInvoke: (FocusNode node, Intent intent) {
invoked = true;
return true;
}),
TestAction.key: () => TestAction(
onInvoke: (FocusNode node, Intent intent) {
invoked = true;
return true;
},
),
},
child: Shortcuts(
shortcuts: <LogicalKeySet, Intent>{
LogicalKeySet(LogicalKeyboardKey.keyA): const DoNothingIntent(),
LogicalKeySet(LogicalKeyboardKey.keyA): Intent.doNothing,
},
child: Focus(
autofocus: true,
......@@ -251,5 +239,45 @@ void main() {
expect(invoked, isTrue);
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);
});
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment