Commit 4e83a5cc authored by Adam Barth's avatar Adam Barth

Add the ability to lose focus

Fixes #1308
parent d081dc67
...@@ -90,6 +90,7 @@ class InputState extends ScrollableState<Input> { ...@@ -90,6 +90,7 @@ class InputState extends ScrollableState<Input> {
} }
void _handleTextSubmitted() { void _handleTextSubmitted() {
Focus.clear(context);
if (config.onSubmitted != null) if (config.onSubmitted != null)
config.onSubmitted(_value); config.onSubmitted(_value);
} }
......
...@@ -136,6 +136,7 @@ class EditableString implements KeyboardClient { ...@@ -136,6 +136,7 @@ class EditableString implements KeyboardClient {
} }
void submit(SubmitAction action) { void submit(SubmitAction action) {
composing = const TextRange.empty();
onSubmitted(); onSubmitted();
} }
} }
......
...@@ -25,7 +25,7 @@ class _FocusScope extends InheritedWidget { ...@@ -25,7 +25,7 @@ class _FocusScope extends InheritedWidget {
Widget child Widget child
}) : super(key: key, child: child); }) : super(key: key, child: child);
final FocusState focusState; final _FocusState focusState;
final bool scopeFocused; final bool scopeFocused;
// These are mutable because we implicitly change them when they're null in // These are mutable because we implicitly change them when they're null in
...@@ -144,6 +144,12 @@ class Focus extends StatefulComponent { ...@@ -144,6 +144,12 @@ class Focus extends StatefulComponent {
} }
} }
static void clear(BuildContext context) {
_FocusScope focusScope = context.ancestorWidgetOfExactType(_FocusScope);
if (focusScope != null)
focusScope.focusState._clearFocusedWidget();
}
/// Focuses a particular focus scope, identified by its GlobalKey. The widget /// Focuses a particular focus scope, identified by its GlobalKey. The widget
/// must be in the widget tree. /// must be in the widget tree.
/// ///
...@@ -157,10 +163,10 @@ class Focus extends StatefulComponent { ...@@ -157,10 +163,10 @@ class Focus extends StatefulComponent {
focusScope.focusState._setFocusedScope(key); focusScope.focusState._setFocusedScope(key);
} }
FocusState createState() => new FocusState(); _FocusState createState() => new _FocusState();
} }
class FocusState extends State<Focus> { class _FocusState extends State<Focus> {
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;
...@@ -181,13 +187,20 @@ class FocusState extends State<Focus> { ...@@ -181,13 +187,20 @@ class FocusState extends State<Focus> {
} }
} }
void _handleWidgetRemoved(GlobalKey key) { void _clearFocusedWidget() {
assert(_focusedWidget == key); if (_focusedWidget != null) {
_updateWidgetRemovalListener(null); _updateWidgetRemovalListener(null);
setState(() { setState(() {
_focusedWidget = null; _focusedWidget = null;
}); });
} }
}
void _handleWidgetRemoved(GlobalKey key) {
assert(key != null);
assert(_focusedWidget == key);
_clearFocusedWidget();
}
void _updateWidgetRemovalListener(GlobalKey key) { void _updateWidgetRemovalListener(GlobalKey key) {
if (_currentlyRegisteredWidgetRemovalListenerKey != key) { if (_currentlyRegisteredWidgetRemovalListenerKey != key) {
......
...@@ -7,11 +7,19 @@ import 'package:flutter/widgets.dart'; ...@@ -7,11 +7,19 @@ import 'package:flutter/widgets.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
class TestFocusable extends StatelessComponent { class TestFocusable extends StatelessComponent {
TestFocusable(this.no, this.yes, GlobalKey key) : super(key: key); TestFocusable({
GlobalKey key,
this.no,
this.yes,
this.autofocus: true
}) : super(key: key);
final String no; final String no;
final String yes; final String yes;
final bool autofocus;
Widget build(BuildContext context) { Widget build(BuildContext context) {
bool focused = Focus.at(context, autofocus: true); bool focused = Focus.at(context, autofocus: autofocus);
return new GestureDetector( return new GestureDetector(
onTap: () { Focus.moveTo(key); }, onTap: () { Focus.moveTo(key); },
child: new Text(focused ? yes : no) child: new Text(focused ? yes : no)
...@@ -29,8 +37,16 @@ void main() { ...@@ -29,8 +37,16 @@ void main() {
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
new TestFocusable('b', 'B FOCUSED', keyB), new TestFocusable(
new TestFocusable('a', 'A FOCUSED', keyA), key: keyB,
no: 'b',
yes: 'B FOCUSED'
),
new TestFocusable(
key: keyA,
no: 'a',
yes: 'A FOCUSED'
),
] ]
) )
) )
...@@ -65,4 +81,35 @@ void main() { ...@@ -65,4 +81,35 @@ void main() {
expect(tester.findText('B FOCUSED'), isNull); expect(tester.findText('B FOCUSED'), isNull);
}); });
}); });
test('Can blur', () {
testWidgets((WidgetTester tester) {
GlobalKey keyA = new GlobalKey();
tester.pumpWidget(
new Focus(
child: 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.clear(keyA.currentContext);
tester.pump();
expect(tester.findText('a'), isNotNull);
expect(tester.findText('A FOCUSED'), isNull);
});
});
} }
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