Initial framework support for iOS platform views. (#23412)
This PR adds the full framework stack (layer->render object->widget, and service) for embedding iOS views with minimal functionality. I allowed myself to throw the entire framework stack in one PR since we're mostly mirroring the structure we already established for embedded Android views, so this PR is a little bigger than usual. I'm happy to break it down to the different pieces of the stack if reviewers prefer. Specifically this PR adds: * A UiKitView widget for embedding a UIView in the widget tree. * A RenderUiKitView which is the render object for showing a UIView. * A PlatformViewLayer which denotes the position of a UIView in the layer tree. * The iOS platform_views system channel client code in services/platform_views.dart * Splits the fake platform views controller to an Android and iOS controllers. TBD in following PRs: * Plumb the layout direction all the way to the platform view (currently there is still no engine support for it). * Integrate with gesture arenas (engine support is still missing as well).
This commit is contained in:
parent
50098f149d
commit
67ffe1c2cb
@ -357,6 +357,40 @@ class TextureLayer extends Layer {
|
|||||||
S find<S>(Offset regionOffset) => null;
|
S find<S>(Offset regionOffset) => null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A layer that shows an embedded [UIView](https://developer.apple.com/documentation/uikit/uiview)
|
||||||
|
/// on iOS.
|
||||||
|
class PlatformViewLayer extends Layer {
|
||||||
|
/// Creates a platform view layer.
|
||||||
|
///
|
||||||
|
/// The `rect` and `viewId` parameters must not be null.
|
||||||
|
PlatformViewLayer({
|
||||||
|
@required this.rect,
|
||||||
|
@required this.viewId,
|
||||||
|
}): assert(rect != null), assert(viewId != null);
|
||||||
|
|
||||||
|
/// Bounding rectangle of this layer in the global coordinate space.
|
||||||
|
final Rect rect;
|
||||||
|
|
||||||
|
/// The unique identifier of the UIView displayed on this layer.
|
||||||
|
///
|
||||||
|
/// A UIView with this identifier must have been created by [PlatformViewsServices.initUiKitView].
|
||||||
|
final int viewId;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void addToScene(ui.SceneBuilder builder, [Offset layerOffset = Offset.zero]) {
|
||||||
|
final Rect shiftedRect = rect.shift(layerOffset);
|
||||||
|
builder.addPlatformView(
|
||||||
|
viewId,
|
||||||
|
offset: shiftedRect.topLeft,
|
||||||
|
width: shiftedRect.width,
|
||||||
|
height: shiftedRect.height,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
S find<S>(Offset regionOffset) => null;
|
||||||
|
}
|
||||||
|
|
||||||
/// A layer that indicates to the compositor that it should display
|
/// A layer that indicates to the compositor that it should display
|
||||||
/// certain performance statistics within it.
|
/// certain performance statistics within it.
|
||||||
///
|
///
|
||||||
|
@ -43,8 +43,10 @@ enum _PlatformViewState {
|
|||||||
/// [RenderAndroidView] is responsible for sizing, displaying and passing touch events to an
|
/// [RenderAndroidView] is responsible for sizing, displaying and passing touch events to an
|
||||||
/// Android [View](https://developer.android.com/reference/android/view/View).
|
/// Android [View](https://developer.android.com/reference/android/view/View).
|
||||||
///
|
///
|
||||||
|
/// {@template flutter.rendering.platformView.layout}
|
||||||
/// The render object's layout behavior is to fill all available space, the parent of this object must
|
/// The render object's layout behavior is to fill all available space, the parent of this object must
|
||||||
/// provide bounded layout constraints.
|
/// provide bounded layout constraints.
|
||||||
|
/// {@endtemplate}
|
||||||
///
|
///
|
||||||
/// RenderAndroidView participates in Flutter's [GestureArena]s, and dispatches touch events to the
|
/// RenderAndroidView participates in Flutter's [GestureArena]s, and dispatches touch events to the
|
||||||
/// Android view iff it won the arena. Specific gestures that should be dispatched to the Android
|
/// Android view iff it won the arena. Specific gestures that should be dispatched to the Android
|
||||||
@ -233,6 +235,81 @@ class RenderAndroidView extends RenderBox {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This is work in progress, not yet ready to be used, and requires a custom engine build. A render object for an iOS UIKit UIView.
|
||||||
|
///
|
||||||
|
/// [RenderUiKitView] is responsible for sizing and displaying an iOS
|
||||||
|
/// [UIView](https://developer.apple.com/documentation/uikit/uiview).
|
||||||
|
///
|
||||||
|
/// UIViews are added as sub views of the FlutterView and are composited by Quartz.
|
||||||
|
///
|
||||||
|
/// {@macro flutter.rendering.platformView.layout}
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
/// * [UiKitView] which is a widget that is used to show a UIView.
|
||||||
|
/// * [PlatformViewsService] which is a service for controlling platform views.
|
||||||
|
class RenderUiKitView extends RenderBox {
|
||||||
|
/// Creates a render object for an iOS UIView.
|
||||||
|
///
|
||||||
|
/// The `viewId` and `hitTestBehavior` parameters must not be null.
|
||||||
|
RenderUiKitView({
|
||||||
|
@required int viewId,
|
||||||
|
@required this.hitTestBehavior,
|
||||||
|
}) : assert(viewId != null),
|
||||||
|
assert(hitTestBehavior != null),
|
||||||
|
_viewId = viewId;
|
||||||
|
|
||||||
|
|
||||||
|
/// The unique identifier of the UIView controlled by this controller.
|
||||||
|
///
|
||||||
|
/// Typically generated by [PlatformViewsRegistry.getNextPlatformViewId], the UIView
|
||||||
|
/// must have been created by calling [PlatformViewsService.initUiKitView].
|
||||||
|
int get viewId => _viewId;
|
||||||
|
int _viewId;
|
||||||
|
set viewId(int viewId) {
|
||||||
|
assert(viewId != null);
|
||||||
|
_viewId = viewId;
|
||||||
|
markNeedsPaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// How to behave during hit testing.
|
||||||
|
// The implicit setter is enough here as changing this value will just affect
|
||||||
|
// any newly arriving events there's nothing we need to invalidate.
|
||||||
|
PlatformViewHitTestBehavior hitTestBehavior;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get sizedByParent => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get alwaysNeedsCompositing => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get isRepaintBoundary => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void performResize() {
|
||||||
|
size = constraints.biggest;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void paint(PaintingContext context, Offset offset) {
|
||||||
|
context.addLayer(PlatformViewLayer(
|
||||||
|
rect: offset & size,
|
||||||
|
viewId: _viewId,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool hitTest(HitTestResult result, { Offset position }) {
|
||||||
|
if (hitTestBehavior == PlatformViewHitTestBehavior.transparent || !size.contains(position))
|
||||||
|
return false;
|
||||||
|
result.add(BoxHitTestEntry(this, position));
|
||||||
|
return hitTestBehavior == PlatformViewHitTestBehavior.opaque;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool hitTestSelf(Offset position) => hitTestBehavior != PlatformViewHitTestBehavior.transparent;
|
||||||
|
}
|
||||||
|
|
||||||
class _AndroidViewGestureRecognizer extends OneSequenceGestureRecognizer {
|
class _AndroidViewGestureRecognizer extends OneSequenceGestureRecognizer {
|
||||||
_AndroidViewGestureRecognizer(this.dispatcher, this.gestureRecognizerFactories) {
|
_AndroidViewGestureRecognizer(this.dispatcher, this.gestureRecognizerFactories) {
|
||||||
team = GestureArenaTeam();
|
team = GestureArenaTeam();
|
||||||
|
@ -91,6 +91,46 @@ class PlatformViewsService {
|
|||||||
onPlatformViewCreated,
|
onPlatformViewCreated,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(amirh): reference the iOS plugin API for registering a UIView factory once it lands.
|
||||||
|
/// This is work in progress, not yet ready to be used, and requires a custom engine build. Creates a controller for a new iOS UIView.
|
||||||
|
///
|
||||||
|
/// `id` is an unused unique identifier generated with [platformViewsRegistry].
|
||||||
|
///
|
||||||
|
/// `viewType` is the identifier of the iOS view type to be created, a
|
||||||
|
/// factory for this view type must have been registered on the platform side.
|
||||||
|
/// Platform view factories are typically registered by plugin code.
|
||||||
|
///
|
||||||
|
/// The `id, `viewType, and `layoutDirection` parameters must not be null.
|
||||||
|
/// If `creationParams` is non null then `cretaionParamsCodec` must not be null.
|
||||||
|
static Future<UiKitViewController> initUiKitView({
|
||||||
|
@required int id,
|
||||||
|
@required String viewType,
|
||||||
|
@required TextDirection layoutDirection,
|
||||||
|
dynamic creationParams,
|
||||||
|
MessageCodec<dynamic> creationParamsCodec,
|
||||||
|
}) async {
|
||||||
|
assert(id != null);
|
||||||
|
assert(viewType != null);
|
||||||
|
assert(layoutDirection != null);
|
||||||
|
assert(creationParams == null || creationParamsCodec != null);
|
||||||
|
|
||||||
|
// TODO(amirh): pass layoutDirection once the system channel supports it.
|
||||||
|
final Map<String, dynamic> args = <String, dynamic> {
|
||||||
|
'id': id,
|
||||||
|
'viewType': viewType,
|
||||||
|
};
|
||||||
|
if (creationParams != null) {
|
||||||
|
final ByteData paramsByteData = creationParamsCodec.encodeMessage(creationParams);
|
||||||
|
args['params'] = Uint8List.view(
|
||||||
|
paramsByteData.buffer,
|
||||||
|
0,
|
||||||
|
paramsByteData.lengthInBytes,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
await SystemChannels.platform_views.invokeMethod('create', args);
|
||||||
|
return UiKitViewController._(id, layoutDirection);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Properties of an Android pointer.
|
/// Properties of an Android pointer.
|
||||||
@ -455,8 +495,7 @@ class AndroidViewController {
|
|||||||
///
|
///
|
||||||
/// The first time a size is set triggers the creation of the Android view.
|
/// The first time a size is set triggers the creation of the Android view.
|
||||||
Future<void> setSize(Size size) async {
|
Future<void> setSize(Size size) async {
|
||||||
if (_state == _AndroidViewState.disposed)
|
assert(_state != _AndroidViewState.disposed, 'trying to size a disposed Android View. View id: $id');
|
||||||
throw FlutterError('trying to size a disposed Android View. View id: $id');
|
|
||||||
|
|
||||||
assert(size != null);
|
assert(size != null);
|
||||||
assert(!size.isEmpty);
|
assert(!size.isEmpty);
|
||||||
@ -473,8 +512,7 @@ class AndroidViewController {
|
|||||||
|
|
||||||
/// Sets the layout direction for the Android view.
|
/// Sets the layout direction for the Android view.
|
||||||
Future<void> setLayoutDirection(TextDirection layoutDirection) async {
|
Future<void> setLayoutDirection(TextDirection layoutDirection) async {
|
||||||
if (_state == _AndroidViewState.disposed)
|
assert(_state != _AndroidViewState.disposed,'trying to set a layout direction for a disposed UIView. View id: $id');
|
||||||
throw FlutterError('trying to set a layout direction for a disposed Android View. View id: $id');
|
|
||||||
|
|
||||||
if (layoutDirection == _layoutDirection)
|
if (layoutDirection == _layoutDirection)
|
||||||
return;
|
return;
|
||||||
@ -544,3 +582,48 @@ class AndroidViewController {
|
|||||||
_state = _AndroidViewState.created;
|
_state = _AndroidViewState.created;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Controls an iOS UIView.
|
||||||
|
///
|
||||||
|
/// Typically created with [PlatformViewsService.initUiKitView].
|
||||||
|
class UiKitViewController {
|
||||||
|
UiKitViewController._(
|
||||||
|
this.id,
|
||||||
|
TextDirection layoutDirection,
|
||||||
|
) : assert(id != null),
|
||||||
|
assert(layoutDirection != null),
|
||||||
|
_layoutDirection = layoutDirection;
|
||||||
|
|
||||||
|
|
||||||
|
/// The unique identifier of the iOS view controlled by this controller.
|
||||||
|
///
|
||||||
|
/// This identifer is typically generated by [PlatformViewsRegistry.getNextPlatformViewId].
|
||||||
|
final int id;
|
||||||
|
|
||||||
|
bool _debugDisposed = false;
|
||||||
|
|
||||||
|
TextDirection _layoutDirection;
|
||||||
|
|
||||||
|
/// Sets the layout direction for the Android view.
|
||||||
|
Future<void> setLayoutDirection(TextDirection layoutDirection) async {
|
||||||
|
assert(!_debugDisposed, 'trying to set a layout direction for a disposed Android View. View id: $id');
|
||||||
|
|
||||||
|
if (layoutDirection == _layoutDirection)
|
||||||
|
return;
|
||||||
|
|
||||||
|
assert(layoutDirection != null);
|
||||||
|
_layoutDirection = layoutDirection;
|
||||||
|
|
||||||
|
// TODO(amirh): invoke the iOS platform views channel direction method once available.
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Disposes the view.
|
||||||
|
///
|
||||||
|
/// The [UiKitViewController] object is unusable after calling this.
|
||||||
|
/// The `id` of the platform view cannot be reused after the view is
|
||||||
|
/// disposed.
|
||||||
|
Future<void> dispose() async {
|
||||||
|
_debugDisposed = true;
|
||||||
|
await SystemChannels.platform_views.invokeMethod('dispose', id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -21,8 +21,10 @@ import 'framework.dart';
|
|||||||
/// The embedded Android view is painted just like any other Flutter widget and transformations
|
/// The embedded Android view is painted just like any other Flutter widget and transformations
|
||||||
/// apply to it as well.
|
/// apply to it as well.
|
||||||
///
|
///
|
||||||
/// The widget fill all available space, the parent of this object must provide bounded layout
|
/// {@template flutter.widgets.platformViews.layout}
|
||||||
|
/// The widget fills all available space, the parent of this object must provide bounded layout
|
||||||
/// constraints.
|
/// constraints.
|
||||||
|
/// {@endtemplate}
|
||||||
///
|
///
|
||||||
/// AndroidView participates in Flutter's [GestureArena]s, and dispatches touch events to the
|
/// AndroidView participates in Flutter's [GestureArena]s, and dispatches touch events to the
|
||||||
/// Android view iff it won the arena. Specific gestures that should be dispatched to the Android
|
/// Android view iff it won the arena. Specific gestures that should be dispatched to the Android
|
||||||
@ -41,19 +43,23 @@ import 'framework.dart';
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// The Android view's lifetime is the same as the lifetime of the [State] object for this widget.
|
/// {@template flutter.widgets.platformViews.lifetime}
|
||||||
|
/// The platform view's lifetime is the same as the lifetime of the [State] object for this widget.
|
||||||
/// When the [State] is disposed the platform view (and auxiliary resources) are lazily
|
/// When the [State] is disposed the platform view (and auxiliary resources) are lazily
|
||||||
/// released (some resources are immediately released and some by platform garbage collector).
|
/// released (some resources are immediately released and some by platform garbage collector).
|
||||||
/// A stateful widget's state is disposed when the widget is removed from the tree or when it is
|
/// A stateful widget's state is disposed when the widget is removed from the tree or when it is
|
||||||
/// moved within the tree. If the stateful widget has a key and it's only moved relative to its siblings,
|
/// moved within the tree. If the stateful widget has a key and it's only moved relative to its siblings,
|
||||||
/// or it has a [GlobalKey] and it's moved within the tree, it will not be disposed.
|
/// or it has a [GlobalKey] and it's moved within the tree, it will not be disposed.
|
||||||
|
/// {@endtemplate}
|
||||||
class AndroidView extends StatefulWidget {
|
class AndroidView extends StatefulWidget {
|
||||||
/// Creates a widget that embeds an Android view.
|
/// Creates a widget that embeds an Android view.
|
||||||
///
|
///
|
||||||
|
/// {@template flutter.widgets.platformViews.constructorParams}
|
||||||
/// The `viewType` and `hitTestBehavior` parameters must not be null.
|
/// The `viewType` and `hitTestBehavior` parameters must not be null.
|
||||||
|
/// {@endtemplate}
|
||||||
/// If `creationParams` is not null then `creationParamsCodec` must not be null.
|
/// If `creationParams` is not null then `creationParamsCodec` must not be null.
|
||||||
AndroidView({ // ignore: prefer_const_constructors_in_immutables
|
AndroidView({ // ignore: prefer_const_constructors_in_immutables
|
||||||
// TODO(aam): Remove lint ignore above once dartbug.com/34297 is fixed
|
// TODO(aam): Remove lint ignore above once https://dartbug.com/34297 is fixed
|
||||||
Key key,
|
Key key,
|
||||||
@required this.viewType,
|
@required this.viewType,
|
||||||
this.onPlatformViewCreated,
|
this.onPlatformViewCreated,
|
||||||
@ -68,25 +74,32 @@ class AndroidView extends StatefulWidget {
|
|||||||
super(key: key);
|
super(key: key);
|
||||||
|
|
||||||
/// The unique identifier for Android view type to be embedded by this widget.
|
/// The unique identifier for Android view type to be embedded by this widget.
|
||||||
|
///
|
||||||
/// A [PlatformViewFactory](/javadoc/io/flutter/plugin/platform/PlatformViewFactory.html)
|
/// A [PlatformViewFactory](/javadoc/io/flutter/plugin/platform/PlatformViewFactory.html)
|
||||||
/// for this type must have been registered.
|
/// for this type must have been registered.
|
||||||
///
|
///
|
||||||
/// See also: [AndroidView] for an example of registering a platform view factory.
|
/// See also: [AndroidView] for an example of registering a platform view factory.
|
||||||
final String viewType;
|
final String viewType;
|
||||||
|
|
||||||
/// Callback to invoke after the Android view has been created.
|
/// {@template flutter.widgets.platformViews.createdParam}
|
||||||
|
/// Callback to invoke after the platform view has been created.
|
||||||
///
|
///
|
||||||
/// May be null.
|
/// May be null.
|
||||||
|
/// {@endtemplate}
|
||||||
final PlatformViewCreatedCallback onPlatformViewCreated;
|
final PlatformViewCreatedCallback onPlatformViewCreated;
|
||||||
|
|
||||||
|
/// {@template flutter.widgets.platformViews.hittestParam}
|
||||||
/// How this widget should behave during hit testing.
|
/// How this widget should behave during hit testing.
|
||||||
///
|
///
|
||||||
/// This defaults to [PlatformViewHitTestBehavior.opaque].
|
/// This defaults to [PlatformViewHitTestBehavior.opaque].
|
||||||
|
/// {@endtemplate}
|
||||||
final PlatformViewHitTestBehavior hitTestBehavior;
|
final PlatformViewHitTestBehavior hitTestBehavior;
|
||||||
|
|
||||||
|
/// {@template flutter.widgets.platformViews.directionParam}
|
||||||
/// The text direction to use for the embedded view.
|
/// The text direction to use for the embedded view.
|
||||||
///
|
///
|
||||||
/// If this is null, the ambient [Directionality] is used instead.
|
/// If this is null, the ambient [Directionality] is used instead.
|
||||||
|
/// {@endtemplate}
|
||||||
final TextDirection layoutDirection;
|
final TextDirection layoutDirection;
|
||||||
|
|
||||||
/// Which gestures should be forwarded to the Android view.
|
/// Which gestures should be forwarded to the Android view.
|
||||||
@ -157,7 +170,50 @@ class AndroidView extends StatefulWidget {
|
|||||||
final MessageCodec<dynamic> creationParamsCodec;
|
final MessageCodec<dynamic> creationParamsCodec;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State createState() => _AndroidViewState();
|
State<AndroidView> createState() => _AndroidViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(amirh): describe the embedding mechanism.
|
||||||
|
/// This is work in progress, not yet ready to be used, and requires a custom engine build. Embeds an iOS view in the Widget hierarchy.
|
||||||
|
///
|
||||||
|
/// Embedding iOS views is an expensive operation and should be avoided when a Flutter
|
||||||
|
/// equivalent is possible.
|
||||||
|
///
|
||||||
|
/// {@macro flutter.widgets.platformViews.layout}
|
||||||
|
///
|
||||||
|
/// {@macro flutter.widgets.platformViews.lifetime}
|
||||||
|
class UiKitView extends StatefulWidget {
|
||||||
|
/// Creates a widget that embeds an iOS view.
|
||||||
|
///
|
||||||
|
/// {@macro flutter.widgets.platformViews.constructorParams}
|
||||||
|
UiKitView({ // ignore: prefer_const_constructors_in_immutables
|
||||||
|
// TODO(aam): Remove lint ignore above once https://dartbug.com/34297 is fixed
|
||||||
|
Key key,
|
||||||
|
@required this.viewType,
|
||||||
|
this.onPlatformViewCreated,
|
||||||
|
this.hitTestBehavior = PlatformViewHitTestBehavior.opaque,
|
||||||
|
this.layoutDirection,
|
||||||
|
}) : assert(viewType != null),
|
||||||
|
assert(hitTestBehavior != null),
|
||||||
|
super(key: key);
|
||||||
|
|
||||||
|
// TODO(amirh): reference the iOS API doc once avaliable.
|
||||||
|
/// The unique identifier for iOS view type to be embedded by this widget.
|
||||||
|
///
|
||||||
|
/// A PlatformViewFactory for this type must have been registered.
|
||||||
|
final String viewType;
|
||||||
|
|
||||||
|
/// {@macro flutter.widgets.platformViews.createdParam}
|
||||||
|
final PlatformViewCreatedCallback onPlatformViewCreated;
|
||||||
|
|
||||||
|
/// {@macro flutter.widgets.platformViews.hittestParam}
|
||||||
|
final PlatformViewHitTestBehavior hitTestBehavior;
|
||||||
|
|
||||||
|
/// {@macro flutter.widgets.platformViews.directionParam}
|
||||||
|
final TextDirection layoutDirection;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<UiKitView> createState() => _UiKitViewState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AndroidViewState extends State<AndroidView> {
|
class _AndroidViewState extends State<AndroidView> {
|
||||||
@ -183,19 +239,17 @@ class _AndroidViewState extends State<AndroidView> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_initialized = true;
|
_initialized = true;
|
||||||
_layoutDirection = _findLayoutDirection();
|
|
||||||
_createNewAndroidView();
|
_createNewAndroidView();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didChangeDependencies() {
|
void didChangeDependencies() {
|
||||||
super.didChangeDependencies();
|
super.didChangeDependencies();
|
||||||
_initializeOnce();
|
|
||||||
|
|
||||||
final TextDirection newLayoutDirection = _findLayoutDirection();
|
final TextDirection newLayoutDirection = _findLayoutDirection();
|
||||||
final bool didChangeLayoutDirection = _layoutDirection != newLayoutDirection;
|
final bool didChangeLayoutDirection = _layoutDirection != newLayoutDirection;
|
||||||
_layoutDirection = newLayoutDirection;
|
_layoutDirection = newLayoutDirection;
|
||||||
|
|
||||||
|
_initializeOnce();
|
||||||
if (didChangeLayoutDirection) {
|
if (didChangeLayoutDirection) {
|
||||||
// The native view will update asynchronously, in the meantime we don't want
|
// The native view will update asynchronously, in the meantime we don't want
|
||||||
// to block the framework. (so this is intentionally not awaiting).
|
// to block the framework. (so this is intentionally not awaiting).
|
||||||
@ -246,6 +300,97 @@ class _AndroidViewState extends State<AndroidView> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _UiKitViewState extends State<UiKitView> {
|
||||||
|
int _id;
|
||||||
|
UiKitViewController _controller;
|
||||||
|
TextDirection _layoutDirection;
|
||||||
|
bool _initialized = false;
|
||||||
|
|
||||||
|
static final Set<Factory<OneSequenceGestureRecognizer>> _emptyRecognizersSet =
|
||||||
|
Set<Factory<OneSequenceGestureRecognizer>>();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (_controller == null) {
|
||||||
|
return const SizedBox.expand();
|
||||||
|
}
|
||||||
|
return _UiKitPlatformView(
|
||||||
|
viewId: _id,
|
||||||
|
hitTestBehavior: widget.hitTestBehavior,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _initializeOnce() {
|
||||||
|
if (_initialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_initialized = true;
|
||||||
|
_createNewUiKitView();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeDependencies() {
|
||||||
|
super.didChangeDependencies();
|
||||||
|
final TextDirection newLayoutDirection = _findLayoutDirection();
|
||||||
|
final bool didChangeLayoutDirection = _layoutDirection != newLayoutDirection;
|
||||||
|
_layoutDirection = newLayoutDirection;
|
||||||
|
|
||||||
|
_initializeOnce();
|
||||||
|
if (didChangeLayoutDirection) {
|
||||||
|
// The native view will update asynchronously, in the meantime we don't want
|
||||||
|
// to block the framework. (so this is intentionally not awaiting).
|
||||||
|
_controller?.setLayoutDirection(_layoutDirection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didUpdateWidget(UiKitView oldWidget) {
|
||||||
|
super.didUpdateWidget(oldWidget);
|
||||||
|
|
||||||
|
final TextDirection newLayoutDirection = _findLayoutDirection();
|
||||||
|
final bool didChangeLayoutDirection = _layoutDirection != newLayoutDirection;
|
||||||
|
_layoutDirection = newLayoutDirection;
|
||||||
|
|
||||||
|
if (widget.viewType != oldWidget.viewType) {
|
||||||
|
_controller?.dispose();
|
||||||
|
_createNewUiKitView();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (didChangeLayoutDirection) {
|
||||||
|
_controller?.setLayoutDirection(_layoutDirection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TextDirection _findLayoutDirection() {
|
||||||
|
assert(widget.layoutDirection != null || debugCheckHasDirectionality(context));
|
||||||
|
return widget.layoutDirection ?? Directionality.of(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_controller?.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _createNewUiKitView() async {
|
||||||
|
_id = platformViewsRegistry.getNextPlatformViewId();
|
||||||
|
final UiKitViewController controller = await PlatformViewsService.initUiKitView(
|
||||||
|
id: _id,
|
||||||
|
viewType: widget.viewType,
|
||||||
|
layoutDirection: _layoutDirection,
|
||||||
|
);
|
||||||
|
if (!mounted) {
|
||||||
|
controller.dispose();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (widget.onPlatformViewCreated != null) {
|
||||||
|
widget.onPlatformViewCreated(_id);
|
||||||
|
}
|
||||||
|
setState(() { _controller = controller; });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class _AndroidPlatformView extends LeafRenderObjectWidget {
|
class _AndroidPlatformView extends LeafRenderObjectWidget {
|
||||||
const _AndroidPlatformView({
|
const _AndroidPlatformView({
|
||||||
Key key,
|
Key key,
|
||||||
@ -276,3 +421,30 @@ class _AndroidPlatformView extends LeafRenderObjectWidget {
|
|||||||
renderObject.updateGestureRecognizers(gestureRecognizers);
|
renderObject.updateGestureRecognizers(gestureRecognizers);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _UiKitPlatformView extends LeafRenderObjectWidget {
|
||||||
|
const _UiKitPlatformView({
|
||||||
|
Key key,
|
||||||
|
@required this.viewId,
|
||||||
|
@required this.hitTestBehavior,
|
||||||
|
}) : assert(viewId != null),
|
||||||
|
assert(hitTestBehavior != null),
|
||||||
|
super(key: key);
|
||||||
|
|
||||||
|
final int viewId;
|
||||||
|
final PlatformViewHitTestBehavior hitTestBehavior;
|
||||||
|
|
||||||
|
@override
|
||||||
|
RenderObject createRenderObject(BuildContext context) {
|
||||||
|
return RenderUiKitView(
|
||||||
|
viewId: viewId,
|
||||||
|
hitTestBehavior: hitTestBehavior,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void updateRenderObject(BuildContext context, RenderUiKitView renderObject) {
|
||||||
|
renderObject.viewId = viewId;
|
||||||
|
renderObject.hitTestBehavior = hitTestBehavior;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -6,21 +6,19 @@ import 'dart:async';
|
|||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter/painting.dart';
|
import 'package:flutter/painting.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
class FakePlatformViewsController {
|
class FakeAndroidPlatformViewsController {
|
||||||
FakePlatformViewsController(this.targetPlatform) : assert(targetPlatform != null) {
|
FakeAndroidPlatformViewsController() {
|
||||||
SystemChannels.platform_views.setMockMethodCallHandler(_onMethodCall);
|
SystemChannels.platform_views.setMockMethodCallHandler(_onMethodCall);
|
||||||
}
|
}
|
||||||
|
|
||||||
final TargetPlatform targetPlatform;
|
|
||||||
|
|
||||||
Iterable<FakePlatformView> get views => _views.values;
|
Iterable<FakeAndroidPlatformView> get views => _views.values;
|
||||||
final Map<int, FakePlatformView> _views = <int, FakePlatformView>{};
|
final Map<int, FakeAndroidPlatformView> _views = <int, FakeAndroidPlatformView>{};
|
||||||
|
|
||||||
final Map<int, List<FakeMotionEvent>> motionEvents = <int, List<FakeMotionEvent>>{};
|
final Map<int, List<FakeAndroidMotionEvent>> motionEvents = <int, List<FakeAndroidMotionEvent>>{};
|
||||||
|
|
||||||
final Set<String> _registeredViewTypes = Set<String>();
|
final Set<String> _registeredViewTypes = Set<String>();
|
||||||
|
|
||||||
@ -33,12 +31,6 @@ class FakePlatformViewsController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> _onMethodCall(MethodCall call) {
|
Future<dynamic> _onMethodCall(MethodCall call) {
|
||||||
if (targetPlatform == TargetPlatform.android)
|
|
||||||
return _onMethodCallAndroid(call);
|
|
||||||
return Future<dynamic>.sync(() => null);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<dynamic> _onMethodCallAndroid(MethodCall call) {
|
|
||||||
switch(call.method) {
|
switch(call.method) {
|
||||||
case 'create':
|
case 'create':
|
||||||
return _create(call);
|
return _create(call);
|
||||||
@ -75,7 +67,7 @@ class FakePlatformViewsController {
|
|||||||
message: 'Trying to create a platform view of unregistered type: $viewType',
|
message: 'Trying to create a platform view of unregistered type: $viewType',
|
||||||
);
|
);
|
||||||
|
|
||||||
_views[id] = FakePlatformView(id, viewType, Size(width, height), layoutDirection, creationParams);
|
_views[id] = FakeAndroidPlatformView(id, viewType, Size(width, height), layoutDirection, creationParams);
|
||||||
final int textureId = _textureCounter++;
|
final int textureId = _textureCounter++;
|
||||||
return Future<int>.sync(() => textureId);
|
return Future<int>.sync(() => textureId);
|
||||||
}
|
}
|
||||||
@ -129,9 +121,9 @@ class FakePlatformViewsController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!motionEvents.containsKey(id))
|
if (!motionEvents.containsKey(id))
|
||||||
motionEvents[id] = <FakeMotionEvent> [];
|
motionEvents[id] = <FakeAndroidMotionEvent> [];
|
||||||
|
|
||||||
motionEvents[id].add(FakeMotionEvent(action, pointerIds, pointerOffsets));
|
motionEvents[id].add(FakeAndroidMotionEvent(action, pointerIds, pointerOffsets));
|
||||||
return Future<dynamic>.sync(() => null);
|
return Future<dynamic>.sync(() => null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,9 +144,77 @@ class FakePlatformViewsController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class FakePlatformView {
|
class FakeIosPlatformViewsController {
|
||||||
|
FakeIosPlatformViewsController() {
|
||||||
|
SystemChannels.platform_views.setMockMethodCallHandler(_onMethodCall);
|
||||||
|
}
|
||||||
|
|
||||||
FakePlatformView(this.id, this.type, this.size, this.layoutDirection, [this.creationParams]);
|
|
||||||
|
Iterable<FakeUiKitView> get views => _views.values;
|
||||||
|
final Map<int, FakeUiKitView> _views = <int, FakeUiKitView>{};
|
||||||
|
|
||||||
|
final Set<String> _registeredViewTypes = Set<String>();
|
||||||
|
|
||||||
|
// When this completer is non null, the 'create' method channel call will be
|
||||||
|
// delayed until it completes.
|
||||||
|
Completer<void> creationDelay;
|
||||||
|
|
||||||
|
void registerViewType(String viewType) {
|
||||||
|
_registeredViewTypes.add(viewType);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<dynamic> _onMethodCall(MethodCall call) {
|
||||||
|
switch(call.method) {
|
||||||
|
case 'create':
|
||||||
|
return _create(call);
|
||||||
|
case 'dispose':
|
||||||
|
return _dispose(call);
|
||||||
|
}
|
||||||
|
return Future<dynamic>.sync(() => null);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<dynamic> _create(MethodCall call) async {
|
||||||
|
if (creationDelay != null)
|
||||||
|
await creationDelay.future;
|
||||||
|
final Map<dynamic, dynamic> args = call.arguments;
|
||||||
|
final int id = args['id'];
|
||||||
|
final String viewType = args['viewType'];
|
||||||
|
|
||||||
|
if (_views.containsKey(id)) {
|
||||||
|
throw PlatformException(
|
||||||
|
code: 'error',
|
||||||
|
message: 'Trying to create an already created platform view, view id: $id',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_registeredViewTypes.contains(viewType)) {
|
||||||
|
throw PlatformException(
|
||||||
|
code: 'error',
|
||||||
|
message: 'Trying to create a platform view of unregistered type: $viewType',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_views[id] = FakeUiKitView(id, viewType);
|
||||||
|
return Future<int>.sync(() => null);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<dynamic> _dispose(MethodCall call) {
|
||||||
|
final int id = call.arguments;
|
||||||
|
|
||||||
|
if (!_views.containsKey(id)) {
|
||||||
|
throw PlatformException(
|
||||||
|
code: 'error',
|
||||||
|
message: 'Trying to dispose a platform view with unknown id: $id',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_views.remove(id);
|
||||||
|
return Future<dynamic>.sync(() => null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FakeAndroidPlatformView {
|
||||||
|
FakeAndroidPlatformView(this.id, this.type, this.size, this.layoutDirection, [this.creationParams]);
|
||||||
|
|
||||||
final int id;
|
final int id;
|
||||||
final String type;
|
final String type;
|
||||||
@ -164,9 +224,9 @@ class FakePlatformView {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(dynamic other) {
|
bool operator ==(dynamic other) {
|
||||||
if (other is! FakePlatformView)
|
if (other.runtimeType != FakeAndroidPlatformView)
|
||||||
return false;
|
return false;
|
||||||
final FakePlatformView typedOther = other;
|
final FakeAndroidPlatformView typedOther = other;
|
||||||
return id == typedOther.id &&
|
return id == typedOther.id &&
|
||||||
type == typedOther.type &&
|
type == typedOther.type &&
|
||||||
creationParams == typedOther.creationParams &&
|
creationParams == typedOther.creationParams &&
|
||||||
@ -178,12 +238,12 @@ class FakePlatformView {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'FakePlatformView(id: $id, type: $type, size: $size, layoutDirection: $layoutDirection, creationParams: $creationParams)';
|
return 'FakeAndroidPlatformView(id: $id, type: $type, size: $size, layoutDirection: $layoutDirection, creationParams: $creationParams)';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class FakeMotionEvent {
|
class FakeAndroidMotionEvent {
|
||||||
const FakeMotionEvent(this.action, this.pointerIds, this.pointers);
|
const FakeAndroidMotionEvent(this.action, this.pointerIds, this.pointers);
|
||||||
|
|
||||||
final int action;
|
final int action;
|
||||||
final List<Offset> pointers;
|
final List<Offset> pointers;
|
||||||
@ -192,9 +252,9 @@ class FakeMotionEvent {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(dynamic other) {
|
bool operator ==(dynamic other) {
|
||||||
if (other is! FakeMotionEvent)
|
if (other is! FakeAndroidMotionEvent)
|
||||||
return false;
|
return false;
|
||||||
final FakeMotionEvent typedOther = other;
|
final FakeAndroidMotionEvent typedOther = other;
|
||||||
const ListEquality<Offset> offsetsEq = ListEquality<Offset>();
|
const ListEquality<Offset> offsetsEq = ListEquality<Offset>();
|
||||||
const ListEquality<int> pointersEq = ListEquality<int>();
|
const ListEquality<int> pointersEq = ListEquality<int>();
|
||||||
return pointersEq.equals(pointerIds, typedOther.pointerIds) &&
|
return pointersEq.equals(pointerIds, typedOther.pointerIds) &&
|
||||||
@ -207,6 +267,30 @@ class FakeMotionEvent {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'FakeMotionEvent(action: $action, pointerIds: $pointerIds, pointers: $pointers)';
|
return 'FakeAndroidMotionEvent(action: $action, pointerIds: $pointerIds, pointers: $pointers)';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FakeUiKitView {
|
||||||
|
FakeUiKitView(this.id, this.type);
|
||||||
|
|
||||||
|
final int id;
|
||||||
|
final String type;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(dynamic other) {
|
||||||
|
if (other.runtimeType != FakeUiKitView)
|
||||||
|
return false;
|
||||||
|
final FakeUiKitView typedOther = other;
|
||||||
|
return id == typedOther.id &&
|
||||||
|
type == typedOther.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => hashValues(id, type);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'FakeIosPlatformView(id: $id, type: $type)';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter/painting.dart';
|
import 'package:flutter/painting.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import '../flutter_test_alternative.dart';
|
import '../flutter_test_alternative.dart';
|
||||||
@ -10,11 +9,11 @@ import '../flutter_test_alternative.dart';
|
|||||||
import 'fake_platform_views.dart';
|
import 'fake_platform_views.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
FakePlatformViewsController viewsController;
|
|
||||||
|
|
||||||
group('Android', () {
|
group('Android', () {
|
||||||
|
FakeAndroidPlatformViewsController viewsController;
|
||||||
setUp(() {
|
setUp(() {
|
||||||
viewsController = FakePlatformViewsController(TargetPlatform.android);
|
viewsController = FakeAndroidPlatformViewsController();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('create Android view of unregistered type', () async {
|
test('create Android view of unregistered type', () async {
|
||||||
@ -38,9 +37,9 @@ void main() {
|
|||||||
.setSize(const Size(200.0, 300.0));
|
.setSize(const Size(200.0, 300.0));
|
||||||
expect(
|
expect(
|
||||||
viewsController.views,
|
viewsController.views,
|
||||||
unorderedEquals(<FakePlatformView>[
|
unorderedEquals(<FakeAndroidPlatformView>[
|
||||||
FakePlatformView(0, 'webview', const Size(100.0, 100.0), AndroidViewController.kAndroidLayoutDirectionLtr),
|
FakeAndroidPlatformView(0, 'webview', const Size(100.0, 100.0), AndroidViewController.kAndroidLayoutDirectionLtr),
|
||||||
FakePlatformView(1, 'webview', const Size(200.0, 300.0), AndroidViewController.kAndroidLayoutDirectionRtl),
|
FakeAndroidPlatformView(1, 'webview', const Size(200.0, 300.0), AndroidViewController.kAndroidLayoutDirectionRtl),
|
||||||
]));
|
]));
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -65,11 +64,11 @@ void main() {
|
|||||||
PlatformViewsService.initAndroidView(id: 1, viewType: 'webview', layoutDirection: TextDirection.ltr);
|
PlatformViewsService.initAndroidView(id: 1, viewType: 'webview', layoutDirection: TextDirection.ltr);
|
||||||
await viewController.setSize(const Size(200.0, 300.0));
|
await viewController.setSize(const Size(200.0, 300.0));
|
||||||
|
|
||||||
viewController.dispose();
|
await viewController.dispose();
|
||||||
expect(
|
expect(
|
||||||
viewsController.views,
|
viewsController.views,
|
||||||
unorderedEquals(<FakePlatformView>[
|
unorderedEquals(<FakeAndroidPlatformView>[
|
||||||
FakePlatformView(0, 'webview', const Size(100.0, 100.0), AndroidViewController.kAndroidLayoutDirectionLtr),
|
FakeAndroidPlatformView(0, 'webview', const Size(100.0, 100.0), AndroidViewController.kAndroidLayoutDirectionLtr),
|
||||||
]));
|
]));
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -94,9 +93,9 @@ void main() {
|
|||||||
await viewController.setSize(const Size(500.0, 500.0));
|
await viewController.setSize(const Size(500.0, 500.0));
|
||||||
expect(
|
expect(
|
||||||
viewsController.views,
|
viewsController.views,
|
||||||
unorderedEquals(<FakePlatformView>[
|
unorderedEquals(<FakeAndroidPlatformView>[
|
||||||
FakePlatformView(0, 'webview', const Size(100.0, 100.0), AndroidViewController.kAndroidLayoutDirectionLtr),
|
FakeAndroidPlatformView(0, 'webview', const Size(100.0, 100.0), AndroidViewController.kAndroidLayoutDirectionLtr),
|
||||||
FakePlatformView(1, 'webview', const Size(500.0, 500.0), AndroidViewController.kAndroidLayoutDirectionLtr),
|
FakeAndroidPlatformView(1, 'webview', const Size(500.0, 500.0), AndroidViewController.kAndroidLayoutDirectionLtr),
|
||||||
]));
|
]));
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -129,8 +128,8 @@ void main() {
|
|||||||
await viewController.setSize(const Size(100.0, 100.0));
|
await viewController.setSize(const Size(100.0, 100.0));
|
||||||
expect(
|
expect(
|
||||||
viewsController.views,
|
viewsController.views,
|
||||||
unorderedEquals(<FakePlatformView>[
|
unorderedEquals(<FakeAndroidPlatformView>[
|
||||||
FakePlatformView(0, 'webview', const Size(100.0, 100.0), AndroidViewController.kAndroidLayoutDirectionLtr),
|
FakeAndroidPlatformView(0, 'webview', const Size(100.0, 100.0), AndroidViewController.kAndroidLayoutDirectionLtr),
|
||||||
]));
|
]));
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -142,9 +141,88 @@ void main() {
|
|||||||
await viewController.setLayoutDirection(TextDirection.rtl);
|
await viewController.setLayoutDirection(TextDirection.rtl);
|
||||||
expect(
|
expect(
|
||||||
viewsController.views,
|
viewsController.views,
|
||||||
unorderedEquals(<FakePlatformView>[
|
unorderedEquals(<FakeAndroidPlatformView>[
|
||||||
FakePlatformView(0, 'webview', const Size(100.0, 100.0), AndroidViewController.kAndroidLayoutDirectionRtl),
|
FakeAndroidPlatformView(0, 'webview', const Size(100.0, 100.0), AndroidViewController.kAndroidLayoutDirectionRtl),
|
||||||
]));
|
]));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
group('iOS', ()
|
||||||
|
{
|
||||||
|
FakeIosPlatformViewsController viewsController;
|
||||||
|
setUp(() {
|
||||||
|
viewsController = FakeIosPlatformViewsController();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('create iOS view of unregistered type', () async {
|
||||||
|
expect(
|
||||||
|
() {
|
||||||
|
return PlatformViewsService.initUiKitView(
|
||||||
|
id: 0,
|
||||||
|
viewType: 'web',
|
||||||
|
layoutDirection: TextDirection.ltr,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
throwsA(isInstanceOf<PlatformException>()),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('create iOS views', () async {
|
||||||
|
viewsController.registerViewType('webview');
|
||||||
|
await PlatformViewsService.initUiKitView(
|
||||||
|
id: 0, viewType: 'webview', layoutDirection: TextDirection.ltr);
|
||||||
|
await PlatformViewsService.initUiKitView(
|
||||||
|
id: 1, viewType: 'webview', layoutDirection: TextDirection.rtl);
|
||||||
|
expect(
|
||||||
|
viewsController.views,
|
||||||
|
unorderedEquals(<FakeUiKitView>[
|
||||||
|
FakeUiKitView(0, 'webview'),
|
||||||
|
FakeUiKitView(1, 'webview'),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('reuse iOS view id', () async {
|
||||||
|
viewsController.registerViewType('webview');
|
||||||
|
await PlatformViewsService.initUiKitView(
|
||||||
|
id: 0,
|
||||||
|
viewType: 'webview',
|
||||||
|
layoutDirection: TextDirection.ltr,
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
() => PlatformViewsService.initUiKitView(
|
||||||
|
id: 0, viewType: 'web', layoutDirection: TextDirection.ltr),
|
||||||
|
throwsA(isInstanceOf<PlatformException>()),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('dispose iOS view', () async {
|
||||||
|
viewsController.registerViewType('webview');
|
||||||
|
await PlatformViewsService.initUiKitView(
|
||||||
|
id: 0, viewType: 'webview', layoutDirection: TextDirection.ltr);
|
||||||
|
final UiKitViewController viewController = await PlatformViewsService.initUiKitView(
|
||||||
|
id: 1, viewType: 'webview', layoutDirection: TextDirection.ltr);
|
||||||
|
|
||||||
|
viewController.dispose();
|
||||||
|
expect(
|
||||||
|
viewsController.views,
|
||||||
|
unorderedEquals(<FakeUiKitView>[
|
||||||
|
FakeUiKitView(0, 'webview'),
|
||||||
|
]));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('dispose inexisting iOS view', () async {
|
||||||
|
viewsController.registerViewType('webview');
|
||||||
|
await PlatformViewsService.initUiKitView(id: 0, viewType: 'webview', layoutDirection: TextDirection.ltr);
|
||||||
|
final UiKitViewController viewController = await PlatformViewsService.initUiKitView(
|
||||||
|
id: 1, viewType: 'webview', layoutDirection: TextDirection.ltr);
|
||||||
|
await viewController.dispose();
|
||||||
|
expect(
|
||||||
|
() async {
|
||||||
|
await viewController.dispose();
|
||||||
|
},
|
||||||
|
throwsA(isInstanceOf<PlatformException>()),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
@ -16,9 +16,10 @@ import '../services/fake_platform_views.dart';
|
|||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
|
|
||||||
|
group('AndroidView', () {
|
||||||
testWidgets('Create Android view', (WidgetTester tester) async {
|
testWidgets('Create Android view', (WidgetTester tester) async {
|
||||||
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
|
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
|
||||||
final FakePlatformViewsController viewsController = FakePlatformViewsController(TargetPlatform.android);
|
final FakeAndroidPlatformViewsController viewsController = FakeAndroidPlatformViewsController();
|
||||||
viewsController.registerViewType('webview');
|
viewsController.registerViewType('webview');
|
||||||
|
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
@ -33,15 +34,16 @@ void main() {
|
|||||||
|
|
||||||
expect(
|
expect(
|
||||||
viewsController.views,
|
viewsController.views,
|
||||||
unorderedEquals(<FakePlatformView>[
|
unorderedEquals(<FakeAndroidPlatformView>[
|
||||||
FakePlatformView(currentViewId + 1, 'webview', const Size(200.0, 100.0), AndroidViewController.kAndroidLayoutDirectionLtr)
|
FakeAndroidPlatformView(currentViewId + 1, 'webview', const Size(200.0, 100.0),
|
||||||
|
AndroidViewController.kAndroidLayoutDirectionLtr)
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('Create Android view with params', (WidgetTester tester) async {
|
testWidgets('Create Android view with params', (WidgetTester tester) async {
|
||||||
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
|
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
|
||||||
final FakePlatformViewsController viewsController = FakePlatformViewsController(TargetPlatform.android);
|
final FakeAndroidPlatformViewsController viewsController = FakeAndroidPlatformViewsController();
|
||||||
viewsController.registerViewType('webview');
|
viewsController.registerViewType('webview');
|
||||||
|
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
@ -59,7 +61,7 @@ void main() {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
final FakePlatformView fakeView = viewsController.views.first;
|
final FakeAndroidPlatformView fakeView = viewsController.views.first;
|
||||||
final Uint8List rawCreationParams = fakeView.creationParams;
|
final Uint8List rawCreationParams = fakeView.creationParams;
|
||||||
final ByteData byteData = ByteData.view(
|
final ByteData byteData = ByteData.view(
|
||||||
rawCreationParams.buffer,
|
rawCreationParams.buffer,
|
||||||
@ -71,14 +73,15 @@ void main() {
|
|||||||
expect(actualParams, 'creation parameters');
|
expect(actualParams, 'creation parameters');
|
||||||
expect(
|
expect(
|
||||||
viewsController.views,
|
viewsController.views,
|
||||||
unorderedEquals(<FakePlatformView>[
|
unorderedEquals(<FakeAndroidPlatformView>[
|
||||||
FakePlatformView(currentViewId + 1, 'webview', const Size(200.0, 100.0), AndroidViewController.kAndroidLayoutDirectionLtr, fakeView.creationParams)
|
FakeAndroidPlatformView(currentViewId + 1, 'webview', const Size(200.0, 100.0),
|
||||||
|
AndroidViewController.kAndroidLayoutDirectionLtr, fakeView.creationParams)
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('Zero sized Android view is not created', (WidgetTester tester) async {
|
testWidgets('Zero sized Android view is not created', (WidgetTester tester) async {
|
||||||
final FakePlatformViewsController viewsController = FakePlatformViewsController(TargetPlatform.android);
|
final FakeAndroidPlatformViewsController viewsController = FakeAndroidPlatformViewsController();
|
||||||
viewsController.registerViewType('webview');
|
viewsController.registerViewType('webview');
|
||||||
|
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
@ -99,7 +102,7 @@ void main() {
|
|||||||
|
|
||||||
testWidgets('Resize Android view', (WidgetTester tester) async {
|
testWidgets('Resize Android view', (WidgetTester tester) async {
|
||||||
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
|
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
|
||||||
final FakePlatformViewsController viewsController = FakePlatformViewsController(TargetPlatform.android);
|
final FakeAndroidPlatformViewsController viewsController = FakeAndroidPlatformViewsController();
|
||||||
viewsController.registerViewType('webview');
|
viewsController.registerViewType('webview');
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
Center(
|
Center(
|
||||||
@ -129,8 +132,9 @@ void main() {
|
|||||||
expect(clipRect.clipRect, Rect.fromLTWH(0.0, 0.0, 100.0, 50.0));
|
expect(clipRect.clipRect, Rect.fromLTWH(0.0, 0.0, 100.0, 50.0));
|
||||||
expect(
|
expect(
|
||||||
viewsController.views,
|
viewsController.views,
|
||||||
unorderedEquals(<FakePlatformView>[
|
unorderedEquals(<FakeAndroidPlatformView>[
|
||||||
FakePlatformView(currentViewId + 1, 'webview', const Size(200.0, 100.0), AndroidViewController.kAndroidLayoutDirectionLtr)
|
FakeAndroidPlatformView(currentViewId + 1, 'webview', const Size(200.0, 100.0),
|
||||||
|
AndroidViewController.kAndroidLayoutDirectionLtr)
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -139,15 +143,16 @@ void main() {
|
|||||||
|
|
||||||
expect(
|
expect(
|
||||||
viewsController.views,
|
viewsController.views,
|
||||||
unorderedEquals(<FakePlatformView>[
|
unorderedEquals(<FakeAndroidPlatformView>[
|
||||||
FakePlatformView(currentViewId + 1, 'webview', const Size(100.0, 50.0), AndroidViewController.kAndroidLayoutDirectionLtr)
|
FakeAndroidPlatformView(currentViewId + 1, 'webview', const Size(100.0, 50.0),
|
||||||
|
AndroidViewController.kAndroidLayoutDirectionLtr)
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('Change Android view type', (WidgetTester tester) async {
|
testWidgets('Change Android view type', (WidgetTester tester) async {
|
||||||
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
|
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
|
||||||
final FakePlatformViewsController viewsController = FakePlatformViewsController(TargetPlatform.android);
|
final FakeAndroidPlatformViewsController viewsController = FakeAndroidPlatformViewsController();
|
||||||
viewsController.registerViewType('webview');
|
viewsController.registerViewType('webview');
|
||||||
viewsController.registerViewType('maps');
|
viewsController.registerViewType('maps');
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
@ -172,14 +177,15 @@ void main() {
|
|||||||
|
|
||||||
expect(
|
expect(
|
||||||
viewsController.views,
|
viewsController.views,
|
||||||
unorderedEquals(<FakePlatformView>[
|
unorderedEquals(<FakeAndroidPlatformView>[
|
||||||
FakePlatformView(currentViewId + 2, 'maps', const Size(200.0, 100.0), AndroidViewController.kAndroidLayoutDirectionLtr)
|
FakeAndroidPlatformView(currentViewId + 2, 'maps', const Size(200.0, 100.0),
|
||||||
|
AndroidViewController.kAndroidLayoutDirectionLtr)
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('Dispose Android view', (WidgetTester tester) async {
|
testWidgets('Dispose Android view', (WidgetTester tester) async {
|
||||||
final FakePlatformViewsController viewsController = FakePlatformViewsController(TargetPlatform.android);
|
final FakeAndroidPlatformViewsController viewsController = FakeAndroidPlatformViewsController();
|
||||||
viewsController.registerViewType('webview');
|
viewsController.registerViewType('webview');
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
Center(
|
Center(
|
||||||
@ -208,7 +214,7 @@ void main() {
|
|||||||
|
|
||||||
testWidgets('Android view survives widget tree change', (WidgetTester tester) async {
|
testWidgets('Android view survives widget tree change', (WidgetTester tester) async {
|
||||||
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
|
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
|
||||||
final FakePlatformViewsController viewsController = FakePlatformViewsController(TargetPlatform.android);
|
final FakeAndroidPlatformViewsController viewsController = FakeAndroidPlatformViewsController();
|
||||||
viewsController.registerViewType('webview');
|
viewsController.registerViewType('webview');
|
||||||
final GlobalKey key = GlobalKey();
|
final GlobalKey key = GlobalKey();
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
@ -235,15 +241,16 @@ void main() {
|
|||||||
|
|
||||||
expect(
|
expect(
|
||||||
viewsController.views,
|
viewsController.views,
|
||||||
unorderedEquals(<FakePlatformView>[
|
unorderedEquals(<FakeAndroidPlatformView>[
|
||||||
FakePlatformView(currentViewId + 1, 'webview', const Size(200.0, 100.0), AndroidViewController.kAndroidLayoutDirectionLtr)
|
FakeAndroidPlatformView(currentViewId + 1, 'webview', const Size(200.0, 100.0),
|
||||||
|
AndroidViewController.kAndroidLayoutDirectionLtr)
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('Android view gets touch events', (WidgetTester tester) async {
|
testWidgets('Android view gets touch events', (WidgetTester tester) async {
|
||||||
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
|
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
|
||||||
final FakePlatformViewsController viewsController = FakePlatformViewsController(TargetPlatform.android);
|
final FakeAndroidPlatformViewsController viewsController = FakeAndroidPlatformViewsController();
|
||||||
viewsController.registerViewType('webview');
|
viewsController.registerViewType('webview');
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
Align(
|
Align(
|
||||||
@ -261,16 +268,18 @@ void main() {
|
|||||||
|
|
||||||
expect(
|
expect(
|
||||||
viewsController.motionEvents[currentViewId + 1],
|
viewsController.motionEvents[currentViewId + 1],
|
||||||
orderedEquals(<FakeMotionEvent>[
|
orderedEquals(<FakeAndroidMotionEvent>[
|
||||||
const FakeMotionEvent(AndroidViewController.kActionDown, <int>[0], <Offset>[Offset(50.0, 50.0)]),
|
const FakeAndroidMotionEvent(
|
||||||
const FakeMotionEvent(AndroidViewController.kActionUp, <int>[0], <Offset>[Offset(50.0, 50.0)]),
|
AndroidViewController.kActionDown, <int>[0], <Offset>[Offset(50.0, 50.0)]),
|
||||||
|
const FakeAndroidMotionEvent(
|
||||||
|
AndroidViewController.kActionUp, <int>[0], <Offset>[Offset(50.0, 50.0)]),
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('Android view transparent hit test behavior', (WidgetTester tester) async {
|
testWidgets('Android view transparent hit test behavior', (WidgetTester tester) async {
|
||||||
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
|
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
|
||||||
final FakePlatformViewsController viewsController = FakePlatformViewsController(TargetPlatform.android);
|
final FakeAndroidPlatformViewsController viewsController = FakeAndroidPlatformViewsController();
|
||||||
viewsController.registerViewType('webview');
|
viewsController.registerViewType('webview');
|
||||||
|
|
||||||
int numPointerDownsOnParent = 0;
|
int numPointerDownsOnParent = 0;
|
||||||
@ -281,7 +290,9 @@ void main() {
|
|||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Listener(
|
Listener(
|
||||||
behavior: HitTestBehavior.opaque,
|
behavior: HitTestBehavior.opaque,
|
||||||
onPointerDown: (PointerDownEvent e) { numPointerDownsOnParent++; },
|
onPointerDown: (PointerDownEvent e) {
|
||||||
|
numPointerDownsOnParent++;
|
||||||
|
},
|
||||||
),
|
),
|
||||||
Positioned(
|
Positioned(
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
@ -313,7 +324,7 @@ void main() {
|
|||||||
|
|
||||||
testWidgets('Android view translucent hit test behavior', (WidgetTester tester) async {
|
testWidgets('Android view translucent hit test behavior', (WidgetTester tester) async {
|
||||||
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
|
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
|
||||||
final FakePlatformViewsController viewsController = FakePlatformViewsController(TargetPlatform.android);
|
final FakeAndroidPlatformViewsController viewsController = FakeAndroidPlatformViewsController();
|
||||||
viewsController.registerViewType('webview');
|
viewsController.registerViewType('webview');
|
||||||
|
|
||||||
int numPointerDownsOnParent = 0;
|
int numPointerDownsOnParent = 0;
|
||||||
@ -324,7 +335,9 @@ void main() {
|
|||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Listener(
|
Listener(
|
||||||
behavior: HitTestBehavior.opaque,
|
behavior: HitTestBehavior.opaque,
|
||||||
onPointerDown: (PointerDownEvent e) { numPointerDownsOnParent++; },
|
onPointerDown: (PointerDownEvent e) {
|
||||||
|
numPointerDownsOnParent++;
|
||||||
|
},
|
||||||
),
|
),
|
||||||
Positioned(
|
Positioned(
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
@ -346,8 +359,9 @@ void main() {
|
|||||||
|
|
||||||
expect(
|
expect(
|
||||||
viewsController.motionEvents[currentViewId + 1],
|
viewsController.motionEvents[currentViewId + 1],
|
||||||
orderedEquals(<FakeMotionEvent>[
|
orderedEquals(<FakeAndroidMotionEvent>[
|
||||||
const FakeMotionEvent(AndroidViewController.kActionDown, <int>[0], <Offset>[Offset(50.0, 50.0)]),
|
const FakeAndroidMotionEvent(
|
||||||
|
AndroidViewController.kActionDown, <int>[0], <Offset>[Offset(50.0, 50.0)]),
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
expect(
|
expect(
|
||||||
@ -358,7 +372,7 @@ void main() {
|
|||||||
|
|
||||||
testWidgets('Android view opaque hit test behavior', (WidgetTester tester) async {
|
testWidgets('Android view opaque hit test behavior', (WidgetTester tester) async {
|
||||||
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
|
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
|
||||||
final FakePlatformViewsController viewsController = FakePlatformViewsController(TargetPlatform.android);
|
final FakeAndroidPlatformViewsController viewsController = FakeAndroidPlatformViewsController();
|
||||||
viewsController.registerViewType('webview');
|
viewsController.registerViewType('webview');
|
||||||
|
|
||||||
int numPointerDownsOnParent = 0;
|
int numPointerDownsOnParent = 0;
|
||||||
@ -369,7 +383,9 @@ void main() {
|
|||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Listener(
|
Listener(
|
||||||
behavior: HitTestBehavior.opaque,
|
behavior: HitTestBehavior.opaque,
|
||||||
onPointerDown: (PointerDownEvent e) { numPointerDownsOnParent++; },
|
onPointerDown: (PointerDownEvent e) {
|
||||||
|
numPointerDownsOnParent++;
|
||||||
|
},
|
||||||
),
|
),
|
||||||
Positioned(
|
Positioned(
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
@ -391,8 +407,9 @@ void main() {
|
|||||||
|
|
||||||
expect(
|
expect(
|
||||||
viewsController.motionEvents[currentViewId + 1],
|
viewsController.motionEvents[currentViewId + 1],
|
||||||
orderedEquals(<FakeMotionEvent>[
|
orderedEquals(<FakeAndroidMotionEvent>[
|
||||||
const FakeMotionEvent(AndroidViewController.kActionDown, <int>[0], <Offset>[Offset(50.0, 50.0)]),
|
const FakeAndroidMotionEvent(
|
||||||
|
AndroidViewController.kActionDown, <int>[0], <Offset>[Offset(50.0, 50.0)]),
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
expect(
|
expect(
|
||||||
@ -401,9 +418,10 @@ void main() {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('Android view touch events are in virtual display\'s coordinate system', (WidgetTester tester) async {
|
testWidgets('Android view touch events are in virtual display\'s coordinate system', (
|
||||||
|
WidgetTester tester) async {
|
||||||
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
|
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
|
||||||
final FakePlatformViewsController viewsController = FakePlatformViewsController(TargetPlatform.android);
|
final FakeAndroidPlatformViewsController viewsController = FakeAndroidPlatformViewsController();
|
||||||
viewsController.registerViewType('webview');
|
viewsController.registerViewType('webview');
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
Align(
|
Align(
|
||||||
@ -424,16 +442,18 @@ void main() {
|
|||||||
|
|
||||||
expect(
|
expect(
|
||||||
viewsController.motionEvents[currentViewId + 1],
|
viewsController.motionEvents[currentViewId + 1],
|
||||||
orderedEquals(<FakeMotionEvent>[
|
orderedEquals(<FakeAndroidMotionEvent>[
|
||||||
const FakeMotionEvent(AndroidViewController.kActionDown, <int>[0], <Offset>[Offset(40.0, 40.0)]),
|
const FakeAndroidMotionEvent(
|
||||||
const FakeMotionEvent(AndroidViewController.kActionUp, <int>[0], <Offset>[Offset(40.0, 40.0)]),
|
AndroidViewController.kActionDown, <int>[0], <Offset>[Offset(40.0, 40.0)]),
|
||||||
|
const FakeAndroidMotionEvent(
|
||||||
|
AndroidViewController.kActionUp, <int>[0], <Offset>[Offset(40.0, 40.0)]),
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('Android view directionality', (WidgetTester tester) async {
|
testWidgets('Android view directionality', (WidgetTester tester) async {
|
||||||
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
|
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
|
||||||
final FakePlatformViewsController viewsController = FakePlatformViewsController(TargetPlatform.android);
|
final FakeAndroidPlatformViewsController viewsController = FakeAndroidPlatformViewsController();
|
||||||
viewsController.registerViewType('maps');
|
viewsController.registerViewType('maps');
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
Center(
|
Center(
|
||||||
@ -447,8 +467,9 @@ void main() {
|
|||||||
|
|
||||||
expect(
|
expect(
|
||||||
viewsController.views,
|
viewsController.views,
|
||||||
unorderedEquals(<FakePlatformView>[
|
unorderedEquals(<FakeAndroidPlatformView>[
|
||||||
FakePlatformView(currentViewId + 1, 'maps', const Size(200.0, 100.0), AndroidViewController.kAndroidLayoutDirectionRtl)
|
FakeAndroidPlatformView(currentViewId + 1, 'maps', const Size(200.0, 100.0),
|
||||||
|
AndroidViewController.kAndroidLayoutDirectionRtl)
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -464,15 +485,16 @@ void main() {
|
|||||||
|
|
||||||
expect(
|
expect(
|
||||||
viewsController.views,
|
viewsController.views,
|
||||||
unorderedEquals(<FakePlatformView>[
|
unorderedEquals(<FakeAndroidPlatformView>[
|
||||||
FakePlatformView(currentViewId + 1, 'maps', const Size(200.0, 100.0), AndroidViewController.kAndroidLayoutDirectionLtr)
|
FakeAndroidPlatformView(currentViewId + 1, 'maps', const Size(200.0, 100.0),
|
||||||
|
AndroidViewController.kAndroidLayoutDirectionLtr)
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('Android view ambient directionality', (WidgetTester tester) async {
|
testWidgets('Android view ambient directionality', (WidgetTester tester) async {
|
||||||
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
|
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
|
||||||
final FakePlatformViewsController viewsController = FakePlatformViewsController(TargetPlatform.android);
|
final FakeAndroidPlatformViewsController viewsController = FakeAndroidPlatformViewsController();
|
||||||
viewsController.registerViewType('maps');
|
viewsController.registerViewType('maps');
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
Directionality(
|
Directionality(
|
||||||
@ -489,8 +511,9 @@ void main() {
|
|||||||
|
|
||||||
expect(
|
expect(
|
||||||
viewsController.views,
|
viewsController.views,
|
||||||
unorderedEquals(<FakePlatformView>[
|
unorderedEquals(<FakeAndroidPlatformView>[
|
||||||
FakePlatformView(currentViewId + 1, 'maps', const Size(200.0, 100.0), AndroidViewController.kAndroidLayoutDirectionRtl)
|
FakeAndroidPlatformView(currentViewId + 1, 'maps', const Size(200.0, 100.0),
|
||||||
|
AndroidViewController.kAndroidLayoutDirectionRtl)
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -509,15 +532,16 @@ void main() {
|
|||||||
|
|
||||||
expect(
|
expect(
|
||||||
viewsController.views,
|
viewsController.views,
|
||||||
unorderedEquals(<FakePlatformView>[
|
unorderedEquals(<FakeAndroidPlatformView>[
|
||||||
FakePlatformView(currentViewId + 1, 'maps', const Size(200.0, 100.0), AndroidViewController.kAndroidLayoutDirectionLtr)
|
FakeAndroidPlatformView(currentViewId + 1, 'maps', const Size(200.0, 100.0),
|
||||||
|
AndroidViewController.kAndroidLayoutDirectionLtr)
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('Android view can lose gesture arenas', (WidgetTester tester) async {
|
testWidgets('Android view can lose gesture arenas', (WidgetTester tester) async {
|
||||||
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
|
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
|
||||||
final FakePlatformViewsController viewsController = FakePlatformViewsController(TargetPlatform.android);
|
final FakeAndroidPlatformViewsController viewsController = FakeAndroidPlatformViewsController();
|
||||||
viewsController.registerViewType('webview');
|
viewsController.registerViewType('webview');
|
||||||
bool verticalDragAcceptedByParent = false;
|
bool verticalDragAcceptedByParent = false;
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
@ -526,7 +550,9 @@ void main() {
|
|||||||
child: Container(
|
child: Container(
|
||||||
margin: const EdgeInsets.all(10.0),
|
margin: const EdgeInsets.all(10.0),
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onVerticalDragStart: (DragStartDetails d) { verticalDragAcceptedByParent = true; },
|
onVerticalDragStart: (DragStartDetails d) {
|
||||||
|
verticalDragAcceptedByParent = true;
|
||||||
|
},
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: 200.0,
|
width: 200.0,
|
||||||
height: 100.0,
|
height: 100.0,
|
||||||
@ -550,14 +576,16 @@ void main() {
|
|||||||
|
|
||||||
testWidgets('Android view gesture recognizers', (WidgetTester tester) async {
|
testWidgets('Android view gesture recognizers', (WidgetTester tester) async {
|
||||||
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
|
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
|
||||||
final FakePlatformViewsController viewsController = FakePlatformViewsController(TargetPlatform.android);
|
final FakeAndroidPlatformViewsController viewsController = FakeAndroidPlatformViewsController();
|
||||||
viewsController.registerViewType('webview');
|
viewsController.registerViewType('webview');
|
||||||
bool verticalDragAcceptedByParent = false;
|
bool verticalDragAcceptedByParent = false;
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
Align(
|
Align(
|
||||||
alignment: Alignment.topLeft,
|
alignment: Alignment.topLeft,
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onVerticalDragStart: (DragStartDetails d) { verticalDragAcceptedByParent = true; },
|
onVerticalDragStart: (DragStartDetails d) {
|
||||||
|
verticalDragAcceptedByParent = true;
|
||||||
|
},
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: 200.0,
|
width: 200.0,
|
||||||
height: 100.0,
|
height: 100.0,
|
||||||
@ -582,17 +610,21 @@ void main() {
|
|||||||
expect(verticalDragAcceptedByParent, false);
|
expect(verticalDragAcceptedByParent, false);
|
||||||
expect(
|
expect(
|
||||||
viewsController.motionEvents[currentViewId + 1],
|
viewsController.motionEvents[currentViewId + 1],
|
||||||
orderedEquals(<FakeMotionEvent>[
|
orderedEquals(<FakeAndroidMotionEvent>[
|
||||||
const FakeMotionEvent(AndroidViewController.kActionDown, <int>[0], <Offset>[Offset(50.0, 50.0)]),
|
const FakeAndroidMotionEvent(
|
||||||
const FakeMotionEvent(AndroidViewController.kActionMove, <int>[0], <Offset>[Offset(50.0, 150.0)]),
|
AndroidViewController.kActionDown, <int>[0], <Offset>[Offset(50.0, 50.0)]),
|
||||||
const FakeMotionEvent(AndroidViewController.kActionUp, <int>[0], <Offset>[Offset(50.0, 150.0)]),
|
const FakeAndroidMotionEvent(
|
||||||
|
AndroidViewController.kActionMove, <int>[0], <Offset>[Offset(50.0, 150.0)]),
|
||||||
|
const FakeAndroidMotionEvent(
|
||||||
|
AndroidViewController.kActionUp, <int>[0], <Offset>[Offset(50.0, 150.0)]),
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('Android view can claim gesture after all pointers are up', (WidgetTester tester) async {
|
testWidgets(
|
||||||
|
'Android view can claim gesture after all pointers are up', (WidgetTester tester) async {
|
||||||
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
|
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
|
||||||
final FakePlatformViewsController viewsController = FakePlatformViewsController(TargetPlatform.android);
|
final FakeAndroidPlatformViewsController viewsController = FakeAndroidPlatformViewsController();
|
||||||
viewsController.registerViewType('webview');
|
viewsController.registerViewType('webview');
|
||||||
bool verticalDragAcceptedByParent = false;
|
bool verticalDragAcceptedByParent = false;
|
||||||
// The long press recognizer rejects the gesture after the AndroidView gets the pointer up event.
|
// The long press recognizer rejects the gesture after the AndroidView gets the pointer up event.
|
||||||
@ -601,7 +633,9 @@ void main() {
|
|||||||
Align(
|
Align(
|
||||||
alignment: Alignment.topLeft,
|
alignment: Alignment.topLeft,
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onVerticalDragStart: (DragStartDetails d) { verticalDragAcceptedByParent = true; },
|
onVerticalDragStart: (DragStartDetails d) {
|
||||||
|
verticalDragAcceptedByParent = true;
|
||||||
|
},
|
||||||
onLongPress: () {},
|
onLongPress: () {},
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: 200.0,
|
width: 200.0,
|
||||||
@ -621,16 +655,18 @@ void main() {
|
|||||||
expect(verticalDragAcceptedByParent, false);
|
expect(verticalDragAcceptedByParent, false);
|
||||||
expect(
|
expect(
|
||||||
viewsController.motionEvents[currentViewId + 1],
|
viewsController.motionEvents[currentViewId + 1],
|
||||||
orderedEquals(<FakeMotionEvent>[
|
orderedEquals(<FakeAndroidMotionEvent>[
|
||||||
const FakeMotionEvent(AndroidViewController.kActionDown, <int>[0], <Offset>[Offset(50.0, 50.0)]),
|
const FakeAndroidMotionEvent(
|
||||||
const FakeMotionEvent(AndroidViewController.kActionUp, <int>[0], <Offset>[Offset(50.0, 50.0)]),
|
AndroidViewController.kActionDown, <int>[0], <Offset>[Offset(50.0, 50.0)]),
|
||||||
|
const FakeAndroidMotionEvent(
|
||||||
|
AndroidViewController.kActionUp, <int>[0], <Offset>[Offset(50.0, 50.0)]),
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('Android view rebuilt during gesture', (WidgetTester tester) async {
|
testWidgets('Android view rebuilt during gesture', (WidgetTester tester) async {
|
||||||
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
|
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
|
||||||
final FakePlatformViewsController viewsController = FakePlatformViewsController(TargetPlatform.android);
|
final FakeAndroidPlatformViewsController viewsController = FakeAndroidPlatformViewsController();
|
||||||
viewsController.registerViewType('webview');
|
viewsController.registerViewType('webview');
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
Align(
|
Align(
|
||||||
@ -667,17 +703,20 @@ void main() {
|
|||||||
|
|
||||||
expect(
|
expect(
|
||||||
viewsController.motionEvents[currentViewId + 1],
|
viewsController.motionEvents[currentViewId + 1],
|
||||||
orderedEquals(<FakeMotionEvent>[
|
orderedEquals(<FakeAndroidMotionEvent>[
|
||||||
const FakeMotionEvent(AndroidViewController.kActionDown, <int>[0], <Offset>[Offset(50.0, 50.0)]),
|
const FakeAndroidMotionEvent(
|
||||||
const FakeMotionEvent(AndroidViewController.kActionMove, <int>[0], <Offset>[Offset(50.0, 150.0)]),
|
AndroidViewController.kActionDown, <int>[0], <Offset>[Offset(50.0, 50.0)]),
|
||||||
const FakeMotionEvent(AndroidViewController.kActionUp, <int>[0], <Offset>[Offset(50.0, 150.0)]),
|
const FakeAndroidMotionEvent(
|
||||||
|
AndroidViewController.kActionMove, <int>[0], <Offset>[Offset(50.0, 150.0)]),
|
||||||
|
const FakeAndroidMotionEvent(
|
||||||
|
AndroidViewController.kActionUp, <int>[0], <Offset>[Offset(50.0, 150.0)]),
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('Android view with eager gesture recognizer', (WidgetTester tester) async {
|
testWidgets('Android view with eager gesture recognizer', (WidgetTester tester) async {
|
||||||
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
|
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
|
||||||
final FakePlatformViewsController viewsController = FakePlatformViewsController(TargetPlatform.android);
|
final FakeAndroidPlatformViewsController viewsController = FakeAndroidPlatformViewsController();
|
||||||
viewsController.registerViewType('webview');
|
viewsController.registerViewType('webview');
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
Align(
|
Align(
|
||||||
@ -709,14 +748,16 @@ void main() {
|
|||||||
// pointer down event is immediately dispatched.
|
// pointer down event is immediately dispatched.
|
||||||
expect(
|
expect(
|
||||||
viewsController.motionEvents[currentViewId + 1],
|
viewsController.motionEvents[currentViewId + 1],
|
||||||
orderedEquals(<FakeMotionEvent>[
|
orderedEquals(<FakeAndroidMotionEvent>[
|
||||||
const FakeMotionEvent(AndroidViewController.kActionDown, <int>[0], <Offset>[Offset(50.0, 50.0)]),
|
const FakeAndroidMotionEvent(
|
||||||
|
AndroidViewController.kActionDown, <int>[0], <Offset>[Offset(50.0, 50.0)]),
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('RenderAndroidView reconstructed with same gestureRecognizers', (WidgetTester tester) async {
|
testWidgets('RenderAndroidView reconstructed with same gestureRecognizers', (
|
||||||
final FakePlatformViewsController viewsController = FakePlatformViewsController(TargetPlatform.android);
|
WidgetTester tester) async {
|
||||||
|
final FakeAndroidPlatformViewsController viewsController = FakeAndroidPlatformViewsController();
|
||||||
viewsController.registerViewType('webview');
|
viewsController.registerViewType('webview');
|
||||||
|
|
||||||
final AndroidView androidView = AndroidView(
|
final AndroidView androidView = AndroidView(
|
||||||
@ -735,7 +776,7 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('AndroidView rebuilt with same gestureRecognizers', (WidgetTester tester) async {
|
testWidgets('AndroidView rebuilt with same gestureRecognizers', (WidgetTester tester) async {
|
||||||
final FakePlatformViewsController viewsController = FakePlatformViewsController(TargetPlatform.android);
|
final FakeAndroidPlatformViewsController viewsController = FakeAndroidPlatformViewsController();
|
||||||
viewsController.registerViewType('webview');
|
viewsController.registerViewType('webview');
|
||||||
|
|
||||||
int factoryInvocationCount = 0;
|
int factoryInvocationCount = 0;
|
||||||
@ -767,4 +808,158 @@ void main() {
|
|||||||
|
|
||||||
expect(factoryInvocationCount, 1);
|
expect(factoryInvocationCount, 1);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('UiKitView', () {
|
||||||
|
testWidgets('Create UIView', (WidgetTester tester) async {
|
||||||
|
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
|
||||||
|
final FakeIosPlatformViewsController viewsController = FakeIosPlatformViewsController();
|
||||||
|
viewsController.registerViewType('webview');
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
Center(
|
||||||
|
child: SizedBox(
|
||||||
|
width: 200.0,
|
||||||
|
height: 100.0,
|
||||||
|
child: UiKitView(viewType: 'webview', layoutDirection: TextDirection.ltr),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
viewsController.views,
|
||||||
|
unorderedEquals(<FakeUiKitView>[
|
||||||
|
FakeUiKitView(currentViewId + 1, 'webview')
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('Change UIView view type', (WidgetTester tester) async {
|
||||||
|
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
|
||||||
|
final FakeIosPlatformViewsController viewsController = FakeIosPlatformViewsController();
|
||||||
|
viewsController.registerViewType('webview');
|
||||||
|
viewsController.registerViewType('maps');
|
||||||
|
await tester.pumpWidget(
|
||||||
|
Center(
|
||||||
|
child: SizedBox(
|
||||||
|
width: 200.0,
|
||||||
|
height: 100.0,
|
||||||
|
child: UiKitView(viewType: 'webview', layoutDirection: TextDirection.ltr),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
Center(
|
||||||
|
child: SizedBox(
|
||||||
|
width: 200.0,
|
||||||
|
height: 100.0,
|
||||||
|
child: UiKitView(viewType: 'maps', layoutDirection: TextDirection.ltr),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
viewsController.views,
|
||||||
|
unorderedEquals(<FakeUiKitView>[
|
||||||
|
FakeUiKitView(currentViewId + 2, 'maps')
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('Dispose UIView ', (WidgetTester tester) async {
|
||||||
|
final FakeIosPlatformViewsController viewsController = FakeIosPlatformViewsController();
|
||||||
|
viewsController.registerViewType('webview');
|
||||||
|
await tester.pumpWidget(
|
||||||
|
Center(
|
||||||
|
child: SizedBox(
|
||||||
|
width: 200.0,
|
||||||
|
height: 100.0,
|
||||||
|
child: UiKitView(viewType: 'webview', layoutDirection: TextDirection.ltr),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
const Center(
|
||||||
|
child: SizedBox(
|
||||||
|
width: 200.0,
|
||||||
|
height: 100.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
viewsController.views,
|
||||||
|
isEmpty,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('Dispose UIView before creation completed ', (WidgetTester tester) async {
|
||||||
|
final FakeIosPlatformViewsController viewsController = FakeIosPlatformViewsController();
|
||||||
|
viewsController.registerViewType('webview');
|
||||||
|
viewsController.creationDelay = Completer<void>();
|
||||||
|
await tester.pumpWidget(
|
||||||
|
Center(
|
||||||
|
child: SizedBox(
|
||||||
|
width: 200.0,
|
||||||
|
height: 100.0,
|
||||||
|
child: UiKitView(viewType: 'webview', layoutDirection: TextDirection.ltr),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
const Center(
|
||||||
|
child: SizedBox(
|
||||||
|
width: 200.0,
|
||||||
|
height: 100.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
viewsController.creationDelay.complete();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
viewsController.views,
|
||||||
|
isEmpty,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('UIView survives widget tree change', (WidgetTester tester) async {
|
||||||
|
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
|
||||||
|
final FakeIosPlatformViewsController viewsController = FakeIosPlatformViewsController();
|
||||||
|
viewsController.registerViewType('webview');
|
||||||
|
final GlobalKey key = GlobalKey();
|
||||||
|
await tester.pumpWidget(
|
||||||
|
Center(
|
||||||
|
child: SizedBox(
|
||||||
|
width: 200.0,
|
||||||
|
height: 100.0,
|
||||||
|
child: UiKitView(viewType: 'webview', layoutDirection: TextDirection.ltr, key: key),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
Center(
|
||||||
|
child: Container(
|
||||||
|
child: SizedBox(
|
||||||
|
width: 200.0,
|
||||||
|
height: 100.0,
|
||||||
|
child: UiKitView(viewType: 'webview', layoutDirection: TextDirection.ltr, key: key),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
viewsController.views,
|
||||||
|
unorderedEquals(<FakeUiKitView>[
|
||||||
|
FakeUiKitView(currentViewId + 1, 'webview')
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user