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
|
||||
@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);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user