Unverified Commit 7d27550f authored by Amir Hardon's avatar Amir Hardon Committed by GitHub

Respond to AndroidView focus events. (#33901)

When an AndroidView gains focus we invoke the(newly introduced)
'TextInput.setPlatformViewClient' text_input system channel method
which sets the platform view as the text input target.

When the AndroidView loses focus we send a clearFocus message to
platform views system channel(so the engine can clear the focus from
the platform view).

This PR is going to land before the engine implementation is rolled to
the framework, we swallow MissingPluginException for the newly
introduced method channel methods so this is a no-op before the engine
is ready(after the engine is rolled with the corresponding change I'll
remove the logic to swallow the exceptions).

The engine counterpart is in: flutter/engine#9203
parent fb8df82c
...@@ -583,6 +583,14 @@ class AndroidViewController { ...@@ -583,6 +583,14 @@ class AndroidViewController {
}); });
} }
/// Clears the focus from the Android View if it is focused.
Future<void> clearFocus() {
if (_state != _AndroidViewState.created) {
return null;
}
return SystemChannels.platform_views.invokeMethod<void>('clearFocus', id);
}
static int _getAndroidDirection(TextDirection direction) { static int _getAndroidDirection(TextDirection direction) {
assert(direction != null); assert(direction != null);
switch (direction) { switch (direction) {
......
...@@ -306,6 +306,7 @@ class _AndroidViewState extends State<AndroidView> { ...@@ -306,6 +306,7 @@ class _AndroidViewState extends State<AndroidView> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Focus( return Focus(
focusNode: _focusNode, focusNode: _focusNode,
onFocusChange: _onFocusChange,
child: _AndroidPlatformView( child: _AndroidPlatformView(
controller: _controller, controller: _controller,
hitTestBehavior: widget.hitTestBehavior, hitTestBehavior: widget.hitTestBehavior,
...@@ -384,6 +385,40 @@ class _AndroidViewState extends State<AndroidView> { ...@@ -384,6 +385,40 @@ class _AndroidViewState extends State<AndroidView> {
_controller.addOnPlatformViewCreatedListener(widget.onPlatformViewCreated); _controller.addOnPlatformViewCreatedListener(widget.onPlatformViewCreated);
} }
} }
void _onFocusChange(bool isFocused) {
if (!_controller.isCreated) {
return;
}
if (!isFocused) {
_controller.clearFocus().catchError((dynamic e) {
if (e is MissingPluginException) {
// We land the framework part of Android platform views keyboard
// support before the engine part. There will be a commit range where
// clearFocus isn't implemented in the engine. When that happens we
// just swallow the error here. Once the engine part is rolled to the
// framework I'll remove this.
// TODO(amirh): remove this once the engine's clearFocus is rolled.
return;
}
});
return;
}
SystemChannels.textInput.invokeMethod<void>(
'TextInput.setPlatformViewClient',
_id,
).catchError((dynamic e) {
if (e is MissingPluginException) {
// We land the framework part of Android platform views keyboard
// support before the engine part. There will be a commit range where
// setPlatformViewClient isn't implemented in the engine. When that
// happens we just swallow the error here. Once the engine part is
// rolled to the framework I'll remove this.
// TODO(amirh): remove this once the engine's clearFocus is rolled.
return;
}
});
}
} }
class _UiKitViewState extends State<UiKitView> { class _UiKitViewState extends State<UiKitView> {
......
...@@ -28,6 +28,8 @@ class FakeAndroidPlatformViewsController { ...@@ -28,6 +28,8 @@ class FakeAndroidPlatformViewsController {
Completer<void> createCompleter; Completer<void> createCompleter;
int lastClearedFocusViewId;
void registerViewType(String viewType) { void registerViewType(String viewType) {
_registeredViewTypes.add(viewType); _registeredViewTypes.add(viewType);
} }
...@@ -50,6 +52,8 @@ class FakeAndroidPlatformViewsController { ...@@ -50,6 +52,8 @@ class FakeAndroidPlatformViewsController {
return _touch(call); return _touch(call);
case 'setDirection': case 'setDirection':
return _setDirection(call); return _setDirection(call);
case 'clearFocus':
return _clearFocus(call);
} }
return Future<dynamic>.sync(() => null); return Future<dynamic>.sync(() => null);
} }
...@@ -154,6 +158,19 @@ class FakeAndroidPlatformViewsController { ...@@ -154,6 +158,19 @@ class FakeAndroidPlatformViewsController {
return Future<dynamic>.sync(() => null); return Future<dynamic>.sync(() => null);
} }
Future<dynamic> _clearFocus(MethodCall call) {
final int id = call.arguments;
if (!_views.containsKey(id))
throw PlatformException(
code: 'error',
message: 'Trying to clear the focus on a platform view with unknown id: $id',
);
lastClearedFocusViewId = id;
return Future<dynamic>.sync(() => null);
}
} }
class FakeIosPlatformViewsController { class FakeIosPlatformViewsController {
......
...@@ -908,6 +908,100 @@ void main() { ...@@ -908,6 +908,100 @@ void main() {
expect(containerFocusNode.hasFocus, isFalse); expect(containerFocusNode.hasFocus, isFalse);
expect(androidViewFocusNode.hasFocus, isTrue); expect(androidViewFocusNode.hasFocus, isTrue);
}); });
testWidgets('AndroidView sets a platform view text input client when focused', (WidgetTester tester) async {
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
final FakeAndroidPlatformViewsController viewsController = FakeAndroidPlatformViewsController();
viewsController.registerViewType('webview');
viewsController.createCompleter = Completer<void>();
final GlobalKey containerKey = GlobalKey();
await tester.pumpWidget(
Center(
child: Column(
children: <Widget>[
const SizedBox(
width: 200.0,
height: 100.0,
child: AndroidView(viewType: 'webview', layoutDirection: TextDirection.ltr),
),
Focus(
debugLabel: 'container',
child: Container(key: containerKey),
),
],
),
),
);
viewsController.createCompleter.complete();
final Element containerElement = tester.element(find.byKey(containerKey));
final FocusNode containerFocusNode = Focus.of(containerElement);
containerFocusNode.requestFocus();
await tester.pump();
int lastPlatformViewTextClient;
SystemChannels.textInput.setMockMethodCallHandler((MethodCall call) {
if (call.method == 'TextInput.setPlatformViewClient') {
lastPlatformViewTextClient = call.arguments;
}
return null;
});
viewsController.invokeViewFocused(currentViewId + 1);
await tester.pump();
expect(lastPlatformViewTextClient, currentViewId + 1);
});
testWidgets('AndroidView clears platform focus when unfocused', (WidgetTester tester) async {
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
final FakeAndroidPlatformViewsController viewsController = FakeAndroidPlatformViewsController();
viewsController.registerViewType('webview');
viewsController.createCompleter = Completer<void>();
final GlobalKey containerKey = GlobalKey();
await tester.pumpWidget(
Center(
child: Column(
children: <Widget>[
const SizedBox(
width: 200.0,
height: 100.0,
child: AndroidView(viewType: 'webview', layoutDirection: TextDirection.ltr),
),
Focus(
debugLabel: 'container',
child: Container(key: containerKey),
),
],
),
),
);
viewsController.createCompleter.complete();
final Element containerElement = tester.element(find.byKey(containerKey));
final FocusNode containerFocusNode = Focus.of(containerElement);
containerFocusNode.requestFocus();
await tester.pump();
viewsController.invokeViewFocused(currentViewId + 1);
await tester.pump();
viewsController.lastClearedFocusViewId = null;
containerFocusNode.requestFocus();
await tester.pump();
expect(viewsController.lastClearedFocusViewId, currentViewId + 1);
});
}); });
group('UiKitView', () { group('UiKitView', () {
......
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