mirror of
https://github.com/encounter/engine.git
synced 2026-03-30 11:09:55 -07:00
Add tests for platform views (#11319)
This commit is contained in:
+60
-48
@@ -17,66 +17,78 @@ import io.flutter.Log;
|
||||
import io.flutter.embedding.android.FlutterActivity;
|
||||
import io.flutter.embedding.android.FlutterFragment;
|
||||
import io.flutter.embedding.android.FlutterView;
|
||||
import io.flutter.embedding.engine.FlutterEngine;
|
||||
import io.flutter.embedding.engine.FlutterShellArgs;
|
||||
import io.flutter.embedding.engine.renderer.OnFirstFrameRenderedListener;
|
||||
import io.flutter.plugin.common.BasicMessageChannel;
|
||||
import io.flutter.plugin.common.BinaryCodec;
|
||||
|
||||
public class MainActivity extends FlutterActivity implements OnFirstFrameRenderedListener {
|
||||
final static String TAG = "Scenarios";
|
||||
final static String TAG = "Scenarios";
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
final Intent launchIntent = getIntent();
|
||||
if ("com.google.intent.action.TEST_LOOP".equals(launchIntent.getAction())) {
|
||||
if(Build.VERSION.SDK_INT > 22){
|
||||
requestPermissions(new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
|
||||
}
|
||||
// Run for one minute, get the timeline data, write it, and finish.
|
||||
final Uri logFileUri = launchIntent.getData();
|
||||
new Handler().postDelayed(() -> writeTimelineData(logFileUri), 20000);
|
||||
}
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
final Intent launchIntent = getIntent();
|
||||
if ("com.google.intent.action.TEST_LOOP".equals(launchIntent.getAction())) {
|
||||
if (Build.VERSION.SDK_INT > 22) {
|
||||
requestPermissions(new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
|
||||
}
|
||||
// Run for one minute, get the timeline data, write it, and finish.
|
||||
final Uri logFileUri = launchIntent.getData();
|
||||
new Handler().postDelayed(() -> writeTimelineData(logFileUri), 20000);
|
||||
}
|
||||
}
|
||||
|
||||
public void onFirstFrameRendered() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
reportFullyDrawn();
|
||||
}
|
||||
public void onFirstFrameRendered() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
reportFullyDrawn();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public FlutterShellArgs getFlutterShellArgs() {
|
||||
FlutterShellArgs args = FlutterShellArgs.fromIntent(getIntent());
|
||||
args.add(FlutterShellArgs.ARG_TRACE_STARTUP);
|
||||
args.add(FlutterShellArgs.ARG_ENABLE_DART_PROFILING);
|
||||
args.add(FlutterShellArgs.ARG_VERBOSE_LOGGING);
|
||||
@Override
|
||||
@NonNull
|
||||
public FlutterShellArgs getFlutterShellArgs() {
|
||||
FlutterShellArgs args = FlutterShellArgs.fromIntent(getIntent());
|
||||
args.add(FlutterShellArgs.ARG_TRACE_STARTUP);
|
||||
args.add(FlutterShellArgs.ARG_ENABLE_DART_PROFILING);
|
||||
args.add(FlutterShellArgs.ARG_VERBOSE_LOGGING);
|
||||
|
||||
return args;
|
||||
return args;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureFlutterEngine(FlutterEngine flutterEngine) {
|
||||
flutterEngine.getPlatformViewsController()
|
||||
.getRegistry()
|
||||
.registerViewFactory(
|
||||
"scenarios/textPlatformView",
|
||||
new TextPlatformViewFactory());
|
||||
}
|
||||
|
||||
|
||||
private void writeTimelineData(Uri logFile) {
|
||||
if (logFile == null) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
private void writeTimelineData(Uri logFile) {
|
||||
if (logFile == null) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
if (getFlutterEngine() == null) {
|
||||
Log.e(TAG, "Could not write timeline data - no engine.");
|
||||
return;
|
||||
}
|
||||
final BasicMessageChannel<ByteBuffer> channel = new BasicMessageChannel<>(
|
||||
getFlutterEngine().getDartExecutor(), "write_timeline", BinaryCodec.INSTANCE);
|
||||
channel.send(null, (ByteBuffer reply) -> {
|
||||
try {
|
||||
final FileDescriptor fd = getContentResolver()
|
||||
.openAssetFileDescriptor(logFile, "w").getFileDescriptor();
|
||||
final FileOutputStream outputStream = new FileOutputStream(fd);
|
||||
outputStream.write(reply.array());
|
||||
outputStream.close();
|
||||
} catch (IOException ex) {
|
||||
Log.e(TAG, "Could not write timeline file: " + ex.toString());
|
||||
}
|
||||
finish();
|
||||
});
|
||||
if (getFlutterEngine() == null) {
|
||||
Log.e(TAG, "Could not write timeline data - no engine.");
|
||||
return;
|
||||
}
|
||||
final BasicMessageChannel<ByteBuffer> channel = new BasicMessageChannel<>(
|
||||
getFlutterEngine().getDartExecutor(), "write_timeline", BinaryCodec.INSTANCE);
|
||||
channel.send(null, (ByteBuffer reply) -> {
|
||||
try {
|
||||
final FileDescriptor fd = getContentResolver()
|
||||
.openAssetFileDescriptor(logFile, "w")
|
||||
.getFileDescriptor();
|
||||
final FileOutputStream outputStream = new FileOutputStream(fd);
|
||||
outputStream.write(reply.array());
|
||||
outputStream.close();
|
||||
} catch (IOException ex) {
|
||||
Log.e(TAG, "Could not write timeline file: " + ex.toString());
|
||||
}
|
||||
finish();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
// Copyright 2018 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package dev.flutter.scenarios;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import io.flutter.plugin.platform.PlatformView;
|
||||
|
||||
public class TextPlatformView implements PlatformView {
|
||||
private final TextView textView;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
TextPlatformView(final Context context, int id, String params) {
|
||||
textView = new TextView(context);
|
||||
textView.setTextSize(72);
|
||||
textView.setBackgroundColor(Color.rgb(255, 255, 255));
|
||||
textView.setText(params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView() {
|
||||
return textView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
// Copyright 2018 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package dev.flutter.scenarios;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import io.flutter.plugin.common.MessageCodec;
|
||||
import io.flutter.plugin.common.StringCodec;
|
||||
import io.flutter.plugin.platform.PlatformView;
|
||||
import io.flutter.plugin.platform.PlatformViewFactory;
|
||||
|
||||
public final class TextPlatformViewFactory extends PlatformViewFactory {
|
||||
TextPlatformViewFactory() {
|
||||
super(new MessageCodec<Object>() {
|
||||
@Nullable
|
||||
@Override
|
||||
public ByteBuffer encodeMessage(@Nullable Object o) {
|
||||
if (o instanceof String) {
|
||||
return StringCodec.INSTANCE.encodeMessage((String)o);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Object decodeMessage(@Nullable ByteBuffer byteBuffer) {
|
||||
return StringCodec.INSTANCE.decodeMessage(byteBuffer);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public PlatformView create(Context context, int id, Object args) {
|
||||
String params = (String) args;
|
||||
return new TextPlatformView(context, id, params);
|
||||
}
|
||||
}
|
||||
@@ -21,9 +21,11 @@
|
||||
248D76CC22E388370012F0C1 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 248D76CB22E388370012F0C1 /* AppDelegate.m */; };
|
||||
248D76D422E388380012F0C1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 248D76D322E388380012F0C1 /* Assets.xcassets */; };
|
||||
248D76DA22E388380012F0C1 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 248D76D922E388380012F0C1 /* main.m */; };
|
||||
248D76E422E388380012F0C1 /* ScenariosTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 248D76E322E388380012F0C1 /* ScenariosTests.m */; };
|
||||
248D76EF22E388380012F0C1 /* ScenariosUITests.m in Sources */ = {isa = PBXBuildFile; fileRef = 248D76EE22E388380012F0C1 /* ScenariosUITests.m */; };
|
||||
248D76EF22E388380012F0C1 /* PlatformViewUITests.m in Sources */ = {isa = PBXBuildFile; fileRef = 248D76EE22E388380012F0C1 /* PlatformViewUITests.m */; };
|
||||
248FDFC422FE7CD0009CC7CD /* FlutterEngineTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 248FDFC322FE7CD0009CC7CD /* FlutterEngineTest.m */; };
|
||||
24D47D1B230C79840069DD5E /* golden_platform_view_D21AAP.png in Resources */ = {isa = PBXBuildFile; fileRef = 24D47D1A230C79840069DD5E /* golden_platform_view_D21AAP.png */; };
|
||||
24D47D1D230CA2700069DD5E /* golden_platform_view_iPhone SE_simulator.png in Resources */ = {isa = PBXBuildFile; fileRef = 24D47D1C230CA2700069DD5E /* golden_platform_view_iPhone SE_simulator.png */; };
|
||||
24F1FB89230B4579005ACE7C /* TextPlatformView.m in Sources */ = {isa = PBXBuildFile; fileRef = 24F1FB87230B4579005ACE7C /* TextPlatformView.m */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@@ -91,12 +93,16 @@
|
||||
248D76D822E388380012F0C1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
248D76D922E388380012F0C1 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
|
||||
248D76DF22E388380012F0C1 /* ScenariosTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ScenariosTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
248D76E322E388380012F0C1 /* ScenariosTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ScenariosTests.m; sourceTree = "<group>"; };
|
||||
248D76E522E388380012F0C1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
248D76EA22E388380012F0C1 /* ScenariosUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ScenariosUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
248D76EE22E388380012F0C1 /* ScenariosUITests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ScenariosUITests.m; sourceTree = "<group>"; };
|
||||
248D76EE22E388380012F0C1 /* PlatformViewUITests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PlatformViewUITests.m; sourceTree = "<group>"; };
|
||||
248D76F022E388380012F0C1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
248FDFC322FE7CD0009CC7CD /* FlutterEngineTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FlutterEngineTest.m; sourceTree = "<group>"; };
|
||||
24D47D1A230C79840069DD5E /* golden_platform_view_D21AAP.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = golden_platform_view_D21AAP.png; sourceTree = "<group>"; };
|
||||
24D47D1C230CA2700069DD5E /* golden_platform_view_iPhone SE_simulator.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "golden_platform_view_iPhone SE_simulator.png"; sourceTree = "<group>"; };
|
||||
24D47D1E230CA4480069DD5E /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
|
||||
24F1FB87230B4579005ACE7C /* TextPlatformView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TextPlatformView.m; sourceTree = "<group>"; };
|
||||
24F1FB88230B4579005ACE7C /* TextPlatformView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TextPlatformView.h; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -152,6 +158,8 @@
|
||||
248D76C922E388370012F0C1 /* Scenarios */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
24F1FB88230B4579005ACE7C /* TextPlatformView.h */,
|
||||
24F1FB87230B4579005ACE7C /* TextPlatformView.m */,
|
||||
248D76CA22E388370012F0C1 /* AppDelegate.h */,
|
||||
248D76CB22E388370012F0C1 /* AppDelegate.m */,
|
||||
248D76D322E388380012F0C1 /* Assets.xcassets */,
|
||||
@@ -166,7 +174,6 @@
|
||||
children = (
|
||||
248FDFC322FE7CD0009CC7CD /* FlutterEngineTest.m */,
|
||||
0DB781FC22EA2C0300E9B371 /* FlutterViewControllerTest.m */,
|
||||
248D76E322E388380012F0C1 /* ScenariosTests.m */,
|
||||
248D76E522E388380012F0C1 /* Info.plist */,
|
||||
);
|
||||
path = ScenariosTests;
|
||||
@@ -175,8 +182,11 @@
|
||||
248D76ED22E388380012F0C1 /* ScenariosUITests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
248D76EE22E388380012F0C1 /* ScenariosUITests.m */,
|
||||
24D47D1C230CA2700069DD5E /* golden_platform_view_iPhone SE_simulator.png */,
|
||||
24D47D1A230C79840069DD5E /* golden_platform_view_D21AAP.png */,
|
||||
248D76EE22E388380012F0C1 /* PlatformViewUITests.m */,
|
||||
248D76F022E388380012F0C1 /* Info.plist */,
|
||||
24D47D1E230CA4480069DD5E /* README.md */,
|
||||
);
|
||||
path = ScenariosUITests;
|
||||
sourceTree = "<group>";
|
||||
@@ -311,6 +321,8 @@
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
24D47D1B230C79840069DD5E /* golden_platform_view_D21AAP.png in Resources */,
|
||||
24D47D1D230CA2700069DD5E /* golden_platform_view_iPhone SE_simulator.png in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -322,6 +334,7 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
248D76DA22E388380012F0C1 /* main.m in Sources */,
|
||||
24F1FB89230B4579005ACE7C /* TextPlatformView.m in Sources */,
|
||||
248D76CC22E388370012F0C1 /* AppDelegate.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -330,7 +343,6 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
248D76E422E388380012F0C1 /* ScenariosTests.m in Sources */,
|
||||
0DB7820222EA493B00E9B371 /* FlutterViewControllerTest.m in Sources */,
|
||||
248FDFC422FE7CD0009CC7CD /* FlutterEngineTest.m in Sources */,
|
||||
);
|
||||
@@ -340,7 +352,7 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
248D76EF22E388380012F0C1 /* ScenariosUITests.m in Sources */,
|
||||
248D76EF22E388380012F0C1 /* PlatformViewUITests.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
||||
@@ -1,12 +1,48 @@
|
||||
#include "AppDelegate.h"
|
||||
#import "TextPlatformView.h"
|
||||
|
||||
@interface NoStatusBarFlutterViewController : FlutterViewController
|
||||
|
||||
@end
|
||||
|
||||
@implementation NoStatusBarFlutterViewController
|
||||
- (BOOL)prefersStatusBarHidden {
|
||||
return YES;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation AppDelegate
|
||||
|
||||
- (BOOL)application:(UIApplication*)application
|
||||
didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
|
||||
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
|
||||
self.window.rootViewController = [[UIViewController alloc] init];
|
||||
|
||||
// This argument is used by the XCUITest for Platform Views so that the app
|
||||
// under test will create platform views.
|
||||
if ([[[NSProcessInfo processInfo] arguments] containsObject:@"--platform-view"]) {
|
||||
FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"PlatformViewTest" project:nil];
|
||||
[engine runWithEntrypoint:nil];
|
||||
|
||||
FlutterViewController* flutterViewController =
|
||||
[[NoStatusBarFlutterViewController alloc] initWithEngine:engine nibName:nil bundle:nil];
|
||||
[engine.binaryMessenger
|
||||
setMessageHandlerOnChannel:@"scenario_status"
|
||||
binaryMessageHandler:^(NSData* _Nullable message, FlutterBinaryReply _Nonnull reply) {
|
||||
[engine.binaryMessenger
|
||||
sendOnChannel:@"set_scenario"
|
||||
message:[@"text_platform_view" dataUsingEncoding:NSUTF8StringEncoding]];
|
||||
}];
|
||||
TextPlatformViewFactory* textPlatformViewFactory =
|
||||
[[TextPlatformViewFactory alloc] initWithMessenger:flutterViewController.binaryMessenger];
|
||||
NSObject<FlutterPluginRegistrar>* registrar =
|
||||
[flutterViewController.engine registrarForPlugin:@"scenarios/TextPlatformViewPlugin"];
|
||||
[registrar registerViewFactory:textPlatformViewFactory withId:@"scenarios/textPlatformView"];
|
||||
self.window.rootViewController = flutterViewController;
|
||||
} else {
|
||||
self.window.rootViewController = [[UIViewController alloc] init];
|
||||
}
|
||||
[self.window makeKeyAndVisible];
|
||||
|
||||
return [super application:application didFinishLaunchingWithOptions:launchOptions];
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,8 @@
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>io.flutter.embedded_views_preview</key>
|
||||
<true/>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright 2013 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#import <Flutter/Flutter.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TextPlatformView : NSObject <FlutterPlatformView>
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame
|
||||
viewIdentifier:(int64_t)viewId
|
||||
arguments:(id _Nullable)args
|
||||
binaryMessenger:(NSObject<FlutterBinaryMessenger>*)messenger;
|
||||
|
||||
- (UIView*)view;
|
||||
@end
|
||||
|
||||
@interface TextPlatformViewFactory : NSObject <FlutterPlatformViewFactory>
|
||||
- (instancetype)initWithMessenger:(NSObject<FlutterBinaryMessenger>*)messenger;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,63 @@
|
||||
// Copyright 2018 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#import "TextPlatformView.h"
|
||||
|
||||
@implementation TextPlatformViewFactory {
|
||||
NSObject<FlutterBinaryMessenger>* _messenger;
|
||||
}
|
||||
|
||||
- (instancetype)initWithMessenger:(NSObject<FlutterBinaryMessenger>*)messenger {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_messenger = messenger;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSObject<FlutterPlatformView>*)createWithFrame:(CGRect)frame
|
||||
viewIdentifier:(int64_t)viewId
|
||||
arguments:(id _Nullable)args {
|
||||
TextPlatformView* textPlatformView = [[TextPlatformView alloc] initWithFrame:frame
|
||||
viewIdentifier:viewId
|
||||
arguments:args
|
||||
binaryMessenger:_messenger];
|
||||
return textPlatformView;
|
||||
}
|
||||
|
||||
- (NSObject<FlutterMessageCodec>*)createArgsCodec {
|
||||
return [FlutterStringCodec sharedInstance];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation TextPlatformView {
|
||||
int64_t _viewId;
|
||||
UITextView* _textView;
|
||||
FlutterMethodChannel* _channel;
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame
|
||||
viewIdentifier:(int64_t)viewId
|
||||
arguments:(id _Nullable)args
|
||||
binaryMessenger:(NSObject<FlutterBinaryMessenger>*)messenger {
|
||||
if ([super init]) {
|
||||
_viewId = viewId;
|
||||
_textView = [[UITextView alloc] initWithFrame:CGRectMake(50.0, 50.0, 250.0, 100.0)];
|
||||
_textView.textColor = UIColor.blueColor;
|
||||
_textView.backgroundColor = UIColor.lightGrayColor;
|
||||
[_textView setFont:[UIFont systemFontOfSize:52]];
|
||||
_textView.text = args;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (UIView*)view {
|
||||
return _textView;
|
||||
}
|
||||
|
||||
// - (void)onMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
|
||||
// }
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,110 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#import <Flutter/flutter.h>
|
||||
#import <XCTest/XCTest.h>
|
||||
#include <sys/sysctl.h>
|
||||
|
||||
#import "../Scenarios/TextPlatformView.h"
|
||||
|
||||
@interface PlatformViewUITests : XCTestCase
|
||||
@property(nonatomic, strong) XCUIApplication* application;
|
||||
@end
|
||||
|
||||
@implementation PlatformViewUITests
|
||||
|
||||
- (void)setUp {
|
||||
[super setUp];
|
||||
self.continueAfterFailure = NO;
|
||||
|
||||
self.application = [[XCUIApplication alloc] init];
|
||||
self.application.launchArguments = @[ @"--platform-view" ];
|
||||
[self.application launch];
|
||||
}
|
||||
|
||||
- (void)testPlatformView {
|
||||
NSBundle* bundle = [NSBundle bundleForClass:[self class]];
|
||||
NSString* goldenName =
|
||||
[NSString stringWithFormat:@"golden_platform_view_%@", [self platformName]];
|
||||
NSString* path = [bundle pathForResource:goldenName ofType:@"png"];
|
||||
UIImage* golden = [[UIImage alloc] initWithContentsOfFile:path];
|
||||
|
||||
XCUIScreenshot* screenshot = [[XCUIScreen mainScreen] screenshot];
|
||||
if (golden) {
|
||||
XCTAttachment* goldenAttachment = [XCTAttachment attachmentWithImage:golden];
|
||||
goldenAttachment.lifetime = XCTAttachmentLifetimeKeepAlways;
|
||||
[self addAttachment:goldenAttachment];
|
||||
}
|
||||
|
||||
XCTAttachment* attachment = [XCTAttachment attachmentWithScreenshot:screenshot];
|
||||
attachment.lifetime = XCTAttachmentLifetimeKeepAlways;
|
||||
[self addAttachment:attachment];
|
||||
XCTAssertTrue([self compareImage:golden toOther:screenshot.image]);
|
||||
}
|
||||
|
||||
- (NSString*)platformName {
|
||||
NSString* simulatorName =
|
||||
[[NSProcessInfo processInfo].environment objectForKey:@"SIMULATOR_DEVICE_NAME"];
|
||||
if (simulatorName) {
|
||||
return [NSString stringWithFormat:@"%@_simulator", simulatorName];
|
||||
}
|
||||
|
||||
size_t size;
|
||||
sysctlbyname("hw.model", NULL, &size, NULL, 0);
|
||||
char* answer = malloc(size);
|
||||
sysctlbyname("hw.model", answer, &size, NULL, 0);
|
||||
|
||||
NSString* results = [NSString stringWithCString:answer encoding:NSUTF8StringEncoding];
|
||||
free(answer);
|
||||
return results;
|
||||
}
|
||||
|
||||
- (BOOL)compareImage:(UIImage*)a toOther:(UIImage*)b {
|
||||
CGImageRef imageRefA = [a CGImage];
|
||||
CGImageRef imageRefB = [b CGImage];
|
||||
|
||||
NSUInteger widthA = CGImageGetWidth(imageRefA);
|
||||
NSUInteger heightA = CGImageGetHeight(imageRefA);
|
||||
NSUInteger widthB = CGImageGetWidth(imageRefB);
|
||||
NSUInteger heightB = CGImageGetHeight(imageRefB);
|
||||
|
||||
if (widthA != widthB || heightA != heightB) {
|
||||
return NO;
|
||||
}
|
||||
NSUInteger bytesPerPixel = 4;
|
||||
NSUInteger size = widthA * heightA * bytesPerPixel;
|
||||
NSMutableData* rawA = [NSMutableData dataWithLength:size];
|
||||
NSMutableData* rawB = [NSMutableData dataWithLength:size];
|
||||
|
||||
if (!rawA || !rawB) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
|
||||
|
||||
NSUInteger bytesPerRow = bytesPerPixel * widthA;
|
||||
NSUInteger bitsPerComponent = 8;
|
||||
CGContextRef contextA =
|
||||
CGBitmapContextCreate(rawA.mutableBytes, widthA, heightA, bitsPerComponent, bytesPerRow,
|
||||
colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
|
||||
|
||||
CGContextDrawImage(contextA, CGRectMake(0, 0, widthA, heightA), imageRefA);
|
||||
CGContextRelease(contextA);
|
||||
|
||||
CGContextRef contextB =
|
||||
CGBitmapContextCreate(rawB.mutableBytes, widthA, heightA, bitsPerComponent, bytesPerRow,
|
||||
colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
|
||||
CGColorSpaceRelease(colorSpace);
|
||||
|
||||
CGContextDrawImage(contextB, CGRectMake(0, 0, widthA, heightA), imageRefB);
|
||||
CGContextRelease(contextB);
|
||||
|
||||
if (memcmp(rawA.mutableBytes, rawB.mutableBytes, size)) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,13 @@
|
||||
# PlatformView UI Tests
|
||||
|
||||
This folder contains a test for platform views. It renders a platform view
|
||||
and does a screen shot comparison against a known good configuration.
|
||||
|
||||
The screen shots are named `golden_platform_view_MODEL`, with `_simulator`
|
||||
appended for simulators. The model numbers for physical devices correspond
|
||||
to the `hw.model` sys call, and will represent the model numbers. Simulator
|
||||
names are taken from the environment.
|
||||
|
||||
New devices require running the test on the device, gathering the attachment
|
||||
and verifying it manually, and then adding an appropriately named file to
|
||||
this folder.
|
||||
@@ -1,42 +0,0 @@
|
||||
//
|
||||
// ScenariosUITests.m
|
||||
// ScenariosUITests
|
||||
//
|
||||
// Created by Dan Field on 7/20/19.
|
||||
// Copyright © 2019 flutter. All rights reserved.
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
@interface ScenariosUITests : XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation ScenariosUITests
|
||||
|
||||
- (void)setUp {
|
||||
// Put setup code here. This method is called before the invocation of each test method in the
|
||||
// class.
|
||||
|
||||
// In UI tests it is usually best to stop immediately when a failure occurs.
|
||||
self.continueAfterFailure = NO;
|
||||
|
||||
// UI tests must launch the application that they test. Doing this in setup will make sure it
|
||||
// happens for each test method.
|
||||
[[[XCUIApplication alloc] init] launch];
|
||||
|
||||
// In UI tests it’s important to set the initial state - such as interface orientation - required
|
||||
// for your tests before they run. The setUp method is a good place to do this.
|
||||
}
|
||||
|
||||
- (void)tearDown {
|
||||
// Put teardown code here. This method is called after the invocation of each test method in the
|
||||
// class.
|
||||
}
|
||||
|
||||
- (void)testExample {
|
||||
// Use recording to get started writing UI tests.
|
||||
// Use XCTAssert and related functions to verify your tests produce the correct results.
|
||||
}
|
||||
|
||||
@end
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 81 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 27 KiB |
@@ -10,10 +10,12 @@ import 'dart:typed_data';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'src/animated_color_square.dart';
|
||||
import 'src/platform_view.dart';
|
||||
import 'src/scenario.dart';
|
||||
|
||||
Map<String, Scenario> _scenarios = <String, Scenario>{
|
||||
'animated_color_square': AnimatedColorSquareScenario(window),
|
||||
'text_platform_view': PlatformViewScenario(window, 'Hello from Scenarios (Platform View)'),
|
||||
};
|
||||
|
||||
Scenario _currentScenario = _scenarios['animated_color_square'];
|
||||
@@ -30,7 +32,10 @@ void main() {
|
||||
window.sendPlatformMessage('scenario_status', data, null);
|
||||
}
|
||||
|
||||
Future<void> _handlePlatformMessage(String name, ByteData data, PlatformMessageResponseCallback callback) async {
|
||||
Future<void> _handlePlatformMessage(
|
||||
String name, ByteData data, PlatformMessageResponseCallback callback) async {
|
||||
print(name);
|
||||
print(utf8.decode(data.buffer.asUint8List()));
|
||||
if (name == 'set_scenario' && data != null) {
|
||||
final String scenarioName = utf8.decode(data.buffer.asUint8List());
|
||||
final Scenario candidateScenario = _scenarios[scenarioName];
|
||||
@@ -59,7 +64,8 @@ Future<String> _getTimelineData() async {
|
||||
final Map<String, dynamic> cpuTimelineJson = await _getJson(cpuProfileTimelineUri);
|
||||
final Map<String, dynamic> vmServiceTimelineJson = await _getJson(vmServiceTimelineUri);
|
||||
final Map<String, dynamic> cpuResult = cpuTimelineJson['result'].cast<String, dynamic>();
|
||||
final Map<String, dynamic> vmServiceResult = vmServiceTimelineJson['result'].cast<String, dynamic>();
|
||||
final Map<String, dynamic> vmServiceResult =
|
||||
vmServiceTimelineJson['result'].cast<String, dynamic>();
|
||||
|
||||
return json.encode(<String, dynamic>{
|
||||
'stackFrames': cpuResult['stackFrames'],
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'scenario.dart';
|
||||
|
||||
List<int> _to32(int value) {
|
||||
final Uint8List temp = Uint8List(4);
|
||||
temp.buffer.asByteData().setInt32(0, value, Endian.little);
|
||||
return temp;
|
||||
}
|
||||
|
||||
List<int> _to64(num value) {
|
||||
final Uint8List temp = Uint8List(15);
|
||||
if (value is double) {
|
||||
temp.buffer.asByteData().setFloat64(7, value, Endian.little);
|
||||
} else if (value is int) {
|
||||
temp.buffer.asByteData().setInt64(7, value, Endian.little);
|
||||
}
|
||||
return temp;
|
||||
}
|
||||
|
||||
/// A simple platform view.
|
||||
class PlatformViewScenario extends Scenario {
|
||||
/// Creates the PlatformView scenario.
|
||||
///
|
||||
/// The [window] parameter must not be null.
|
||||
PlatformViewScenario(Window window, String text, {int id = 0})
|
||||
: assert(window != null),
|
||||
super(window) {
|
||||
const int _valueInt32 = 3;
|
||||
const int _valueFloat64 = 6;
|
||||
const int _valueString = 7;
|
||||
const int _valueUint8List = 8;
|
||||
const int _valueMap = 13;
|
||||
final Uint8List message = Uint8List.fromList(<int>[
|
||||
_valueString,
|
||||
'create'.length, // this is safe as long as these are all single byte characters.
|
||||
...utf8.encode('create'),
|
||||
_valueMap,
|
||||
if (Platform.isIOS)
|
||||
3, // 3 entries in map for iOS.
|
||||
if (Platform.isAndroid)
|
||||
6, // 6 entries in map for Android.
|
||||
_valueString,
|
||||
'id'.length,
|
||||
...utf8.encode('id'),
|
||||
_valueInt32,
|
||||
..._to32(id),
|
||||
_valueString,
|
||||
'viewType'.length,
|
||||
...utf8.encode('viewType'),
|
||||
_valueString,
|
||||
'scenarios/textPlatformView'.length,
|
||||
...utf8.encode('scenarios/textPlatformView'),
|
||||
if (Platform.isAndroid) ...<int>[
|
||||
_valueString,
|
||||
'width'.length,
|
||||
...utf8.encode('width'),
|
||||
_valueFloat64,
|
||||
..._to64(500.0),
|
||||
_valueString,
|
||||
'height'.length,
|
||||
...utf8.encode('height'),
|
||||
_valueFloat64,
|
||||
..._to64(500.0),
|
||||
_valueString,
|
||||
'direction'.length,
|
||||
...utf8.encode('direction'),
|
||||
_valueInt32,
|
||||
..._to32(0), // LTR
|
||||
],
|
||||
_valueString,
|
||||
'params'.length,
|
||||
...utf8.encode('params'),
|
||||
_valueUint8List,
|
||||
text.length,
|
||||
...utf8.encode(text),
|
||||
]);
|
||||
|
||||
window.sendPlatformMessage(
|
||||
'flutter/platform_views',
|
||||
message.buffer.asByteData(),
|
||||
(ByteData response) {
|
||||
if (Platform.isAndroid) {
|
||||
_textureId = response.getInt64(2);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
int _textureId;
|
||||
|
||||
@override
|
||||
void onBeginFrame(Duration duration) {
|
||||
final SceneBuilder builder = SceneBuilder();
|
||||
|
||||
builder.pushOffset(0, 0);
|
||||
|
||||
if (Platform.isIOS) {
|
||||
builder.addPlatformView(0, width: 500, height: 500);
|
||||
} else if (Platform.isAndroid && _textureId != null) {
|
||||
builder.addTexture(_textureId, offset: const Offset(150, 300), width: 500, height: 500);
|
||||
} else {
|
||||
throw UnsupportedError('Platform ${Platform.operatingSystem} is not supported');
|
||||
}
|
||||
|
||||
final PictureRecorder recorder = PictureRecorder();
|
||||
final Canvas canvas = Canvas(recorder);
|
||||
canvas.drawCircle(const Offset(50, 50), 50, Paint()..color = const Color(0xFFABCDEF));
|
||||
final Picture picture = recorder.endRecording();
|
||||
builder.addPicture(const Offset(300, 300), picture);
|
||||
|
||||
final Scene scene = builder.build();
|
||||
window.render(scene);
|
||||
scene.dispose();
|
||||
}
|
||||
}
|
||||
@@ -21,10 +21,10 @@ abstract class Scenario {
|
||||
/// flushed.
|
||||
///
|
||||
/// See [Window.onDrawFrame] for more details.
|
||||
void onDrawFrame();
|
||||
void onDrawFrame() {}
|
||||
|
||||
/// Called by the program when the window metrics have changed.
|
||||
///
|
||||
/// See [Window.onMetricsChanged].
|
||||
void onMetricsChanged();
|
||||
void onMetricsChanged() {}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user