[sdk] Support updating view properties.
This change allows updating the view's focusable, hitTestable and
the new viewInsets property after the view is created.
Bug: 71338
Change-Id: If840ed8d22bdedfe27ab6f15520de9d63fe57cc4
Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/511281
Commit-Queue: Sanjay Chouksey <sanjayc@google.com>
Reviewed-by: David Worsham <dworsham@google.com>
diff --git a/sdk/dart/fuchsia_scenic_flutter/lib/src/fuchsia_view.dart b/sdk/dart/fuchsia_scenic_flutter/lib/src/fuchsia_view.dart
index c3daa5a..c4a8cfe 100644
--- a/sdk/dart/fuchsia_scenic_flutter/lib/src/fuchsia_view.dart
+++ b/sdk/dart/fuchsia_scenic_flutter/lib/src/fuchsia_view.dart
@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+import 'dart:ui';
+
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
@@ -10,7 +12,7 @@
import 'fuchsia_view_controller.dart';
/// A widget that is replaced by content from another process.
-class FuchsiaView extends StatelessWidget {
+class FuchsiaView extends StatefulWidget {
/// The [PlatformViewController] used to control this [FuchsiaView].
final FuchsiaViewController controller;
@@ -24,26 +26,79 @@
/// Defaults to true.
final bool focusable;
+ /// View insets passed to the child view.
+ ///
+ /// Defaults to [Rect.zero].
+ final Rect viewInsets;
+
/// Creates a widget that is replaced by content from another process.
FuchsiaView({
required this.controller,
this.hitTestable = true,
this.focusable = true,
+ this.viewInsets = Rect.zero,
}) : super(key: GlobalObjectKey(controller));
@override
+ _FuchsiaViewState createState() => _FuchsiaViewState();
+}
+
+class _FuchsiaViewState extends State<FuchsiaView> {
+ bool _needsUpdate = false;
+
+ @override
+ void initState() {
+ super.initState();
+
+ // Apply any pending updates once the platform view is connected.
+ widget.controller.whenConnected.then((_) => _updateView());
+ }
+
+ @override
+ void didUpdateWidget(FuchsiaView oldWidget) {
+ super.didUpdateWidget(oldWidget);
+
+ if (widget.focusable != oldWidget.focusable ||
+ widget.hitTestable != oldWidget.hitTestable ||
+ widget.viewInsets != oldWidget.viewInsets) {
+ _needsUpdate = true;
+ _updateView();
+ }
+ }
+
+ // Updates the view attributes on the underlying platform view.
+ //
+ // Called when view's [focusable], [hitTestable] or [viewInsets] have changed
+ // or when the underlying platform view is connected.
+ void _updateView() {
+ if (_needsUpdate == false || !widget.controller.connected) {
+ return;
+ }
+
+ widget.controller.update(
+ focusable: widget.focusable,
+ hitTestable: widget.hitTestable,
+ viewInsets: widget.viewInsets);
+ _needsUpdate = false;
+ }
+
+ @override
Widget build(BuildContext context) {
return PlatformViewLink(
viewType: 'fuchsiaView',
- onCreatePlatformView: (params) => controller
- ..connect(hitTestable: hitTestable, focusable: focusable).then((_) {
- params.onPlatformViewCreated(controller.viewId);
+ onCreatePlatformView: (params) => widget.controller
+ ..connect(
+ hitTestable: widget.hitTestable,
+ focusable: widget.focusable,
+ viewInsets: widget.viewInsets,
+ ).then((_) {
+ params.onPlatformViewCreated(widget.controller.viewId);
}),
surfaceFactory: (context, controller) {
return PlatformViewSurface(
gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{},
controller: controller,
- hitTestBehavior: hitTestable
+ hitTestBehavior: widget.hitTestable
? PlatformViewHitTestBehavior.opaque
: PlatformViewHitTestBehavior.transparent,
);
diff --git a/sdk/dart/fuchsia_scenic_flutter/lib/src/fuchsia_view_controller.dart b/sdk/dart/fuchsia_scenic_flutter/lib/src/fuchsia_view_controller.dart
index 90de812..22bd9f9 100644
--- a/sdk/dart/fuchsia_scenic_flutter/lib/src/fuchsia_view_controller.dart
+++ b/sdk/dart/fuchsia_scenic_flutter/lib/src/fuchsia_view_controller.dart
@@ -4,6 +4,7 @@
// ignore_for_file: avoid_as, unnecessary_null_comparison
+import 'dart:async';
import 'dart:io';
import 'dart:ui';
@@ -53,8 +54,14 @@
@visibleForTesting
MethodChannel get platformViewChannel => _platformViewChannel;
- // Set to true if connected to underlying child view.
- bool _connected = false;
+ // The [Completer] that is completed when the platform view is connected.
+ var _whenConnected = Completer();
+
+ /// The future that completes when the platform view is connected.
+ Future get whenConnected => _whenConnected.future;
+
+ /// Returns true when platform view is connected.
+ bool get connected => _whenConnected.isCompleted;
/// Constructor.
FuchsiaViewController({
@@ -69,18 +76,22 @@
///
/// Called by [FuchsiaView] when the platform view is ready to be initialized
/// and should not be called directly.
- Future<void> connect({bool hitTestable = true, bool focusable = true}) async {
- if (_connected) return;
+ Future<void> connect({
+ bool hitTestable = true,
+ bool focusable = true,
+ Rect viewInsets = Rect.zero,
+ }) async {
+ if (_whenConnected.isCompleted) return;
// Setup callbacks for receiving view events.
platformViewChannel.setMethodCallHandler((call) async {
switch (call.method) {
case 'View.viewConnected':
- _connected = true;
+ _whenConnected.complete();
onViewConnected?.call(this);
break;
case 'View.viewDisconnected':
- _connected = false;
+ _whenConnected = Completer();
onViewDisconnected?.call(this);
break;
case 'View.viewStateChanged':
@@ -96,6 +107,12 @@
'viewId': viewId,
'hitTestable': hitTestable,
'focusable': focusable,
+ 'viewInsetsLTRB': <double>[
+ viewInsets.left,
+ viewInsets.top,
+ viewInsets.right,
+ viewInsets.bottom
+ ],
};
return platformViewChannel.invokeMethod('View.create', args);
}
@@ -114,6 +131,29 @@
onViewDisconnected?.call(this);
}
+ /// Updates properties on the platform view given it's [viewId].
+ ///
+ /// Called by [FuchsiaView] when the [focusable] or [hitTestable] or
+ /// [viewInsets] properties are changed.
+ Future<void> update({
+ bool focusable = true,
+ bool hitTestable = true,
+ Rect viewInsets = Rect.zero,
+ }) async {
+ final args = <String, dynamic>{
+ 'viewId': viewId,
+ 'hitTestable': hitTestable,
+ 'focusable': focusable,
+ 'viewInsetsLTRB': <double>[
+ viewInsets.left,
+ viewInsets.top,
+ viewInsets.right,
+ viewInsets.bottom
+ ],
+ };
+ return platformViewChannel.invokeMethod('View.update', args);
+ }
+
/// Requests that focus be transferred to the remote Scene represented by
/// this connection.
Future<void> requestFocus(int viewRef) async {
diff --git a/sdk/dart/fuchsia_scenic_flutter/test/fuchsia_view_connection_test.dart b/sdk/dart/fuchsia_scenic_flutter/test/fuchsia_view_connection_test.dart
index 112c8cf..9407ffd 100644
--- a/sdk/dart/fuchsia_scenic_flutter/test/fuchsia_view_connection_test.dart
+++ b/sdk/dart/fuchsia_scenic_flutter/test/fuchsia_view_connection_test.dart
@@ -31,6 +31,7 @@
'viewId': 42,
'hitTestable': true,
'focusable': true,
+ 'viewInsetsLTRB': [0, 0, 0, 0],
}));
final methodCallback =
diff --git a/sdk/dart/fuchsia_scenic_flutter/test/fuchsia_view_controller_test.dart b/sdk/dart/fuchsia_scenic_flutter/test/fuchsia_view_controller_test.dart
index f2bdfa3..a130ef8 100644
--- a/sdk/dart/fuchsia_scenic_flutter/test/fuchsia_view_controller_test.dart
+++ b/sdk/dart/fuchsia_scenic_flutter/test/fuchsia_view_controller_test.dart
@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+import 'dart:ui';
+
import 'package:flutter/gestures.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
@@ -27,6 +29,18 @@
'viewId': 42,
'hitTestable': true,
'focusable': true,
+ 'viewInsetsLTRB': [0, 0, 0, 0],
+ }));
+
+ await controller.update(
+ focusable: false,
+ hitTestable: false,
+ viewInsets: Rect.fromLTRB(10, 10, 20, 30));
+ verify(controller.platformViewChannel.invokeMethod('View.update', {
+ 'viewId': 42,
+ 'hitTestable': false,
+ 'focusable': false,
+ 'viewInsetsLTRB': [10, 10, 20, 30],
}));
final methodCallback =
diff --git a/sdk/dart/fuchsia_scenic_flutter/test/fuchsia_view_test.dart b/sdk/dart/fuchsia_scenic_flutter/test/fuchsia_view_test.dart
index 9325d9f..0cf56ae 100644
--- a/sdk/dart/fuchsia_scenic_flutter/test/fuchsia_view_test.dart
+++ b/sdk/dart/fuchsia_scenic_flutter/test/fuchsia_view_test.dart
@@ -14,6 +14,7 @@
final controller = MockFuchsiaViewController();
final completer = Completer();
when(controller.viewId).thenReturn(42);
+ when(controller.whenConnected).thenAnswer((_) => Future<bool>.value(false));
when(controller.connect()).thenAnswer((_) => completer.future);
await tester.pumpWidget(
@@ -28,12 +29,27 @@
await tester.pumpAndSettle();
verify(controller.connect(hitTestable: true, focusable: true));
+
+ // Change properties on the view.
+ when(controller.connected).thenReturn(true);
+
+ await tester.pumpWidget(
+ Center(
+ child: SizedBox(
+ child: FuchsiaView(controller: controller, hitTestable: false),
+ ),
+ ),
+ );
+ await tester.pumpAndSettle();
+
+ verify(controller.update(hitTestable: false));
});
testWidgets('FuchsiaView with args', (tester) async {
final controller = MockFuchsiaViewController();
final completer = Completer();
when(controller.viewId).thenReturn(42);
+ when(controller.whenConnected).thenAnswer((_) => Future<bool>.value(false));
when(controller.connect(hitTestable: false, focusable: false))
.thenAnswer((_) => completer.future);