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> {
}
LocalHistoryEntry _historyEntry;
// TODO(abarth): This should be a GlobalValueKey when those exist.
GlobalKey get _focusKey => new GlobalObjectKey(config.key);
void _ensureHistoryEntry() {
if (_historyEntry == null) {
......@@ -88,6 +90,7 @@ class DrawerControllerState extends State<DrawerController> {
if (route != null) {
_historyEntry = new LocalHistoryEntry(onRemove: _handleHistoryEntryRemoved);
route.addLocalHistoryEntry(_historyEntry);
Focus.moveScopeTo(_focusKey, context: context);
}
}
}
......@@ -194,7 +197,7 @@ class DrawerControllerState extends State<DrawerController> {
onSizeChanged: _handleSizeChanged,
child: new RepaintBoundary(
child: new Focus(
key: new GlobalObjectKey(config.key),
key: _focusKey,
child: config.child
)
)
......
......@@ -34,9 +34,9 @@ class _FocusScope extends InheritedWidget {
GlobalKey focusedScope;
GlobalKey focusedWidget;
// The ...IfUnset() methods don't need to notify descendants because by
// definition they are only going to make a change the very first time that
// our state is checked.
// The _setFocusedWidgetIfUnset() methodsdon't need to notify descendants
// because by definition they are only going to make a change the very first
// time that our state is checked.
void _setFocusedWidgetIfUnset(GlobalKey key) {
focusState._setFocusedWidgetIfUnset(key);
......@@ -44,12 +44,6 @@ class _FocusScope extends InheritedWidget {
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) {
if (scopeFocused != oldWidget.scopeFocused)
return true;
......@@ -77,14 +71,12 @@ class _FocusScope extends InheritedWidget {
class Focus extends StatefulComponent {
Focus({
GlobalKey key, // key is required if this is a nested Focus scope
this.autofocus: false,
GlobalKey key,
this.child
}) : super(key: key) {
assert(!autofocus || key != null);
assert(key != null);
}
final bool autofocus;
final Widget child;
static GlobalKey debugOnlyFocusedKey;
......@@ -114,15 +106,13 @@ class Focus extends StatefulComponent {
return true;
}
static bool _atScope(BuildContext context, { bool autofocus: false }) {
static bool _atScope(BuildContext context) {
assert(context != null);
assert(context.widget != null);
assert(context.widget is Focus);
assert(context.widget.key != null);
_FocusScope focusScope = context.inheritFromWidgetOfExactType(_FocusScope);
if (focusScope != null) {
assert(context.widget.key != null);
if (autofocus)
focusScope._setFocusedScopeIfUnset(context.widget.key);
return focusScope.scopeFocused &&
focusScope.focusedScope == context.widget.key;
}
......@@ -150,15 +140,20 @@ class Focus extends StatefulComponent {
focusScope.focusState._clearFocusedWidget();
}
/// Focuses a particular focus scope, identified by its GlobalKey. The widget
/// must be in the widget tree.
/// Focuses a particular focus scope, identified by its GlobalKey.
///
/// 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.
static void moveScopeTo(GlobalKey key) {
assert(key.currentWidget is Focus);
assert(key.currentContext != null);
_FocusScope focusScope = key.currentContext.ancestorWidgetOfExactType(_FocusScope);
static void moveScopeTo(GlobalKey key, { BuildContext context }) {
_FocusScope focusScope;
BuildContext searchContext = key.currentContext;
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)
focusScope.focusState._setFocusedScope(key);
}
......@@ -167,6 +162,18 @@ class Focus extends StatefulComponent {
}
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 _currentlyRegisteredWidgetRemovalListenerKey;
......@@ -222,13 +229,6 @@ class _FocusState extends State<Focus> {
_updateScopeRemovalListener(key);
}
void _setFocusedScopeIfUnset(GlobalKey key) {
if (_focusedScope == null) {
_focusedScope = key;
_updateScopeRemovalListener(key);
}
}
void _scopeRemoved(GlobalKey key) {
assert(_focusedScope == key);
GlobalKey.unregisterRemoveListener(_currentlyRegisteredScopeRemovalListenerKey, _scopeRemoved);
......@@ -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;
EdgeDims _mediaPadding;
......
......@@ -339,20 +339,20 @@ class _ModalScopeState extends State<_ModalScope> {
if (config.route.offstage) {
contents = new OffStage(child: contents);
} else {
contents = new Focus(
key: new GlobalObjectKey(config.route),
child: new IgnorePointer(
ignoring: config.route.animation?.status == AnimationStatus.reverse,
child: config.route.buildTransitions(
context,
config.route.animation,
config.route.forwardAnimation,
contents
)
contents = new IgnorePointer(
ignoring: config.route.animation?.status == AnimationStatus.reverse,
child: config.route.buildTransitions(
context,
config.route.animation,
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);
if (position == null)
return contents;
......@@ -401,6 +401,10 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
return child;
}
void didPush() {
Focus.moveScopeTo(new GlobalObjectKey(this), context: navigator.context);
super.didPush();
}
// The API for subclasses to override - used by this class
......
......@@ -30,10 +30,12 @@ class TestFocusable extends StatelessComponent {
void main() {
test('Can have multiple focused children and they update accordingly', () {
testWidgets((WidgetTester tester) {
GlobalKey keyFocus = new GlobalKey();
GlobalKey keyA = new GlobalKey();
GlobalKey keyB = new GlobalKey();
tester.pumpWidget(
new Focus(
key: keyFocus,
child: new Column(
children: <Widget>[
// reverse these when you fix https://github.com/flutter/engine/issues/1495
......@@ -84,9 +86,11 @@ void main() {
test('Can blur', () {
testWidgets((WidgetTester tester) {
GlobalKey keyFocus = new GlobalKey();
GlobalKey keyA = new GlobalKey();
tester.pumpWidget(
new Focus(
key: keyFocus,
child: new TestFocusable(
key: keyA,
no: 'a',
......@@ -112,4 +116,89 @@ void main() {
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