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 {
});
}
/// 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) {
assert(direction != null);
switch (direction) {
......
......@@ -306,6 +306,7 @@ class _AndroidViewState extends State<AndroidView> {
Widget build(BuildContext context) {
return Focus(
focusNode: _focusNode,
onFocusChange: _onFocusChange,
child: _AndroidPlatformView(
controller: _controller,
hitTestBehavior: widget.hitTestBehavior,
......@@ -384,6 +385,40 @@ class _AndroidViewState extends State<AndroidView> {
_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> {
......
......@@ -28,6 +28,8 @@ class FakeAndroidPlatformViewsController {
Completer<void> createCompleter;
int lastClearedFocusViewId;
void registerViewType(String viewType) {
_registeredViewTypes.add(viewType);
}
......@@ -50,6 +52,8 @@ class FakeAndroidPlatformViewsController {
return _touch(call);
case 'setDirection':
return _setDirection(call);
case 'clearFocus':
return _clearFocus(call);
}
return Future<dynamic>.sync(() => null);
}
......@@ -154,6 +158,19 @@ class FakeAndroidPlatformViewsController {
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 {
......
......@@ -908,6 +908,100 @@ void main() {
expect(containerFocusNode.hasFocus, isFalse);
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', () {
......
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