diff --git a/packages/flutter/lib/src/widgets/actions.dart b/packages/flutter/lib/src/widgets/actions.dart index e99e4eaf9881..adf544a877c6 100644 --- a/packages/flutter/lib/src/widgets/actions.dart +++ b/packages/flutter/lib/src/widgets/actions.dart @@ -954,7 +954,7 @@ class Actions extends StatefulWidget { final Action? result = _castAction(actions, intent: intent); if (result != null && result.isEnabled(intent)) { // Invoke the action we found using the relevant dispatcher from the Actions - // Element we found. + // element we found. returnValue = _findDispatcher(element).invokeAction(result, intent, context); } return result != null; @@ -1478,7 +1478,8 @@ class ActivateIntent extends Intent { /// * [WidgetsApp.shortcuts], which defines the shortcuts to use in an /// application (and defaults to [WidgetsApp.defaultShortcuts]). class ButtonActivateIntent extends Intent { - /// Creates an intent that the currently focused control, if it's a button. + /// Creates an intent that activates the currently focused control, + /// if it's a button. const ButtonActivateIntent(); } @@ -1581,7 +1582,7 @@ mixin _OverridableActionMixin on Action { bool debugAssertConsumeKeyMutuallyRecursive = false; // The default action to invoke if an enabled override Action can't be found - // using [lookupContext]; + // using [lookupContext]. Action get defaultAction; // The [BuildContext] used to find the override of this [Action]. diff --git a/packages/flutter/lib/src/widgets/focus_manager.dart b/packages/flutter/lib/src/widgets/focus_manager.dart index 329f2e649d15..9973ab4ba97f 100644 --- a/packages/flutter/lib/src/widgets/focus_manager.dart +++ b/packages/flutter/lib/src/widgets/focus_manager.dart @@ -110,7 +110,7 @@ class _Autofocus { // The widget tree is responsible for calling reparent/detach on attached // nodes to keep their parent/manager information up-to-date, so here we can // safely check if the scope/node involved in each autofocus request is - // still attached, and discard the ones are no longer attached to the + // still attached, and discard the ones which are no longer attached to the // original manager. void applyIfValid(FocusManager manager) { final bool shouldApply = (scope.parent != null || identical(scope, manager.rootScope)) @@ -128,8 +128,8 @@ class _Autofocus { /// An attachment point for a [FocusNode]. /// -/// Using a [FocusAttachment] is rarely needed, unless you are building -/// something akin to the [Focus] or [FocusScope] widgets from scratch. +/// Using a [FocusAttachment] is rarely needed, unless building something +/// akin to the [Focus] or [FocusScope] widgets from scratch. /// /// Once created, a [FocusNode] must be attached to the widget tree by its /// _host_ [StatefulWidget] via a [FocusAttachment] object. [FocusAttachment]s @@ -261,8 +261,8 @@ enum UnfocusDisposition { /// /// _Please see the [Focus] and [FocusScope] widgets, which are utility widgets /// that manage their own [FocusNode]s and [FocusScopeNode]s, respectively. If -/// they aren't appropriate, [FocusNode]s can be managed directly, but doing -/// this yourself is rare._ +/// they aren't appropriate, [FocusNode]s can be managed directly, but doing this +/// is rare._ /// /// [FocusNode]s are persistent objects that form a _focus tree_ that is a /// representation of the widgets in the hierarchy that are interested in focus. @@ -1405,8 +1405,8 @@ enum FocusHighlightStrategy { /// The focus manager is responsible for tracking which [FocusNode] has the /// primary input focus (the [primaryFocus]), holding the [FocusScopeNode] that /// is the root of the focus tree (the [rootScope]), and what the current -/// [highlightMode] is. It also distributes key events from [RawKeyboard] to the -/// nodes in the focus tree. +/// [highlightMode] is. It also distributes key events from [KeyEventManager] +/// to the nodes in the focus tree. /// /// The singleton [FocusManager] instance is held by the [WidgetsBinding] as /// [WidgetsBinding.focusManager], and can be conveniently accessed using the diff --git a/packages/flutter/lib/src/widgets/focus_scope.dart b/packages/flutter/lib/src/widgets/focus_scope.dart index f908d32ec940..ca5c8b4480b3 100644 --- a/packages/flutter/lib/src/widgets/focus_scope.dart +++ b/packages/flutter/lib/src/widgets/focus_scope.dart @@ -195,7 +195,7 @@ class Focus extends StatefulWidget { /// {@endtemplate} /// /// A non-null [focusNode] must be supplied if using the - /// [Focus.withExternalFocusNode] constructor is used. + /// [Focus.withExternalFocusNode] constructor. final FocusNode? focusNode; /// {@template flutter.widgets.Focus.autofocus} diff --git a/packages/flutter/lib/src/widgets/focus_traversal.dart b/packages/flutter/lib/src/widgets/focus_traversal.dart index 4bd503dbfd3c..db9ec6463c2a 100644 --- a/packages/flutter/lib/src/widgets/focus_traversal.dart +++ b/packages/flutter/lib/src/widgets/focus_traversal.dart @@ -200,7 +200,7 @@ abstract class FocusTraversalPolicy with Diagnosticable { /// /// See also: /// - /// * [previous], the function that is called to move the focus to the next node. + /// * [previous], the function that is called to move the focus to the previous node. /// * [DirectionalFocusTraversalPolicyMixin.findFirstFocusInDirection], a /// function that finds the first focusable widget in a particular direction. FocusNode findLastFocus(FocusNode currentNode, {bool ignoreCurrentFocus = false}) { @@ -1139,7 +1139,7 @@ class ReadingOrderTraversalPolicy extends FocusTraversalPolicy with DirectionalF // It has to have at least topmost in it if the topmost is not degenerate. assert(topmost.rect.isEmpty || inBandOfTop.isNotEmpty); - // The topmost rect in is in a band by itself, so just return that one. + // The topmost rect is in a band by itself, so just return that one. if (inBandOfTop.length <= 1) { return topmost; } diff --git a/packages/flutter/lib/src/widgets/shortcuts.dart b/packages/flutter/lib/src/widgets/shortcuts.dart index 1fba42b101e2..543c7dfcda0e 100644 --- a/packages/flutter/lib/src/widgets/shortcuts.dart +++ b/packages/flutter/lib/src/widgets/shortcuts.dart @@ -90,7 +90,6 @@ class KeySet { && setEquals(other._keys, _keys); } - // Cached hash code value. Improves [hashCode] performance by 27%-900%, // depending on key set size and read/write ratio. @override @@ -334,8 +333,8 @@ class LogicalKeySet extends KeySet with Diagnosticable } } -/// A [DiagnosticsProperty] which handles formatting a `Map` (the same type as the [Shortcuts.shortcuts] property) so that its +/// A [DiagnosticsProperty] which handles formatting a `Map` +/// (the same type as the [Shortcuts.shortcuts] property) so that its /// diagnostic output is human-readable. class ShortcutMapProperty extends DiagnosticsProperty> { /// Create a diagnostics property for `Map` objects, @@ -1183,7 +1182,7 @@ class ShortcutRegistryEntry { /// /// The registry may be listened to (with [addListener]/[removeListener]) for /// change notifications when the registered shortcuts change. Change -/// notifications take place after the the current frame is drawn, so that +/// notifications take place after the current frame is drawn, so that /// widgets that are not descendants of the registry can listen to it (e.g. in /// overlays). class ShortcutRegistry with ChangeNotifier { diff --git a/packages/flutter/test/widgets/actions_test.dart b/packages/flutter/test/widgets/actions_test.dart index 36db3a3d5f79..044b04636809 100644 --- a/packages/flutter/test/widgets/actions_test.dart +++ b/packages/flutter/test/widgets/actions_test.dart @@ -74,6 +74,7 @@ void main() { expect(result, isTrue); expect(invoked, isTrue); }); + testWidgets('Actions widget can invoke actions with default dispatcher and maybeInvoke', (WidgetTester tester) async { final GlobalKey containerKey = GlobalKey(); bool invoked = false; @@ -1083,6 +1084,7 @@ void main() { action._testInvoke(intent); expect(passedIntent, equals(intent)); }); + testWidgets('VoidCallbackAction', (WidgetTester tester) async { bool called = false; void testCallback() { @@ -1121,6 +1123,7 @@ void main() { expect(description, isEmpty); }); + testWidgets('default Actions debugFillProperties', (WidgetTester tester) async { final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder(); @@ -1146,6 +1149,7 @@ void main() { ]), ); }); + testWidgets('Actions implements debugFillProperties', (WidgetTester tester) async { final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder(); diff --git a/packages/flutter/test/widgets/focus_scope_test.dart b/packages/flutter/test/widgets/focus_scope_test.dart index 08943d3089d3..3edaeb803c20 100644 --- a/packages/flutter/test/widgets/focus_scope_test.dart +++ b/packages/flutter/test/widgets/focus_scope_test.dart @@ -420,7 +420,7 @@ void main() { expect(find.text('b'), findsOneWidget); }); - testWidgets('Adding a new FocusScope attaches the child it to its parent.', (WidgetTester tester) async { + testWidgets('Adding a new FocusScope attaches the child to its parent.', (WidgetTester tester) async { final GlobalKey keyA = GlobalKey(); final FocusScopeNode parentFocusScope = FocusScopeNode(debugLabel: 'Parent Scope Node'); final FocusScopeNode childFocusScope = FocusScopeNode(debugLabel: 'Child Scope Node'); @@ -540,24 +540,24 @@ void main() { await tester.pumpWidget( FocusScope.withExternalFocusNode( - focusScopeNode: topNode, - child: Column( - children: [ - FocusScope.withExternalFocusNode( - focusScopeNode: parentNode, - child: const SizedBox(), + focusScopeNode: topNode, + child: Column( + children: [ + FocusScope.withExternalFocusNode( + focusScopeNode: parentNode, + child: const SizedBox(), + ), + FocusScope.withExternalFocusNode( + focusScopeNode: childNode, + parentNode: parentNode, + child: const Focus( + autofocus: true, + child: SizedBox(), ), - FocusScope.withExternalFocusNode( - focusScopeNode: childNode, - parentNode: parentNode, - child: const Focus( - autofocus: true, - child: SizedBox(), - ), - ) - ], - ), + ) + ], ), + ), ); await tester.pump(); @@ -568,27 +568,27 @@ void main() { // Check that inserting a Focus in between doesn't reparent the child. await tester.pumpWidget( FocusScope.withExternalFocusNode( - focusScopeNode: topNode, - child: Column( - children: [ - FocusScope.withExternalFocusNode( - focusScopeNode: parentNode, - child: const SizedBox(), - ), - FocusScope.withExternalFocusNode( - focusScopeNode: insertedNode, - child: FocusScope.withExternalFocusNode( - focusScopeNode: childNode, - parentNode: parentNode, - child: const Focus( + focusScopeNode: topNode, + child: Column( + children: [ + FocusScope.withExternalFocusNode( + focusScopeNode: parentNode, + child: const SizedBox(), + ), + FocusScope.withExternalFocusNode( + focusScopeNode: insertedNode, + child: FocusScope.withExternalFocusNode( + focusScopeNode: childNode, + parentNode: parentNode, + child: const Focus( autofocus: true, child: SizedBox(), ), - ), - ) - ], - ), + ), + ) + ], ), + ), ); await tester.pump(); @@ -1673,6 +1673,7 @@ void main() { await pumpTest(traverseScope1: true); expect(scope1.traversalDescendants, equals([focus2, focus1, scope2])); }); + testWidgets('descendantsAreFocusable works as expected.', (WidgetTester tester) async { final GlobalKey key1 = GlobalKey(debugLabel: '1'); final GlobalKey key2 = GlobalKey(debugLabel: '2'); @@ -1756,13 +1757,11 @@ void main() { final GlobalKey key1 = GlobalKey(debugLabel: '1'); final FocusNode focusNode = FocusNode(); bool? keyEventHandled; - // ignore: prefer_function_declarations_over_variables - final FocusOnKeyCallback handleCallback = (FocusNode node, RawKeyEvent event) { + KeyEventResult handleCallback(FocusNode node, RawKeyEvent event) { keyEventHandled = true; return KeyEventResult.handled; - }; - // ignore: prefer_function_declarations_over_variables - final FocusOnKeyCallback ignoreCallback = (FocusNode node, RawKeyEvent event) => KeyEventResult.ignored; + } + KeyEventResult ignoreCallback(FocusNode node, RawKeyEvent event) => KeyEventResult.ignored; Focus focusWidget = Focus( onKey: ignoreCallback, // This one does nothing. focusNode: focusNode, @@ -1807,13 +1806,11 @@ void main() { final GlobalKey key1 = GlobalKey(debugLabel: '1'); final FocusNode focusNode = FocusNode(); bool? keyEventHandled; - // ignore: prefer_function_declarations_over_variables - final FocusOnKeyEventCallback handleEventCallback = (FocusNode node, KeyEvent event) { + KeyEventResult handleEventCallback(FocusNode node, KeyEvent event) { keyEventHandled = true; return KeyEventResult.handled; - }; - // ignore: prefer_function_declarations_over_variables - final FocusOnKeyEventCallback ignoreEventCallback = (FocusNode node, KeyEvent event) => KeyEventResult.ignored; + } + KeyEventResult ignoreEventCallback(FocusNode node, KeyEvent event) => KeyEventResult.ignored; Focus focusWidget = Focus( onKeyEvent: ignoreEventCallback, // This one does nothing. focusNode: focusNode, @@ -1858,20 +1855,16 @@ void main() { final GlobalKey key1 = GlobalKey(debugLabel: '1'); final FocusNode focusNode = FocusNode(); bool? keyEventHandled; - // ignore: prefer_function_declarations_over_variables - final FocusOnKeyCallback handleCallback = (FocusNode node, RawKeyEvent event) { + KeyEventResult handleCallback(FocusNode node, RawKeyEvent event) { keyEventHandled = true; return KeyEventResult.handled; - }; - // ignore: prefer_function_declarations_over_variables - final FocusOnKeyEventCallback handleEventCallback = (FocusNode node, KeyEvent event) { + } + KeyEventResult handleEventCallback(FocusNode node, KeyEvent event) { keyEventHandled = true; return KeyEventResult.handled; - }; - // ignore: prefer_function_declarations_over_variables - final FocusOnKeyCallback ignoreCallback = (FocusNode node, RawKeyEvent event) => KeyEventResult.ignored; - // ignore: prefer_function_declarations_over_variables - final FocusOnKeyEventCallback ignoreEventCallback = (FocusNode node, KeyEvent event) => KeyEventResult.ignored; + } + KeyEventResult ignoreCallback(FocusNode node, RawKeyEvent event) => KeyEventResult.ignored; + KeyEventResult ignoreEventCallback(FocusNode node, KeyEvent event) => KeyEventResult.ignored; focusNode.onKey = ignoreCallback; focusNode.onKeyEvent = ignoreEventCallback; focusNode.descendantsAreFocusable = false; @@ -1974,6 +1967,7 @@ void main() { expect(containerNode.hasFocus, isFalse); expect(unfocusableNode.hasFocus, isFalse); }); + // Regression test for https://github.com/flutter/flutter/issues/61700 testWidgets("ExcludeFocus doesn't transfer focus to another descendant.", (WidgetTester tester) async { final FocusNode parentFocusNode = FocusNode(debugLabel: 'group'); diff --git a/packages/flutter/test/widgets/focus_traversal_test.dart b/packages/flutter/test/widgets/focus_traversal_test.dart index 99979c40a3c0..30494f242cc0 100644 --- a/packages/flutter/test/widgets/focus_traversal_test.dart +++ b/packages/flutter/test/widgets/focus_traversal_test.dart @@ -2153,6 +2153,7 @@ void main() { expect(events.length, 2); }, variant: KeySimulatorTransitModeVariant.all()); }); + group(FocusTraversalGroup, () { testWidgets("Focus traversal group doesn't introduce a Semantics node", (WidgetTester tester) async { final SemanticsTester semantics = SemanticsTester(tester); @@ -2381,11 +2382,11 @@ void main() { final SemanticsTester semantics = SemanticsTester(tester); final FocusNode focusNode = FocusNode(); await tester.pumpWidget( - RawKeyboardListener( - focusNode: focusNode, - includeSemantics: false, - child: Container(), - ), + RawKeyboardListener( + focusNode: focusNode, + includeSemantics: false, + child: Container(), + ), ); final TestSemantics expectedSemantics = TestSemantics.root(); expect(semantics, hasSemantics(expectedSemantics)); diff --git a/packages/flutter/test/widgets/shortcuts_test.dart b/packages/flutter/test/widgets/shortcuts_test.dart index 4c314c14405c..1075354eae08 100644 --- a/packages/flutter/test/widgets/shortcuts_test.dart +++ b/packages/flutter/test/widgets/shortcuts_test.dart @@ -72,6 +72,7 @@ void main() { }), ); }); + test('LogicalKeySet works as a map key.', () { final LogicalKeySet set1 = LogicalKeySet(LogicalKeyboardKey.keyA); final LogicalKeySet set2 = LogicalKeySet( @@ -109,6 +110,7 @@ void main() { })), ); }); + testWidgets('handles two keys', (WidgetTester tester) async { int invoked = 0; await tester.pumpWidget(activatorTester( @@ -537,6 +539,7 @@ void main() { expect(shortcuts.shortcuts, isNotNull); expect(shortcuts.shortcuts, isEmpty); }); + testWidgets('Default constructed Shortcuts.manager has empty shortcuts', (WidgetTester tester) async { final ShortcutManager manager = ShortcutManager(); expect(manager.shortcuts, isNotNull); @@ -546,9 +549,10 @@ void main() { expect(shortcuts.shortcuts, isNotNull); expect(shortcuts.shortcuts, isEmpty); }); + testWidgets('Shortcuts.manager passes on shortcuts', (WidgetTester tester) async { final Map testShortcuts = { - LogicalKeySet(LogicalKeyboardKey.shift): const TestIntent(), + LogicalKeySet(LogicalKeyboardKey.shift): const TestIntent(), }; final ShortcutManager manager = ShortcutManager(shortcuts: testShortcuts); expect(manager.shortcuts, isNotNull); @@ -558,6 +562,7 @@ void main() { expect(shortcuts.shortcuts, isNotNull); expect(shortcuts.shortcuts, equals(testShortcuts)); }); + testWidgets('ShortcutManager handles shortcuts', (WidgetTester tester) async { final GlobalKey containerKey = GlobalKey(); final List pressedKeys = []; @@ -592,6 +597,7 @@ void main() { expect(invoked, isTrue); expect(pressedKeys, equals([LogicalKeyboardKey.shiftLeft])); }); + testWidgets('Shortcuts.manager lets manager handle shortcuts', (WidgetTester tester) async { final GlobalKey containerKey = GlobalKey(); final List pressedKeys = []; @@ -633,6 +639,7 @@ void main() { expect(shortcutsSet, isFalse); expect(pressedKeys, equals([LogicalKeyboardKey.shiftLeft])); }); + testWidgets('ShortcutManager ignores key presses with no primary focus', (WidgetTester tester) async { final GlobalKey containerKey = GlobalKey(); final List pressedKeys = []; @@ -665,6 +672,7 @@ void main() { expect(invoked, isFalse); expect(pressedKeys, isEmpty); }); + testWidgets("Shortcuts passes to the next Shortcuts widget if it doesn't map the key", (WidgetTester tester) async { final GlobalKey containerKey = GlobalKey(); final List pressedKeys = []; @@ -704,6 +712,7 @@ void main() { expect(invoked, isTrue); expect(pressedKeys, equals([LogicalKeyboardKey.shiftLeft])); }); + testWidgets('Shortcuts can disable a shortcut with Intent.doNothing', (WidgetTester tester) async { final GlobalKey containerKey = GlobalKey(); final List pressedKeys = []; @@ -745,6 +754,7 @@ void main() { expect(invoked, isFalse); expect(pressedKeys, isEmpty); }); + testWidgets("Shortcuts that aren't bound to an action don't absorb keys meant for text fields", (WidgetTester tester) async { final GlobalKey textFieldKey = GlobalKey(); final List pressedKeys = []; @@ -769,6 +779,7 @@ void main() { expect(handled, isFalse); expect(pressedKeys, equals([LogicalKeyboardKey.keyA])); }); + testWidgets('Shortcuts that are bound to an action do override text fields', (WidgetTester tester) async { final GlobalKey textFieldKey = GlobalKey(); final List pressedKeys = []; @@ -805,6 +816,7 @@ void main() { expect(pressedKeys, equals([LogicalKeyboardKey.keyA])); expect(invoked, isTrue); }); + testWidgets('Shortcuts can override intents that apply to text fields', (WidgetTester tester) async { final GlobalKey textFieldKey = GlobalKey(); final List pressedKeys = []; @@ -845,6 +857,7 @@ void main() { expect(result, isFalse); expect(invoked, isFalse); }); + testWidgets('Shortcuts can override intents that apply to text fields with DoNothingAndStopPropagationIntent', (WidgetTester tester) async { final GlobalKey textFieldKey = GlobalKey(); final List pressedKeys = []; @@ -885,6 +898,7 @@ void main() { expect(result, isFalse); expect(invoked, isFalse); }); + test('Shortcuts diagnostics work.', () { final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder(); @@ -914,6 +928,7 @@ void main() { ), ); }); + test('Shortcuts diagnostics work when debugLabel specified.', () { final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder(); @@ -935,6 +950,7 @@ void main() { expect(description.length, equals(1)); expect(description[0], equals('shortcuts: ')); }); + test('Shortcuts diagnostics work when manager not specified.', () { final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder(); @@ -955,6 +971,7 @@ void main() { expect(description.length, equals(1)); expect(description[0], equalsIgnoringHashCodes('shortcuts: {{Key A + Key B}: ActivateIntent#00000}')); }); + test('Shortcuts diagnostics work when manager specified.', () { final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder(); final List pressedKeys = []; @@ -981,6 +998,7 @@ void main() { expect(description[0], equalsIgnoringHashCodes('manager: TestShortcutManager#00000(shortcuts: {LogicalKeySet#00000(keys: Key A + Key B): ActivateIntent#00000})')); expect(description[1], equalsIgnoringHashCodes('shortcuts: {{Key A + Key B}: ActivateIntent#00000}')); }); + testWidgets('Shortcuts support multiple intents', (WidgetTester tester) async { tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; bool? value = true; @@ -1952,11 +1970,11 @@ class TestShortcutManager extends ShortcutManager { } Widget activatorTester( - ShortcutActivator activator, - ValueSetter onInvoke, [ - ShortcutActivator? activator2, - ValueSetter? onInvoke2, - ]) { + ShortcutActivator activator, + ValueSetter onInvoke, [ + ShortcutActivator? activator2, + ValueSetter? onInvoke2, +]) { final bool hasSecond = activator2 != null && onInvoke2 != null; return Actions( key: GlobalKey(),