Commit 5cfccb91 authored by Adam Barth's avatar Adam Barth

Merge pull request #1397 from abarth/route_focus

Keyboard doesn't dismiss when the drawer comes up
parents 594e7000 a5351643
...@@ -81,6 +81,8 @@ class DrawerControllerState extends State<DrawerController> { ...@@ -81,6 +81,8 @@ class DrawerControllerState extends State<DrawerController> {
} }
LocalHistoryEntry _historyEntry; LocalHistoryEntry _historyEntry;
// TODO(abarth): This should be a GlobalValueKey when those exist.
GlobalKey get _focusKey => new GlobalObjectKey(config.key);
void _ensureHistoryEntry() { void _ensureHistoryEntry() {
if (_historyEntry == null) { if (_historyEntry == null) {
...@@ -88,6 +90,7 @@ class DrawerControllerState extends State<DrawerController> { ...@@ -88,6 +90,7 @@ class DrawerControllerState extends State<DrawerController> {
if (route != null) { if (route != null) {
_historyEntry = new LocalHistoryEntry(onRemove: _handleHistoryEntryRemoved); _historyEntry = new LocalHistoryEntry(onRemove: _handleHistoryEntryRemoved);
route.addLocalHistoryEntry(_historyEntry); route.addLocalHistoryEntry(_historyEntry);
Focus.moveScopeTo(_focusKey, context: context);
} }
} }
} }
...@@ -194,7 +197,7 @@ class DrawerControllerState extends State<DrawerController> { ...@@ -194,7 +197,7 @@ class DrawerControllerState extends State<DrawerController> {
onSizeChanged: _handleSizeChanged, onSizeChanged: _handleSizeChanged,
child: new RepaintBoundary( child: new RepaintBoundary(
child: new Focus( child: new Focus(
key: new GlobalObjectKey(config.key), key: _focusKey,
child: config.child child: config.child
) )
) )
......
...@@ -34,9 +34,9 @@ class _FocusScope extends InheritedWidget { ...@@ -34,9 +34,9 @@ class _FocusScope extends InheritedWidget {
GlobalKey focusedScope; GlobalKey focusedScope;
GlobalKey focusedWidget; GlobalKey focusedWidget;
// The ...IfUnset() methods don't need to notify descendants because by // The _setFocusedWidgetIfUnset() methodsdon't need to notify descendants
// definition they are only going to make a change the very first time that // because by definition they are only going to make a change the very first
// our state is checked. // time that our state is checked.
void _setFocusedWidgetIfUnset(GlobalKey key) { void _setFocusedWidgetIfUnset(GlobalKey key) {
focusState._setFocusedWidgetIfUnset(key); focusState._setFocusedWidgetIfUnset(key);
...@@ -44,12 +44,6 @@ class _FocusScope extends InheritedWidget { ...@@ -44,12 +44,6 @@ class _FocusScope extends InheritedWidget {
focusedScope = focusState._focusedScope == _noFocusedScope ? null : focusState._focusedScope; focusedScope = focusState._focusedScope == _noFocusedScope ? null : focusState._focusedScope;
} }
void _setFocusedScopeIfUnset(GlobalKey key) {
focusState._setFocusedScopeIfUnset(key);
assert(focusedWidget == focusState._focusedWidget);
focusedScope = focusState._focusedScope == _noFocusedScope ? null : focusState._focusedScope;
}
bool updateShouldNotify(_FocusScope oldWidget) { bool updateShouldNotify(_FocusScope oldWidget) {
if (scopeFocused != oldWidget.scopeFocused) if (scopeFocused != oldWidget.scopeFocused)
return true; return true;
...@@ -77,14 +71,12 @@ class _FocusScope extends InheritedWidget { ...@@ -77,14 +71,12 @@ class _FocusScope extends InheritedWidget {
class Focus extends StatefulComponent { class Focus extends StatefulComponent {
Focus({ Focus({
GlobalKey key, // key is required if this is a nested Focus scope GlobalKey key,
this.autofocus: false,
this.child this.child
}) : super(key: key) { }) : super(key: key) {
assert(!autofocus || key != null); assert(key != null);
} }
final bool autofocus;
final Widget child; final Widget child;
static GlobalKey debugOnlyFocusedKey; static GlobalKey debugOnlyFocusedKey;
...@@ -114,15 +106,13 @@ class Focus extends StatefulComponent { ...@@ -114,15 +106,13 @@ class Focus extends StatefulComponent {
return true; return true;
} }
static bool _atScope(BuildContext context, { bool autofocus: false }) { static bool _atScope(BuildContext context) {
assert(context != null); assert(context != null);
assert(context.widget != null); assert(context.widget != null);
assert(context.widget is Focus); assert(context.widget is Focus);
assert(context.widget.key != null);
_FocusScope focusScope = context.inheritFromWidgetOfExactType(_FocusScope); _FocusScope focusScope = context.inheritFromWidgetOfExactType(_FocusScope);
if (focusScope != null) { if (focusScope != null) {
assert(context.widget.key != null);
if (autofocus)
focusScope._setFocusedScopeIfUnset(context.widget.key);
return focusScope.scopeFocused && return focusScope.scopeFocused &&
focusScope.focusedScope == context.widget.key; focusScope.focusedScope == context.widget.key;
} }
...@@ -150,15 +140,20 @@ class Focus extends StatefulComponent { ...@@ -150,15 +140,20 @@ class Focus extends StatefulComponent {
focusScope.focusState._clearFocusedWidget(); focusScope.focusState._clearFocusedWidget();
} }
/// Focuses a particular focus scope, identified by its GlobalKey. The widget /// Focuses a particular focus scope, identified by its GlobalKey.
/// must be in the widget tree.
/// ///
/// Don't call moveScopeTo() from your build() functions, it's intended to be /// Don't call moveScopeTo() from your build() functions, it's intended to be
/// called from event listeners, e.g. in response to a finger tap or tab key. /// called from event listeners, e.g. in response to a finger tap or tab key.
static void moveScopeTo(GlobalKey key) { static void moveScopeTo(GlobalKey key, { BuildContext context }) {
assert(key.currentWidget is Focus); _FocusScope focusScope;
assert(key.currentContext != null); BuildContext searchContext = key.currentContext;
_FocusScope focusScope = key.currentContext.ancestorWidgetOfExactType(_FocusScope); if (searchContext != null) {
assert(key.currentWidget is Focus);
focusScope = searchContext.ancestorWidgetOfExactType(_FocusScope);
assert(context == null || focusScope == context.ancestorWidgetOfExactType(_FocusScope));
} else {
focusScope = context.ancestorWidgetOfExactType(_FocusScope);
}
if (focusScope != null) if (focusScope != null)
focusScope.focusState._setFocusedScope(key); focusScope.focusState._setFocusedScope(key);
} }
...@@ -167,6 +162,18 @@ class Focus extends StatefulComponent { ...@@ -167,6 +162,18 @@ class Focus extends StatefulComponent {
} }
class _FocusState extends State<Focus> { class _FocusState extends State<Focus> {
void initState() {
super.initState();
_updateWidgetRemovalListener(_focusedWidget);
_updateScopeRemovalListener(_focusedScope);
}
void dispose() {
_updateWidgetRemovalListener(null);
_updateScopeRemovalListener(null);
super.dispose();
}
GlobalKey _focusedWidget; // when null, the first component to ask if it's focused will get the focus GlobalKey _focusedWidget; // when null, the first component to ask if it's focused will get the focus
GlobalKey _currentlyRegisteredWidgetRemovalListenerKey; GlobalKey _currentlyRegisteredWidgetRemovalListenerKey;
...@@ -222,13 +229,6 @@ class _FocusState extends State<Focus> { ...@@ -222,13 +229,6 @@ class _FocusState extends State<Focus> {
_updateScopeRemovalListener(key); _updateScopeRemovalListener(key);
} }
void _setFocusedScopeIfUnset(GlobalKey key) {
if (_focusedScope == null) {
_focusedScope = key;
_updateScopeRemovalListener(key);
}
}
void _scopeRemoved(GlobalKey key) { void _scopeRemoved(GlobalKey key) {
assert(_focusedScope == key); assert(_focusedScope == key);
GlobalKey.unregisterRemoveListener(_currentlyRegisteredScopeRemovalListenerKey, _scopeRemoved); GlobalKey.unregisterRemoveListener(_currentlyRegisteredScopeRemovalListenerKey, _scopeRemoved);
...@@ -248,18 +248,6 @@ class _FocusState extends State<Focus> { ...@@ -248,18 +248,6 @@ class _FocusState extends State<Focus> {
} }
} }
void initState() {
super.initState();
_updateWidgetRemovalListener(_focusedWidget);
_updateScopeRemovalListener(_focusedScope);
}
void dispose() {
_updateWidgetRemovalListener(null);
_updateScopeRemovalListener(null);
super.dispose();
}
Size _mediaSize; Size _mediaSize;
EdgeDims _mediaPadding; EdgeDims _mediaPadding;
......
...@@ -339,20 +339,20 @@ class _ModalScopeState extends State<_ModalScope> { ...@@ -339,20 +339,20 @@ class _ModalScopeState extends State<_ModalScope> {
if (config.route.offstage) { if (config.route.offstage) {
contents = new OffStage(child: contents); contents = new OffStage(child: contents);
} else { } else {
contents = new Focus( contents = new IgnorePointer(
key: new GlobalObjectKey(config.route), ignoring: config.route.animation?.status == AnimationStatus.reverse,
child: new IgnorePointer( child: config.route.buildTransitions(
ignoring: config.route.animation?.status == AnimationStatus.reverse, context,
child: config.route.buildTransitions( config.route.animation,
context, config.route.forwardAnimation,
config.route.animation, contents
config.route.forwardAnimation,
contents
)
) )
); );
} }
contents = new RepaintBoundary(child: contents); contents = new Focus(
key: new GlobalObjectKey(config.route),
child: new RepaintBoundary(child: contents)
);
ModalPosition position = config.route.getPosition(context); ModalPosition position = config.route.getPosition(context);
if (position == null) if (position == null)
return contents; return contents;
...@@ -401,6 +401,10 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T ...@@ -401,6 +401,10 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
return child; return child;
} }
void didPush() {
Focus.moveScopeTo(new GlobalObjectKey(this), context: navigator.context);
super.didPush();
}
// The API for subclasses to override - used by this class // The API for subclasses to override - used by this class
......
...@@ -30,10 +30,12 @@ class TestFocusable extends StatelessComponent { ...@@ -30,10 +30,12 @@ class TestFocusable extends StatelessComponent {
void main() { void main() {
test('Can have multiple focused children and they update accordingly', () { test('Can have multiple focused children and they update accordingly', () {
testWidgets((WidgetTester tester) { testWidgets((WidgetTester tester) {
GlobalKey keyFocus = new GlobalKey();
GlobalKey keyA = new GlobalKey(); GlobalKey keyA = new GlobalKey();
GlobalKey keyB = new GlobalKey(); GlobalKey keyB = new GlobalKey();
tester.pumpWidget( tester.pumpWidget(
new Focus( new Focus(
key: keyFocus,
child: new Column( child: new Column(
children: <Widget>[ children: <Widget>[
// reverse these when you fix https://github.com/flutter/engine/issues/1495 // reverse these when you fix https://github.com/flutter/engine/issues/1495
...@@ -84,9 +86,11 @@ void main() { ...@@ -84,9 +86,11 @@ void main() {
test('Can blur', () { test('Can blur', () {
testWidgets((WidgetTester tester) { testWidgets((WidgetTester tester) {
GlobalKey keyFocus = new GlobalKey();
GlobalKey keyA = new GlobalKey(); GlobalKey keyA = new GlobalKey();
tester.pumpWidget( tester.pumpWidget(
new Focus( new Focus(
key: keyFocus,
child: new TestFocusable( child: new TestFocusable(
key: keyA, key: keyA,
no: 'a', no: 'a',
...@@ -112,4 +116,89 @@ void main() { ...@@ -112,4 +116,89 @@ void main() {
expect(tester.findText('A FOCUSED'), isNull); expect(tester.findText('A FOCUSED'), isNull);
}); });
}); });
test('Can move focus to scope', () {
testWidgets((WidgetTester tester) {
GlobalKey keyParentFocus = new GlobalKey();
GlobalKey keyChildFocus = new GlobalKey();
GlobalKey keyA = new GlobalKey();
tester.pumpWidget(
new Focus(
key: keyParentFocus,
child: new Row(
children: [
new TestFocusable(
key: keyA,
no: 'a',
yes: 'A FOCUSED',
autofocus: false
)
]
)
)
);
expect(tester.findText('a'), isNotNull);
expect(tester.findText('A FOCUSED'), isNull);
Focus.moveTo(keyA);
tester.pump();
expect(tester.findText('a'), isNull);
expect(tester.findText('A FOCUSED'), isNotNull);
Focus.moveScopeTo(keyChildFocus, context: keyA.currentContext);
tester.pumpWidget(
new Focus(
key: keyParentFocus,
child: new Row(
children: [
new TestFocusable(
key: keyA,
no: 'a',
yes: 'A FOCUSED',
autofocus: false
),
new Focus(
key: keyChildFocus,
child: new Container(
width: 50.0,
height: 50.0
)
)
]
)
)
);
expect(tester.findText('a'), isNotNull);
expect(tester.findText('A FOCUSED'), isNull);
tester.pumpWidget(
new Focus(
key: keyParentFocus,
child: new Row(
children: [
new TestFocusable(
key: keyA,
no: 'a',
yes: 'A FOCUSED',
autofocus: false
)
]
)
)
);
// Focus has received the removal notification but we haven't rebuilt yet.
expect(tester.findText('a'), isNotNull);
expect(tester.findText('A FOCUSED'), isNull);
tester.pump();
expect(tester.findText('a'), isNull);
expect(tester.findText('A FOCUSED'), isNotNull);
});
});
} }
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