Unverified Commit f8520927 authored by hellohuanlin's avatar hellohuanlin Committed by GitHub

Add Focus support for iOS platform view (#103019)

parent 5c135cc5
......@@ -199,6 +199,8 @@ class PlatformViewsService {
/// factory for this view type must have been registered on the platform side.
/// Platform view factories are typically registered by plugin code.
///
/// `onFocus` is a callback that will be invoked when the UIKit view asks to
/// get the input focus.
/// The `id, `viewType, and `layoutDirection` parameters must not be null.
/// If `creationParams` is non null then `creationParamsCodec` must not be null.
static Future<UiKitViewController> initUiKitView({
......@@ -207,6 +209,7 @@ class PlatformViewsService {
required TextDirection layoutDirection,
dynamic creationParams,
MessageCodec<dynamic>? creationParamsCodec,
VoidCallback? onFocus,
}) async {
assert(id != null);
assert(viewType != null);
......@@ -227,6 +230,9 @@ class PlatformViewsService {
);
}
await SystemChannels.platform_views.invokeMethod<void>('create', args);
if (onFocus != null) {
_instance._focusCallbacks[id] = onFocus;
}
return UiKitViewController._(id, layoutDirection);
}
}
......
......@@ -562,6 +562,7 @@ class _UiKitViewState extends State<UiKitView> {
UiKitViewController? _controller;
TextDirection? _layoutDirection;
bool _initialized = false;
late FocusNode _focusNode;
static final Set<Factory<OneSequenceGestureRecognizer>> _emptyRecognizersSet =
<Factory<OneSequenceGestureRecognizer>>{};
......@@ -571,10 +572,14 @@ class _UiKitViewState extends State<UiKitView> {
if (_controller == null) {
return const SizedBox.expand();
}
return _UiKitPlatformView(
controller: _controller!,
hitTestBehavior: widget.hitTestBehavior,
gestureRecognizers: widget.gestureRecognizers ?? _emptyRecognizersSet,
return Focus(
focusNode: _focusNode,
onFocusChange: _onFocusChange,
child: _UiKitPlatformView(
controller: _controller!,
hitTestBehavior: widget.hitTestBehavior,
gestureRecognizers: widget.gestureRecognizers ?? _emptyRecognizersSet,
),
);
}
......@@ -639,13 +644,23 @@ class _UiKitViewState extends State<UiKitView> {
layoutDirection: _layoutDirection!,
creationParams: widget.creationParams,
creationParamsCodec: widget.creationParamsCodec,
onFocus: () {
_focusNode.requestFocus();
}
);
if (!mounted) {
controller.dispose();
return;
}
widget.onPlatformViewCreated?.call(id);
setState(() { _controller = controller; });
setState(() {
_controller = controller;
_focusNode = FocusNode(debugLabel: 'UiKitView(id: $id)');
});
}
void _onFocusChange(bool isFocused) {
// TODO(hellohuanlin): send 'TextInput.setPlatformViewClient' channel message to engine after the engine is updated to handle this message.
}
}
......
......@@ -340,6 +340,13 @@ class FakeIosPlatformViewsController {
_registeredViewTypes.add(viewType);
}
void invokeViewFocused(int viewId) {
final MethodCodec codec = SystemChannels.platform_views.codec;
final ByteData data = codec.encodeMethodCall(MethodCall('viewFocused', viewId));
ServicesBinding.instance.defaultBinaryMessenger
.handlePlatformMessage(SystemChannels.platform_views.name, data, (ByteData? data) {});
}
Future<dynamic> _onMethodCall(MethodCall call) {
switch(call.method) {
case 'create':
......
......@@ -1978,7 +1978,7 @@ void main() {
},
);
testWidgets('AndroidView rebuilt with same gestureRecognizers', (WidgetTester tester) async {
testWidgets('UiKitView rebuilt with same gestureRecognizers', (WidgetTester tester) async {
final FakeIosPlatformViewsController viewsController = FakeIosPlatformViewsController();
viewsController.registerViewType('webview');
......@@ -2012,6 +2012,59 @@ void main() {
expect(factoryInvocationCount, 1);
});
testWidgets('UiKitView can take input focus', (WidgetTester tester) async {
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
final FakeIosPlatformViewsController viewsController = FakeIosPlatformViewsController();
viewsController.registerViewType('webview');
final GlobalKey containerKey = GlobalKey();
await tester.pumpWidget(
Center(
child: Column(
children: <Widget>[
const SizedBox(
width: 200.0,
height: 100.0,
child: UiKitView(viewType: 'webview', layoutDirection: TextDirection.ltr),
),
Focus(
debugLabel: 'container',
child: Container(key: containerKey),
),
],
),
),
);
// First frame is before the platform view was created so the render object
// is not yet in the tree.
await tester.pump();
final Focus uiKitViewFocusWidget = tester.widget(
find.descendant(
of: find.byType(UiKitView),
matching: find.byType(Focus),
),
);
final FocusNode uiKitViewFocusNode = uiKitViewFocusWidget.focusNode!;
final Element containerElement = tester.element(find.byKey(containerKey));
final FocusNode containerFocusNode = Focus.of(containerElement);
containerFocusNode.requestFocus();
await tester.pump();
expect(containerFocusNode.hasFocus, isTrue);
expect(uiKitViewFocusNode.hasFocus, isFalse);
viewsController.invokeViewFocused(currentViewId + 1);
await tester.pump();
expect(containerFocusNode.hasFocus, isFalse);
expect(uiKitViewFocusNode.hasFocus, isTrue);
});
testWidgets('UiKitView has correct semantics', (WidgetTester tester) async {
final SemanticsHandle handle = tester.ensureSemantics();
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
......@@ -2039,7 +2092,14 @@ void main() {
// is not yet in the tree.
await tester.pump();
final SemanticsNode semantics = tester.getSemantics(find.byType(UiKitView));
final SemanticsNode semantics = tester.getSemantics(
find.descendant(
of: find.byType(UiKitView),
matching: find.byWidgetPredicate(
(Widget widget) => widget.runtimeType.toString() == '_UiKitPlatformView',
),
),
);
expect(semantics.platformViewId, currentViewId + 1);
expect(semantics.rect, const Rect.fromLTWH(0, 0, 200, 100));
......
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