Commit a5351643 authored by Adam Barth's avatar Adam Barth

Keyboard doesn't dismiss when the drawer comes up

When introducing Focus widgets for the Drawer (and ModalRoutes), we weren't
actually giving them the focus. Now we move the focus scope when pushing modal
routes.

Fixes #184
parent 594e7000
...@@ -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