mirror of
https://github.com/flutter/flutter
synced 2024-08-25 02:46:31 +00:00
Revert "Add FocusNode.focusabilityListenable
(#144280)" since the feature is no longer needed (#145102)
This reverts commit 726e5d28c0
.
*Replace this paragraph with a description of what this PR is changing or adding, and why. Consider including before/after screenshots.*
*List which issues are fixed by this PR. You must list at least one issue. An issue is not required if the PR fixes something trivial like a typo.*
*If you had to change anything in the [flutter/tests] repo, include a link to the migration guide as per the [breaking change policy].*
This commit is contained in:
parent
33fbb75a95
commit
4f786841f9
|
@ -517,76 +517,21 @@ class FocusNode with DiagnosticableTreeMixin, ChangeNotifier {
|
||||||
/// focus traversal policy for a widget subtree.
|
/// focus traversal policy for a widget subtree.
|
||||||
/// * [FocusTraversalPolicy], a class that can be extended to describe a
|
/// * [FocusTraversalPolicy], a class that can be extended to describe a
|
||||||
/// traversal policy.
|
/// traversal policy.
|
||||||
bool get canRequestFocus => _canRequestFocus && (_focusabilityListenable?.value ?? _computeAncestorsAllowFocus());
|
bool get canRequestFocus => _canRequestFocus && ancestors.every(_allowDescendantsToBeFocused);
|
||||||
bool _computeAncestorsAllowFocus() => ancestors.every(_allowDescendantsToBeFocused);
|
|
||||||
static bool _allowDescendantsToBeFocused(FocusNode ancestor) => ancestor.descendantsAreFocusable;
|
static bool _allowDescendantsToBeFocused(FocusNode ancestor) => ancestor.descendantsAreFocusable;
|
||||||
|
|
||||||
bool _canRequestFocus;
|
bool _canRequestFocus;
|
||||||
@mustCallSuper
|
@mustCallSuper
|
||||||
set canRequestFocus(bool value) {
|
set canRequestFocus(bool value) {
|
||||||
if (value == _canRequestFocus) {
|
if (value != _canRequestFocus) {
|
||||||
return;
|
// Have to set this first before unfocusing, since it checks this to cull
|
||||||
}
|
// unfocusable, previously-focused children.
|
||||||
// Have to set this first before unfocusing, since it checks this to cull
|
_canRequestFocus = value;
|
||||||
// unfocusable, previously-focused children.
|
if (hasFocus && !value) {
|
||||||
_canRequestFocus = value;
|
unfocus(disposition: UnfocusDisposition.previouslyFocusedChild);
|
||||||
if (hasFocus && !value) {
|
|
||||||
unfocus(disposition: UnfocusDisposition.previouslyFocusedChild);
|
|
||||||
}
|
|
||||||
|
|
||||||
final _FocusabilityListenable? focusabilityListenable = _focusabilityListenable;
|
|
||||||
if (focusabilityListenable != null && focusabilityListenable.hasListeners) {
|
|
||||||
final bool ancestorsAllowFocus = focusabilityListenable._ancestorsAllowFocus = _adjustListeningNodeCountForAncestors(value ? 1 : -1);
|
|
||||||
if (ancestorsAllowFocus) {
|
|
||||||
focusabilityListenable.notifyListeners();
|
|
||||||
}
|
}
|
||||||
|
_manager?._markPropertiesChanged(this);
|
||||||
}
|
}
|
||||||
_manager?._markPropertiesChanged(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
// The number of descendant focus nodes whose focusability must be
|
|
||||||
// re-evaluated, when this node's `descentantsAreFocusable` value changes.
|
|
||||||
// This does not include nodes with `_canRequestFocus` set to false, even when
|
|
||||||
// their focusability listenable has listeners.
|
|
||||||
int _focusabilityListeningDescendantCount = 0;
|
|
||||||
|
|
||||||
/// A [ValueListenable] that notifies registered listeners when the
|
|
||||||
/// focusability of this [FocusNode] changes.
|
|
||||||
///
|
|
||||||
/// The [ValueListenable]'s `value` indicates whether this [FocusNode] can
|
|
||||||
/// request primary focus. It's value is always consistent with the return
|
|
||||||
/// value of the [canRequestFocus] getter, which only returns true when the
|
|
||||||
/// [FocusNode]'s [canRequestFocus] setter is set to true, and all of its
|
|
||||||
/// ancestors in the focus tree have [FocusNode.descendantsAreFocusable] set to
|
|
||||||
/// true.
|
|
||||||
///
|
|
||||||
/// Unlike listeners added to the [FocusNode] itself, which won't be notified
|
|
||||||
/// until focus changes are applied in microtasks, listeners added to
|
|
||||||
/// [focusabilityListenable] are notified immediately as the [FocusNode]'s
|
|
||||||
/// focusability changes.
|
|
||||||
///
|
|
||||||
/// This can be used to monitor, for example, whether a text field is currently
|
|
||||||
/// disabled, or in an inactive route, thus isn't receiving user interactions,
|
|
||||||
/// so that text field can unsubscribe itself from system services such as
|
|
||||||
/// scribble and autofill when it becomes unfocusable, and re-subscribe when it
|
|
||||||
/// becomes focusable again.
|
|
||||||
///
|
|
||||||
/// This [ValueListenable] is managed by this [FocusNode]. It must not be used
|
|
||||||
/// after the [FocusNode] itself is disposed.
|
|
||||||
ValueListenable<bool> get focusabilityListenable => _focusabilityListenable ??= _FocusabilityListenable(this);
|
|
||||||
_FocusabilityListenable? _focusabilityListenable;
|
|
||||||
|
|
||||||
// Returns whether all ancestors have `descendantsAreFocusable` set to true.
|
|
||||||
bool _adjustListeningNodeCountForAncestors(int delta) {
|
|
||||||
assert(delta != 0);
|
|
||||||
for (FocusNode? node = parent; node != null; node = node.parent) {
|
|
||||||
node._focusabilityListeningDescendantCount += delta;
|
|
||||||
assert(node._focusabilityListeningDescendantCount >= 0);
|
|
||||||
if (!node.descendantsAreFocusable) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If false, will disable focus for all of this node's descendants.
|
/// If false, will disable focus for all of this node's descendants.
|
||||||
|
@ -629,47 +574,9 @@ class FocusNode with DiagnosticableTreeMixin, ChangeNotifier {
|
||||||
if (!value && hasFocus) {
|
if (!value && hasFocus) {
|
||||||
unfocus(disposition: UnfocusDisposition.previouslyFocusedChild);
|
unfocus(disposition: UnfocusDisposition.previouslyFocusedChild);
|
||||||
}
|
}
|
||||||
_onDescendantsAreFocusableChanged(value);
|
|
||||||
_manager?._markPropertiesChanged(this);
|
_manager?._markPropertiesChanged(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onDescendantsAreFocusableChanged(bool newValue) {
|
|
||||||
assert(_focusabilityListeningDescendantCount >= 0);
|
|
||||||
final int ancestorListenerAdjustment = newValue ? _focusabilityListeningDescendantCount : -_focusabilityListeningDescendantCount;
|
|
||||||
|
|
||||||
// If there's an ancestor that disallows focus, changing the
|
|
||||||
// `descendantsAreFocusable` value of this node never affects the
|
|
||||||
// focusability of the descendants. Notify _focusabilityListenerCount
|
|
||||||
// listeners only when this is not the case.
|
|
||||||
final bool notifyChildren = ancestorListenerAdjustment != 0
|
|
||||||
&& _adjustListeningNodeCountForAncestors(ancestorListenerAdjustment);
|
|
||||||
if (notifyChildren) {
|
|
||||||
assert(children.isNotEmpty);
|
|
||||||
for (final FocusNode child in children) {
|
|
||||||
child._notifyFocusabilityListenersInSubtree(newValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _notifyFocusabilityListenersInSubtree(bool ancestorsAllowFocus) {
|
|
||||||
final _FocusabilityListenable? focusabilityListenable = _focusabilityListenable;
|
|
||||||
if (_canRequestFocus && focusabilityListenable != null && focusabilityListenable.hasListeners) {
|
|
||||||
assert(ancestorsAllowFocus == _computeAncestorsAllowFocus());
|
|
||||||
assert(ancestorsAllowFocus != focusabilityListenable._ancestorsAllowFocus);
|
|
||||||
focusabilityListenable._ancestorsAllowFocus = ancestorsAllowFocus;
|
|
||||||
focusabilityListenable.notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_focusabilityListeningDescendantCount > 0 && descendantsAreFocusable) {
|
|
||||||
// Further propagate to children whose focusability is determined by this
|
|
||||||
// node's ancestors.
|
|
||||||
assert(children.isNotEmpty);
|
|
||||||
for (final FocusNode child in children) {
|
|
||||||
child._notifyFocusabilityListenersInSubtree(ancestorsAllowFocus);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// If false, tells the focus traversal policy to skip over for all of this
|
/// If false, tells the focus traversal policy to skip over for all of this
|
||||||
/// node's descendants for purposes of the traversal algorithm.
|
/// node's descendants for purposes of the traversal algorithm.
|
||||||
///
|
///
|
||||||
|
@ -1104,8 +1011,7 @@ class FocusNode with DiagnosticableTreeMixin, ChangeNotifier {
|
||||||
@mustCallSuper
|
@mustCallSuper
|
||||||
void _reparent(FocusNode child) {
|
void _reparent(FocusNode child) {
|
||||||
assert(child != this, 'Tried to make a child into a parent of itself.');
|
assert(child != this, 'Tried to make a child into a parent of itself.');
|
||||||
final FocusNode? oldParent = child._parent;
|
if (child._parent == this) {
|
||||||
if (oldParent == this) {
|
|
||||||
assert(_children.contains(child), "Found a node that says it's a child, but doesn't appear in the child list.");
|
assert(_children.contains(child), "Found a node that says it's a child, but doesn't appear in the child list.");
|
||||||
// The child is already a child of this parent.
|
// The child is already a child of this parent.
|
||||||
return;
|
return;
|
||||||
|
@ -1114,15 +1020,7 @@ class FocusNode with DiagnosticableTreeMixin, ChangeNotifier {
|
||||||
assert(!ancestors.contains(child), 'The supplied child is already an ancestor of this node. Loops are not allowed.');
|
assert(!ancestors.contains(child), 'The supplied child is already an ancestor of this node. Loops are not allowed.');
|
||||||
final FocusScopeNode? oldScope = child.enclosingScope;
|
final FocusScopeNode? oldScope = child.enclosingScope;
|
||||||
final bool hadFocus = child.hasFocus;
|
final bool hadFocus = child.hasFocus;
|
||||||
|
child._parent?._removeChild(child, removeScopeFocus: oldScope != nearestScope);
|
||||||
final _FocusabilityListenable? childFocusabilityListenable = child._focusabilityListenable;
|
|
||||||
final int childSubtreeListenerCount = (child.descendantsAreFocusable ? child._focusabilityListeningDescendantCount : 0)
|
|
||||||
+ (child._canRequestFocus && childFocusabilityListenable != null && childFocusabilityListenable.hasListeners ? 1 : 0);
|
|
||||||
// If childSubtreeListenerCount == 0, we don't care about focusability since there are no listeners.
|
|
||||||
final bool childCouldFocus = childSubtreeListenerCount > 0
|
|
||||||
&& child._adjustListeningNodeCountForAncestors(-childSubtreeListenerCount);
|
|
||||||
oldParent?._removeChild(child, removeScopeFocus: oldScope != nearestScope);
|
|
||||||
|
|
||||||
_children.add(child);
|
_children.add(child);
|
||||||
child._parent = this;
|
child._parent = this;
|
||||||
child._ancestors = null;
|
child._ancestors = null;
|
||||||
|
@ -1130,14 +1028,6 @@ class FocusNode with DiagnosticableTreeMixin, ChangeNotifier {
|
||||||
for (final FocusNode ancestor in child.ancestors) {
|
for (final FocusNode ancestor in child.ancestors) {
|
||||||
ancestor._descendants = null;
|
ancestor._descendants = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (childSubtreeListenerCount > 0) {
|
|
||||||
final bool childCanFocus = child._adjustListeningNodeCountForAncestors(childSubtreeListenerCount);
|
|
||||||
if (childCanFocus != childCouldFocus) {
|
|
||||||
child._notifyFocusabilityListenersInSubtree(childCanFocus);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hadFocus) {
|
if (hadFocus) {
|
||||||
// Update the focus chain for the current focus without changing it.
|
// Update the focus chain for the current focus without changing it.
|
||||||
_manager?.primaryFocus?._setAsFocusedChildForScope();
|
_manager?.primaryFocus?._setAsFocusedChildForScope();
|
||||||
|
@ -1183,8 +1073,6 @@ class FocusNode with DiagnosticableTreeMixin, ChangeNotifier {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_focusabilityListenable?.dispose();
|
|
||||||
_focusabilityListenable = null;
|
|
||||||
// Detaching will also unfocus and clean up the manager's data structures.
|
// Detaching will also unfocus and clean up the manager's data structures.
|
||||||
_attachment?.detach();
|
_attachment?.detach();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
|
@ -1383,14 +1271,6 @@ class FocusScopeNode extends FocusNode {
|
||||||
this.traversalEdgeBehavior = TraversalEdgeBehavior.closedLoop,
|
this.traversalEdgeBehavior = TraversalEdgeBehavior.closedLoop,
|
||||||
}) : super(descendantsAreFocusable: true);
|
}) : super(descendantsAreFocusable: true);
|
||||||
|
|
||||||
@override
|
|
||||||
set canRequestFocus(bool value) {
|
|
||||||
if (value != _canRequestFocus) {
|
|
||||||
super.canRequestFocus = value;
|
|
||||||
_onDescendantsAreFocusableChanged(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FocusScopeNode get nearestScope => this;
|
FocusScopeNode get nearestScope => this;
|
||||||
|
|
||||||
|
@ -1534,42 +1414,6 @@ class FocusScopeNode extends FocusNode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _FocusabilityListenable extends ChangeNotifier implements ValueListenable<bool> {
|
|
||||||
_FocusabilityListenable(this.node);
|
|
||||||
|
|
||||||
final FocusNode node;
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool get value {
|
|
||||||
assert(!hasListeners || _ancestorsAllowFocus == node._computeAncestorsAllowFocus());
|
|
||||||
return node._canRequestFocus && (hasListeners ? _ancestorsAllowFocus : node._computeAncestorsAllowFocus());
|
|
||||||
}
|
|
||||||
|
|
||||||
// True if all ancestors of `node` have `descentantsAreFocusable` set to
|
|
||||||
// true. The value is only maintained when there are listeners, and
|
|
||||||
// `node._canRequestFocus` is true.
|
|
||||||
bool _ancestorsAllowFocus = true;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void addListener(VoidCallback listener) {
|
|
||||||
final bool hadListener = hasListeners;
|
|
||||||
super.addListener(listener);
|
|
||||||
assert(hasListeners);
|
|
||||||
if (!hadListener && node._canRequestFocus) {
|
|
||||||
_ancestorsAllowFocus = node._adjustListeningNodeCountForAncestors(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void removeListener(VoidCallback listener) {
|
|
||||||
final bool hadListener = hasListeners;
|
|
||||||
super.removeListener(listener);
|
|
||||||
if (node._canRequestFocus && hadListener && !hasListeners) {
|
|
||||||
_ancestorsAllowFocus = node._adjustListeningNodeCountForAncestors(-1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An enum to describe which kind of focus highlight behavior to use when
|
/// An enum to describe which kind of focus highlight behavior to use when
|
||||||
/// displaying focus information.
|
/// displaying focus information.
|
||||||
enum FocusHighlightMode {
|
enum FocusHighlightMode {
|
||||||
|
|
|
@ -2143,275 +2143,6 @@ void main() {
|
||||||
tester.binding.focusManager.removeListener(handleFocusChange);
|
tester.binding.focusManager.removeListener(handleFocusChange);
|
||||||
});
|
});
|
||||||
|
|
||||||
group('focusability listener', () {
|
|
||||||
int focusabilityChangeCount = 0;
|
|
||||||
void focusabilityCallback() {
|
|
||||||
focusabilityChangeCount += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
setUp(() { focusabilityChangeCount = 0; });
|
|
||||||
|
|
||||||
testWidgets('canRequestFocus affects focusability of the node', (WidgetTester tester) async {
|
|
||||||
int node2CallbackCounter = 0;
|
|
||||||
void node2Callback() { node2CallbackCounter += 1; }
|
|
||||||
final FocusNode node1 = FocusNode(debugLabel: 'node 1')..focusabilityListenable.addListener(focusabilityCallback);
|
|
||||||
final FocusNode node2 = FocusNode(debugLabel: 'node 2')..focusabilityListenable.addListener(node2Callback);
|
|
||||||
|
|
||||||
addTearDown(node1.dispose);
|
|
||||||
addTearDown(node2.dispose);
|
|
||||||
|
|
||||||
await tester.pumpWidget(
|
|
||||||
Focus(
|
|
||||||
focusNode: node1,
|
|
||||||
child: Focus(
|
|
||||||
focusNode: node2,
|
|
||||||
child: const SizedBox(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(node1.focusabilityListenable.value, isTrue);
|
|
||||||
expect(focusabilityChangeCount, 0);
|
|
||||||
expect(node2.focusabilityListenable.value, isTrue);
|
|
||||||
expect(node2CallbackCounter, 0);
|
|
||||||
|
|
||||||
node1.canRequestFocus = false;
|
|
||||||
expect(node1.focusabilityListenable.value, isFalse);
|
|
||||||
expect(focusabilityChangeCount, 1);
|
|
||||||
expect(node2.focusabilityListenable.value, isTrue);
|
|
||||||
expect(node2CallbackCounter, 0);
|
|
||||||
|
|
||||||
node1.canRequestFocus = true;
|
|
||||||
expect(node1.focusabilityListenable.value, isTrue);
|
|
||||||
expect(focusabilityChangeCount, 2);
|
|
||||||
expect(node2.focusabilityListenable.value, isTrue);
|
|
||||||
expect(node2CallbackCounter, 0);
|
|
||||||
|
|
||||||
node2.canRequestFocus = false;
|
|
||||||
expect(node1.focusabilityListenable.value, isTrue);
|
|
||||||
expect(focusabilityChangeCount, 2);
|
|
||||||
expect(node2.focusabilityListenable.value, isFalse);
|
|
||||||
expect(node2CallbackCounter, 1);
|
|
||||||
|
|
||||||
node2.canRequestFocus = true;
|
|
||||||
expect(node1.focusabilityListenable.value, isTrue);
|
|
||||||
expect(focusabilityChangeCount, 2);
|
|
||||||
expect(node2.focusabilityListenable.value, isTrue);
|
|
||||||
expect(node2CallbackCounter, 2);
|
|
||||||
});
|
|
||||||
|
|
||||||
testWidgets('descendantsAreFocusable affects focusability of the descendants', (WidgetTester tester) async {
|
|
||||||
int node2CallbackCounter = 0;
|
|
||||||
void node2Callback() { node2CallbackCounter += 1; }
|
|
||||||
final FocusNode node1 = FocusNode(debugLabel: 'node 1')..focusabilityListenable.addListener(focusabilityCallback);
|
|
||||||
final FocusNode node2 = FocusNode(debugLabel: 'node 2', descendantsAreFocusable: false)..focusabilityListenable.addListener(node2Callback);
|
|
||||||
|
|
||||||
addTearDown(node1.dispose);
|
|
||||||
addTearDown(node2.dispose);
|
|
||||||
|
|
||||||
await tester.pumpWidget(
|
|
||||||
Focus(
|
|
||||||
focusNode: node1,
|
|
||||||
child: Focus(
|
|
||||||
focusNode: node2,
|
|
||||||
child: const SizedBox(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(node1.focusabilityListenable.value, isTrue);
|
|
||||||
expect(focusabilityChangeCount, 0);
|
|
||||||
expect(node2.focusabilityListenable.value, isTrue);
|
|
||||||
expect(node2CallbackCounter, 0);
|
|
||||||
|
|
||||||
node1.descendantsAreFocusable = false;
|
|
||||||
expect(node1.focusabilityListenable.value, isTrue);
|
|
||||||
expect(focusabilityChangeCount, 0);
|
|
||||||
expect(node2.focusabilityListenable.value, isFalse);
|
|
||||||
expect(node2CallbackCounter, 1);
|
|
||||||
|
|
||||||
node1.descendantsAreFocusable = true;
|
|
||||||
expect(node1.focusabilityListenable.value, isTrue);
|
|
||||||
expect(focusabilityChangeCount, 0);
|
|
||||||
expect(node2.focusabilityListenable.value, isTrue);
|
|
||||||
expect(node2CallbackCounter, 2);
|
|
||||||
|
|
||||||
node2.descendantsAreFocusable = false;
|
|
||||||
expect(node1.focusabilityListenable.value, isTrue);
|
|
||||||
expect(focusabilityChangeCount, 0);
|
|
||||||
expect(node2.focusabilityListenable.value, isTrue);
|
|
||||||
expect(node2CallbackCounter, 2);
|
|
||||||
});
|
|
||||||
|
|
||||||
testWidgets('Reparenting affects focusability of the node', (WidgetTester tester) async {
|
|
||||||
int node3CallbackCounter = 0;
|
|
||||||
void node3Callback() { node3CallbackCounter += 1; }
|
|
||||||
final FocusNode node1 = FocusNode(debugLabel: 'node 1');
|
|
||||||
final FocusNode node2 = FocusNode(debugLabel: 'node 2', descendantsAreFocusable: false);
|
|
||||||
final FocusNode node3 = FocusNode(debugLabel: 'node 3')..focusabilityListenable.addListener(node3Callback);
|
|
||||||
final FocusNode node4 = FocusNode(debugLabel: 'node 4')..focusabilityListenable.addListener(focusabilityCallback);
|
|
||||||
addTearDown(node1.dispose);
|
|
||||||
addTearDown(node2.dispose);
|
|
||||||
addTearDown(node3.dispose);
|
|
||||||
addTearDown(node4.dispose);
|
|
||||||
|
|
||||||
await tester.pumpWidget(
|
|
||||||
Focus(
|
|
||||||
focusNode: node1,
|
|
||||||
child: Focus(
|
|
||||||
focusNode: node2,
|
|
||||||
child: Column(
|
|
||||||
children: <Widget>[
|
|
||||||
Focus(focusNode: node3, child: Container()),
|
|
||||||
Focus(focusNode: node4, child: Container()),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
// The listeners are notified on reparent.
|
|
||||||
expect(node4.focusabilityListenable.value, isFalse);
|
|
||||||
expect(focusabilityChangeCount, 1);
|
|
||||||
expect(node3.focusabilityListenable.value, isFalse);
|
|
||||||
expect(node3CallbackCounter, 1);
|
|
||||||
|
|
||||||
// Swap node 1 and node 3.
|
|
||||||
await tester.pumpWidget(
|
|
||||||
Focus(
|
|
||||||
focusNode: node3,
|
|
||||||
child: Focus(
|
|
||||||
focusNode: node2,
|
|
||||||
child: Column(
|
|
||||||
children: <Widget>[
|
|
||||||
Focus(focusNode: node1, child: Container()),
|
|
||||||
Focus(focusNode: node4, child: Container()),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(node4.focusabilityListenable.value, isFalse);
|
|
||||||
expect(focusabilityChangeCount, 1);
|
|
||||||
expect(node3.focusabilityListenable.value, isTrue);
|
|
||||||
expect(node3CallbackCounter, 2);
|
|
||||||
|
|
||||||
// Swap node 1 and node 2.
|
|
||||||
await tester.pumpWidget(
|
|
||||||
Focus(
|
|
||||||
focusNode: node3,
|
|
||||||
child: Focus(
|
|
||||||
focusNode: node1,
|
|
||||||
child: Column(
|
|
||||||
children: <Widget>[
|
|
||||||
Focus(focusNode: node2, child: Container()),
|
|
||||||
Focus(focusNode: node4, child: Container()),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(node4.focusabilityListenable.value, isTrue);
|
|
||||||
expect(focusabilityChangeCount, 2);
|
|
||||||
expect(node3.focusabilityListenable.value, isTrue);
|
|
||||||
expect(node3CallbackCounter, 2);
|
|
||||||
|
|
||||||
// Swap node 2 and node 4.
|
|
||||||
await tester.pumpWidget(
|
|
||||||
Focus(
|
|
||||||
focusNode: node3,
|
|
||||||
child: Focus(
|
|
||||||
focusNode: node1,
|
|
||||||
child: Column(
|
|
||||||
children: <Widget>[
|
|
||||||
Focus(focusNode: node4, child: Container()),
|
|
||||||
Focus(focusNode: node2, child: Container()),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(node4.focusabilityListenable.value, isTrue);
|
|
||||||
expect(focusabilityChangeCount, 2);
|
|
||||||
expect(node3.focusabilityListenable.value, isTrue);
|
|
||||||
expect(node3CallbackCounter, 2);
|
|
||||||
|
|
||||||
// Return to the initial state
|
|
||||||
await tester.pumpWidget(
|
|
||||||
Focus(
|
|
||||||
focusNode: node1,
|
|
||||||
child: Focus(
|
|
||||||
focusNode: node2,
|
|
||||||
child: Column(
|
|
||||||
children: <Widget>[
|
|
||||||
Focus(focusNode: node3, child: Container()),
|
|
||||||
Focus(focusNode: node4, child: Container()),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(node4.focusabilityListenable.value, isFalse);
|
|
||||||
expect(focusabilityChangeCount, 3);
|
|
||||||
expect(node3.focusabilityListenable.value, isFalse);
|
|
||||||
expect(node3CallbackCounter, 3);
|
|
||||||
});
|
|
||||||
|
|
||||||
testWidgets('does not get called in dispose', (WidgetTester tester) async {
|
|
||||||
final FocusNode node1 = FocusNode(debugLabel: 'node 1')..focusabilityListenable.addListener(focusabilityCallback);
|
|
||||||
final FocusNode node2 = FocusNode(debugLabel: 'node 2')..focusabilityListenable.addListener(focusabilityCallback);
|
|
||||||
|
|
||||||
await tester.pumpWidget(
|
|
||||||
Focus(
|
|
||||||
descendantsAreFocusable: false,
|
|
||||||
child: Column(
|
|
||||||
children: <Widget>[
|
|
||||||
Focus(focusNode: node1, child: Container()),
|
|
||||||
Focus(focusNode: node2, child: Container()),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
expect(focusabilityChangeCount, 2);
|
|
||||||
|
|
||||||
await tester.pumpWidget(const SizedBox());
|
|
||||||
expect(focusabilityChangeCount, 2);
|
|
||||||
});
|
|
||||||
|
|
||||||
testWidgets('Adding removing listeners many times', (WidgetTester tester) async {
|
|
||||||
final FocusNode node1 = FocusNode(debugLabel: 'node 1')..focusabilityListenable.addListener(focusabilityCallback);
|
|
||||||
final FocusNode node2 = FocusNode(debugLabel: 'node 2');
|
|
||||||
|
|
||||||
for (int i = 0; i < 100; i += 1) {
|
|
||||||
node1.focusabilityListenable.removeListener(focusabilityCallback);
|
|
||||||
node1.focusabilityListenable.removeListener(focusabilityCallback);
|
|
||||||
node1.focusabilityListenable.addListener(focusabilityCallback);
|
|
||||||
node1.focusabilityListenable.removeListener(focusabilityCallback);
|
|
||||||
}
|
|
||||||
node1.focusabilityListenable.addListener(focusabilityCallback);
|
|
||||||
node1.focusabilityListenable.addListener(focusabilityCallback);
|
|
||||||
node2.focusabilityListenable.addListener(focusabilityCallback);
|
|
||||||
expect(focusabilityChangeCount, 0);
|
|
||||||
|
|
||||||
await tester.pumpWidget(
|
|
||||||
Focus(
|
|
||||||
descendantsAreFocusable: false,
|
|
||||||
child: Column(
|
|
||||||
children: <Widget>[
|
|
||||||
Focus(focusNode: node1, child: Container()),
|
|
||||||
Focus(focusNode: node2, child: Container()),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
expect(focusabilityChangeCount, 3);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
testWidgets('debugFocusChanges causes logging of focus changes', (WidgetTester tester) async {
|
testWidgets('debugFocusChanges causes logging of focus changes', (WidgetTester tester) async {
|
||||||
final bool oldDebugFocusChanges = debugFocusChanges;
|
final bool oldDebugFocusChanges = debugFocusChanges;
|
||||||
final DebugPrintCallback oldDebugPrint = debugPrint;
|
final DebugPrintCallback oldDebugPrint = debugPrint;
|
||||||
|
|
|
@ -2141,169 +2141,6 @@ void main() {
|
||||||
expect(childFocusNode.canRequestFocus, isTrue);
|
expect(childFocusNode.canRequestFocus, isTrue);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
group('focusability listener', () {
|
|
||||||
int focusabilityChangeCount = 0;
|
|
||||||
void focusabilityCallback() {
|
|
||||||
focusabilityChangeCount += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
setUp(() { focusabilityChangeCount = 0; });
|
|
||||||
|
|
||||||
testWidgets('canRequestFocus affects child focusability', (WidgetTester tester) async {
|
|
||||||
final FocusScopeNode scopeNode1 = FocusScopeNode(debugLabel: 'scope1');
|
|
||||||
final FocusScopeNode scopeNode2 = FocusScopeNode(debugLabel: 'scope2');
|
|
||||||
final FocusNode node1 = FocusNode(debugLabel: 'node 1');
|
|
||||||
final FocusNode node2 = FocusNode(debugLabel: 'node 2');
|
|
||||||
final FocusNode node3 = FocusNode(debugLabel: 'node 3');
|
|
||||||
addTearDown(scopeNode1.dispose);
|
|
||||||
addTearDown(scopeNode2.dispose);
|
|
||||||
addTearDown(node1.dispose);
|
|
||||||
addTearDown(node2.dispose);
|
|
||||||
addTearDown(node3.dispose);
|
|
||||||
|
|
||||||
await tester.pumpWidget(
|
|
||||||
FocusScope(
|
|
||||||
node: scopeNode1,
|
|
||||||
child: Column(
|
|
||||||
children: <Widget>[
|
|
||||||
Focus(
|
|
||||||
focusNode: node1,
|
|
||||||
child: Container(),
|
|
||||||
),
|
|
||||||
Focus(
|
|
||||||
focusNode: node2,
|
|
||||||
child: FocusScope(
|
|
||||||
node: scopeNode2,
|
|
||||||
child: Focus(focusNode: node3, child: const SizedBox()),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
node3.focusabilityListenable.addListener(focusabilityCallback);
|
|
||||||
int node1FocusabilityCallbackCount = 0;
|
|
||||||
node1.focusabilityListenable.addListener(() => node1FocusabilityCallbackCount += 1);
|
|
||||||
|
|
||||||
scopeNode1.canRequestFocus = false;
|
|
||||||
expect(node3.focusabilityListenable.value, isFalse);
|
|
||||||
expect(focusabilityChangeCount, 1);
|
|
||||||
expect(node1.focusabilityListenable.value, isFalse);
|
|
||||||
expect(node1FocusabilityCallbackCount, 1);
|
|
||||||
|
|
||||||
scopeNode2.canRequestFocus = false;
|
|
||||||
expect(node3.focusabilityListenable.value, isFalse);
|
|
||||||
expect(focusabilityChangeCount, 1);
|
|
||||||
expect(node1.focusabilityListenable.value, isFalse);
|
|
||||||
expect(node1FocusabilityCallbackCount, 1);
|
|
||||||
|
|
||||||
scopeNode1.canRequestFocus = true;
|
|
||||||
expect(node3.focusabilityListenable.value, isFalse);
|
|
||||||
expect(focusabilityChangeCount, 1);
|
|
||||||
expect(node1.focusabilityListenable.value, isTrue);
|
|
||||||
expect(node1FocusabilityCallbackCount, 2);
|
|
||||||
|
|
||||||
scopeNode2.canRequestFocus = true;
|
|
||||||
expect(node3.focusabilityListenable.value, isTrue);
|
|
||||||
expect(focusabilityChangeCount, 2);
|
|
||||||
expect(node1.focusabilityListenable.value, isTrue);
|
|
||||||
expect(node1FocusabilityCallbackCount, 2);
|
|
||||||
});
|
|
||||||
|
|
||||||
testWidgets('onFocusabilityCallback invoked on mount, if not focusable', (WidgetTester tester) async {
|
|
||||||
final FocusScopeNode scopeNode1 = FocusScopeNode(debugLabel: 'scope1', canRequestFocus: false);
|
|
||||||
final FocusNode node1 = FocusNode(debugLabel: 'node 1')..focusabilityListenable.addListener(focusabilityCallback);
|
|
||||||
addTearDown(scopeNode1.dispose);
|
|
||||||
addTearDown(node1.dispose);
|
|
||||||
|
|
||||||
await tester.pumpWidget(
|
|
||||||
FocusScope(
|
|
||||||
node: scopeNode1,
|
|
||||||
child: Column(
|
|
||||||
children: <Widget>[
|
|
||||||
Focus(
|
|
||||||
focusNode: node1,
|
|
||||||
child: Container(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(node1.focusabilityListenable.value, isFalse);
|
|
||||||
expect(focusabilityChangeCount, 1);
|
|
||||||
});
|
|
||||||
|
|
||||||
testWidgets('onFocusabilityCallback is not invoked on mount, if focusable', (WidgetTester tester) async {
|
|
||||||
final FocusScopeNode scopeNode1 = FocusScopeNode(debugLabel: 'scope1');
|
|
||||||
final FocusNode node1 = FocusNode(debugLabel: 'node 1')..focusabilityListenable.addListener(focusabilityCallback);
|
|
||||||
addTearDown(scopeNode1.dispose);
|
|
||||||
addTearDown(node1.dispose);
|
|
||||||
|
|
||||||
await tester.pumpWidget(
|
|
||||||
FocusScope(
|
|
||||||
node: scopeNode1,
|
|
||||||
child: Column(
|
|
||||||
children: <Widget>[
|
|
||||||
Focus(
|
|
||||||
focusNode: node1,
|
|
||||||
child: Container(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(focusabilityChangeCount, 0);
|
|
||||||
});
|
|
||||||
|
|
||||||
testWidgets('onFocusabilityCallback on scope node', (WidgetTester tester) async {
|
|
||||||
final FocusScopeNode scopeNode1 = FocusScopeNode(debugLabel: 'scope1');
|
|
||||||
final FocusScopeNode scopeNode2 = FocusScopeNode(debugLabel: 'scope2')..focusabilityListenable.addListener(focusabilityCallback);
|
|
||||||
addTearDown(scopeNode1.dispose);
|
|
||||||
addTearDown(scopeNode2.dispose);
|
|
||||||
|
|
||||||
await tester.pumpWidget(
|
|
||||||
FocusScope(
|
|
||||||
node: scopeNode1,
|
|
||||||
child: FocusScope(node: scopeNode2, child: Container())
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(focusabilityChangeCount, 0);
|
|
||||||
|
|
||||||
scopeNode2.canRequestFocus = false;
|
|
||||||
expect(focusabilityChangeCount, 1);
|
|
||||||
expect(scopeNode2.focusabilityListenable.value, isFalse);
|
|
||||||
|
|
||||||
scopeNode2.canRequestFocus = true;
|
|
||||||
expect(focusabilityChangeCount, 2);
|
|
||||||
expect(scopeNode2.focusabilityListenable.value, isTrue);
|
|
||||||
|
|
||||||
// scope 2 has no descendants.
|
|
||||||
scopeNode2.descendantsAreFocusable = false;
|
|
||||||
expect(focusabilityChangeCount, 2);
|
|
||||||
expect(scopeNode2.focusabilityListenable.value, isTrue);
|
|
||||||
|
|
||||||
scopeNode1.descendantsAreFocusable = false;
|
|
||||||
expect(focusabilityChangeCount, 3);
|
|
||||||
expect(scopeNode2.focusabilityListenable.value, isFalse);
|
|
||||||
|
|
||||||
scopeNode1.descendantsAreFocusable = true;
|
|
||||||
expect(focusabilityChangeCount, 4);
|
|
||||||
expect(scopeNode2.focusabilityListenable.value, isTrue);
|
|
||||||
|
|
||||||
scopeNode1.canRequestFocus = false;
|
|
||||||
expect(focusabilityChangeCount, 5);
|
|
||||||
expect(scopeNode2.focusabilityListenable.value, isFalse);
|
|
||||||
|
|
||||||
scopeNode1.canRequestFocus = true;
|
|
||||||
expect(focusabilityChangeCount, 6);
|
|
||||||
expect(scopeNode2.focusabilityListenable.value, isTrue);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class TestFocus extends StatefulWidget {
|
class TestFocus extends StatefulWidget {
|
||||||
|
|
Loading…
Reference in a new issue