[platform_view]Send platform message when platform view is focused (#105050)

This commit is contained in:
hellohuanlin 2022-06-22 17:01:07 -07:00 committed by GitHub
parent 94e318456e
commit 0dd0c2edca
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 522 additions and 57 deletions

View file

@ -3657,6 +3657,17 @@ targets:
- bin/**
- .ci.yaml
- name: Mac native_platform_view_ui_tests_ios
bringup: true
recipe: devicelab/devicelab_drone
presubmit: false
timeout: 60
properties:
tags: >
["devicelab", "hostonly"]
task_name: native_platform_view_ui_tests_ios
scheduler: luci
- name: Mac run_release_test_macos
recipe: devicelab/devicelab_drone
timeout: 60

View file

@ -195,7 +195,7 @@
/dev/devicelab/bin/tasks/module_custom_host_app_name_test.dart @zanderso @flutter/tool
/dev/devicelab/bin/tasks/module_host_with_custom_build_test.dart @zanderso @flutter/tool
/dev/devicelab/bin/tasks/module_test.dart @zanderso @flutter/tool
/dev/devicelab/bin/tasks/native_ui_tests_ios.dart @jmagman @flutter/engine
/dev/devicelab/bin/tasks/native_platform_view_ui_tests_ios.dart @hellohuanlin @flutter/ios
/dev/devicelab/bin/tasks/native_ui_tests_macos.dart @cbracken @flutter/desktop
/dev/devicelab/bin/tasks/plugin_test.dart @stuartmorgan @flutter/plugin
/dev/devicelab/bin/tasks/plugin_test_ios.dart @jmagman @flutter/ios

View file

@ -0,0 +1,47 @@
// Copyright 2014 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 'package:flutter_devicelab/framework/devices.dart';
import 'package:flutter_devicelab/framework/framework.dart';
import 'package:flutter_devicelab/framework/ios.dart';
import 'package:flutter_devicelab/framework/task_result.dart';
import 'package:flutter_devicelab/framework/utils.dart';
import 'package:path/path.dart' as path;
Future<void> main() async {
await task(() async {
final String projectDirectory = '${flutterDirectory.path}/dev/integration_tests/ios_platform_view_tests';
await inDirectory(projectDirectory, () async {
section('Build clean');
await flutter('clean');
section('Build platform view app');
await flutter(
'build',
options: <String>[
'ios',
'-v',
'--release',
'--config-only',
],
);
});
section('Run platform view XCUITests');
final Device device = await devices.workingDevice;
if (!await runXcodeTests(
platformDirectory: path.join(projectDirectory, 'ios'),
destination: 'id=${device.deviceId}',
testName: 'native_platform_view_ui_tests_ios',
)) {
return TaskResult.failure('Platform view XCUITests failed');
}
return TaskResult.success(null);
});
}

View file

@ -83,6 +83,9 @@ TaskFunction createIOSPlatformViewTests() {
return DriverTest(
'${flutterDirectory.path}/dev/integration_tests/ios_platform_view_tests',
'lib/main.dart',
extraOptions: <String>[
'--dart-define=ENABLE_DRIVER_EXTENSION=true',
],
);
}

View file

@ -0,0 +1,51 @@
// Copyright 2014 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 XCTest;
@interface XCUIElement(KeyboardFocus)
@property (nonatomic, readonly) BOOL flt_hasKeyboardFocus;
@end
@implementation XCUIElement(KeyboardFocus)
- (BOOL)flt_hasKeyboardFocus {
return [[self valueForKey:@"hasKeyboardFocus"] boolValue];
}
@end
@interface PlatformViewUITests : XCTestCase
@property (strong) XCUIApplication *app;
@end
@implementation PlatformViewUITests
- (void)setUp {
self.continueAfterFailure = NO;
self.app = [[XCUIApplication alloc] init];
[self.app launch];
}
- (void)testPlatformViewFocus {
XCUIElement *entranceButton = self.app.buttons[@"platform view focus test"];
XCTAssertTrue([entranceButton waitForExistenceWithTimeout:1]);
[entranceButton tap];
XCUIElement *platformView = self.app.textFields[@"platform_view[0]"];
XCTAssertTrue([platformView waitForExistenceWithTimeout:1]);
XCUIElement *flutterTextField = self.app.textFields[@"Flutter Text Field"];
XCTAssertTrue([flutterTextField waitForExistenceWithTimeout:1]);
[flutterTextField tap];
XCTAssertTrue([self.app.windows.element waitForExistenceWithTimeout:1]);
XCTAssertFalse(platformView.flt_hasKeyboardFocus);
XCTAssertTrue(flutterTextField.flt_hasKeyboardFocus);
// Tapping on platformView should unfocus the previously focused flutterTextField
[platformView tap];
XCTAssertTrue(platformView.flt_hasKeyboardFocus);
XCTAssertFalse(flutterTextField.flt_hasKeyboardFocus);
}
@end

View file

@ -14,8 +14,21 @@
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
E09898DE2853DBE800064317 /* ViewFactory.m in Sources */ = {isa = PBXBuildFile; fileRef = E09898DD2853DBE800064317 /* ViewFactory.m */; };
E09898E12853DC3C00064317 /* TextFieldFactory.m in Sources */ = {isa = PBXBuildFile; fileRef = E09898E02853DC3C00064317 /* TextFieldFactory.m */; };
E09898E92853E9F000064317 /* PlatformViewUITests.m in Sources */ = {isa = PBXBuildFile; fileRef = E09898E82853E9F000064317 /* PlatformViewUITests.m */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
E09898EC2853E9F000064317 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
proxyType = 1;
remoteGlobalIDString = 97C146ED1CF9000F007C117D;
remoteInfo = Runner;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
@ -44,6 +57,12 @@
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
E09898DC2853DBE800064317 /* ViewFactory.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewFactory.h; sourceTree = "<group>"; };
E09898DD2853DBE800064317 /* ViewFactory.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewFactory.m; sourceTree = "<group>"; };
E09898DF2853DC3C00064317 /* TextFieldFactory.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TextFieldFactory.h; sourceTree = "<group>"; };
E09898E02853DC3C00064317 /* TextFieldFactory.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TextFieldFactory.m; sourceTree = "<group>"; };
E09898E62853E9F000064317 /* PlatformViewUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PlatformViewUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
E09898E82853E9F000064317 /* PlatformViewUITests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PlatformViewUITests.m; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -54,6 +73,13 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
E09898E32853E9F000064317 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
@ -73,6 +99,7 @@
children = (
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
E09898E72853E9F000064317 /* PlatformViewUITests */,
97C146EF1CF9000F007C117D /* Products */,
);
sourceTree = "<group>";
@ -81,6 +108,7 @@
isa = PBXGroup;
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
E09898E62853E9F000064317 /* PlatformViewUITests.xctest */,
);
name = Products;
sourceTree = "<group>";
@ -90,6 +118,10 @@
children = (
7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */,
7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */,
E09898DF2853DC3C00064317 /* TextFieldFactory.h */,
E09898E02853DC3C00064317 /* TextFieldFactory.m */,
E09898DC2853DBE800064317 /* ViewFactory.h */,
E09898DD2853DBE800064317 /* ViewFactory.m */,
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
@ -109,6 +141,14 @@
name = "Supporting Files";
sourceTree = "<group>";
};
E09898E72853E9F000064317 /* PlatformViewUITests */ = {
isa = PBXGroup;
children = (
E09898E82853E9F000064317 /* PlatformViewUITests.m */,
);
path = PlatformViewUITests;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@ -132,6 +172,24 @@
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
productType = "com.apple.product-type.application";
};
E09898E52853E9F000064317 /* PlatformViewUITests */ = {
isa = PBXNativeTarget;
buildConfigurationList = E09898EE2853E9F000064317 /* Build configuration list for PBXNativeTarget "PlatformViewUITests" */;
buildPhases = (
E09898E22853E9F000064317 /* Sources */,
E09898E32853E9F000064317 /* Frameworks */,
E09898E42853E9F000064317 /* Resources */,
);
buildRules = (
);
dependencies = (
E09898ED2853E9F000064317 /* PBXTargetDependency */,
);
name = PlatformViewUITests;
productName = PlatformViewUITests;
productReference = E09898E62853E9F000064317 /* PlatformViewUITests.xctest */;
productType = "com.apple.product-type.bundle.ui-testing";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
@ -144,6 +202,10 @@
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
};
E09898E52853E9F000064317 = {
CreatedOnToolsVersion = 13.3.1;
TestTargetID = 97C146ED1CF9000F007C117D;
};
};
};
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
@ -160,6 +222,7 @@
projectRoot = "";
targets = (
97C146ED1CF9000F007C117D /* Runner */,
E09898E52853E9F000064317 /* PlatformViewUITests */,
);
};
/* End PBXProject section */
@ -176,6 +239,13 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
E09898E42853E9F000064317 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
@ -216,12 +286,30 @@
files = (
978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */,
97C146F31CF9000F007C117D /* main.m in Sources */,
E09898E12853DC3C00064317 /* TextFieldFactory.m in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
E09898DE2853DBE800064317 /* ViewFactory.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
E09898E22853E9F000064317 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
E09898E92853E9F000064317 /* PlatformViewUITests.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
E09898ED2853E9F000064317 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 97C146ED1CF9000F007C117D /* Runner */;
targetProxy = E09898EC2853E9F000064317 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
isa = PBXVariantGroup;
@ -454,6 +542,79 @@
};
name = Release;
};
E09898EF2853E9F000064317 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GCC_C_LANGUAGE_STANDARD = gnu11;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MARKETING_VERSION = 1.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.flutter.platformview.PlatformViewUITests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = NO;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_TARGET_NAME = Runner;
};
name = Debug;
};
E09898F02853E9F000064317 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GCC_C_LANGUAGE_STANDARD = gnu11;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MARKETING_VERSION = 1.0;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.flutter.platformview.PlatformViewUITests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = NO;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_TARGET_NAME = Runner;
};
name = Release;
};
E09898F12853E9F000064317 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GCC_C_LANGUAGE_STANDARD = gnu11;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MARKETING_VERSION = 1.0;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.flutter.platformview.PlatformViewUITests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = NO;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_TARGET_NAME = Runner;
};
name = Profile;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
@ -477,6 +638,16 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
E09898EE2853E9F000064317 /* Build configuration list for PBXNativeTarget "PlatformViewUITests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
E09898EF2853E9F000064317 /* Debug */,
E09898F02853E9F000064317 /* Release */,
E09898F12853E9F000064317 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;

View file

@ -27,8 +27,6 @@
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
@ -38,8 +36,18 @@
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "E09898E52853E9F000064317"
BuildableName = "PlatformViewUITests.xctest"
BlueprintName = "PlatformViewUITests"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
@ -61,8 +69,6 @@
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"

View file

@ -4,43 +4,8 @@
#import "AppDelegate.h"
#import "GeneratedPluginRegistrant.h"
@interface PlatformView: NSObject<FlutterPlatformView>
@property (strong, nonatomic) UIView *platformView;
@end
@implementation PlatformView
- (instancetype)init
{
self = [super init];
if (self) {
self.platformView = [[UIView alloc] init];
self.platformView.backgroundColor = [UIColor blueColor];
}
return self;
}
- (UIView *)view {
return self.platformView;
}
@end
@interface ViewFactory: NSObject<FlutterPlatformViewFactory>
@end
@implementation ViewFactory
- (NSObject<FlutterPlatformView> *)createWithFrame:(CGRect)frame viewIdentifier:(int64_t)viewId arguments:(id)args {
PlatformView *platformView = [[PlatformView alloc] init];
return platformView;
}
@end
#import "ViewFactory.h"
#import "TextFieldFactory.h"
@implementation AppDelegate
@ -48,7 +13,9 @@
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[GeneratedPluginRegistrant registerWithRegistry:self];
// Override point for customization after application launch.
[[self registrarForPlugin:@"flutter"] registerViewFactory:[ViewFactory new] withId:@"platform_view"];
id<FlutterPluginRegistrar> registrar = [self registrarForPlugin:@"flutter"];
[registrar registerViewFactory:[[ViewFactory alloc] init] withId:@"platform_view"];
[registrar registerViewFactory:[[TextFieldFactory alloc] init] withId:@"platform_text_field"];
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}

View file

@ -0,0 +1,14 @@
// Copyright 2014 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 <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface TextFieldFactory : NSObject<FlutterPlatformViewFactory>
@end
NS_ASSUME_NONNULL_END

View file

@ -0,0 +1,37 @@
// Copyright 2014 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 "TextFieldFactory.h"
@interface PlatformTextField: NSObject<FlutterPlatformView>
@property (strong, nonatomic) UITextField *textField;
@end
@implementation PlatformTextField
- (instancetype)init
{
self = [super init];
if (self) {
_textField = [[UITextField alloc] init];
_textField.text = @"Platform Text Field";
}
return self;
}
- (UIView *)view {
return self.textField;
}
@end
@implementation TextFieldFactory
- (NSObject<FlutterPlatformView> *)createWithFrame:(CGRect)frame viewIdentifier:(int64_t)viewId arguments:(id)args {
return [[PlatformTextField alloc] init];
}
@end

View file

@ -0,0 +1,14 @@
// Copyright 2014 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 <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface ViewFactory: NSObject<FlutterPlatformViewFactory>
@end
NS_ASSUME_NONNULL_END

View file

@ -0,0 +1,38 @@
// Copyright 2014 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 "ViewFactory.h"
@interface PlatformView: NSObject<FlutterPlatformView>
@property (strong, nonatomic) UIView *platformView;
@end
@implementation PlatformView
- (instancetype)init
{
self = [super init];
if (self) {
_platformView = [[UIView alloc] init];
_platformView.backgroundColor = [UIColor blueColor];
}
return self;
}
- (UIView *)view {
return self.platformView;
}
@end
@implementation ViewFactory
- (NSObject<FlutterPlatformView> *)createWithFrame:(CGRect)frame viewIdentifier:(int64_t)viewId arguments:(id)args {
return [[PlatformView alloc] init];
}
@end

View file

@ -6,7 +6,12 @@ import 'package:flutter/material.dart';
import 'package:flutter_driver/driver_extension.dart';
void main() {
enableFlutterDriverExtension();
// enableFlutterDriverExtension() will disable keyboard,
// which is required for flutter_driver tests
// But breaks the XCUITests
if (const bool.fromEnvironment('ENABLE_DRIVER_EXTENSION')) {
enableFlutterDriverExtension();
}
runApp(const MyApp());
}
@ -26,7 +31,7 @@ class MyApp extends StatelessWidget {
}
}
/// A page with a button in the center.
/// A page with several buttons in the center.
///
/// On press the button, a page with platform view should be pushed into the scene.
class MyHomePage extends StatefulWidget {
@ -51,8 +56,8 @@ class _MyHomePageState extends State<MyHomePage> {
onPressed: () {
Navigator.push(
context,
MaterialPageRoute<PlatformViewPage>(
builder: (BuildContext context) => const PlatformViewPage()),
MaterialPageRoute<MergeThreadTestPage>(
builder: (BuildContext context) => const MergeThreadTestPage()),
);
},
),
@ -62,14 +67,25 @@ class _MyHomePageState extends State<MyHomePage> {
child: const Text('Tap to unmerge threads'),
onPressed: () {},
),
TextButton(
key: const ValueKey<String>('platform_view_focus_test'),
child: const Text('platform view focus test'),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute<FocusTestPage>(
builder: (BuildContext context) => const FocusTestPage()),
);
},
),
]),
);
}
}
/// A page contains the platform view to be tested.
class PlatformViewPage extends StatelessWidget {
const PlatformViewPage({super.key});
/// A page to test thread merge for platform view.
class MergeThreadTestPage extends StatelessWidget {
const MergeThreadTestPage({super.key});
static Key button = const ValueKey<String>('plus_button');
@ -77,7 +93,7 @@ class PlatformViewPage extends StatelessWidget {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Platform View'),
title: const Text('Platform View Thread Merge Tests'),
),
body: Column(
children: <Widget>[
@ -97,3 +113,44 @@ class PlatformViewPage extends StatelessWidget {
);
}
}
/// A page to test platform view focus.
class FocusTestPage extends StatefulWidget {
const FocusTestPage({super.key});
@override
State<FocusTestPage> createState() => _FocusTestPageState();
}
class _FocusTestPageState extends State<FocusTestPage> {
late TextEditingController _controller;
@override
void initState() {
super.initState();
_controller = TextEditingController();
_controller.text = 'Flutter Text Field';
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Platform View Focus Tests'),
),
body: Column(
children: <Widget>[
const SizedBox(
width: 300,
height: 50,
child: UiKitView(viewType: 'platform_text_field'),
),
TextField(
controller: _controller,
),
],
),
);
}
}

View file

@ -569,12 +569,13 @@ class _UiKitViewState extends State<UiKitView> {
@override
Widget build(BuildContext context) {
if (_controller == null) {
final UiKitViewController? controller = _controller;
if (controller == null) {
return const SizedBox.expand();
}
return Focus(
focusNode: _focusNode,
onFocusChange: _onFocusChange,
onFocusChange: (bool isFocused) => _onFocusChange(isFocused, controller),
child: _UiKitPlatformView(
controller: _controller!,
hitTestBehavior: widget.hitTestBehavior,
@ -659,8 +660,17 @@ class _UiKitViewState extends State<UiKitView> {
});
}
void _onFocusChange(bool isFocused) {
// TODO(hellohuanlin): send 'TextInput.setPlatformViewClient' channel message to engine after the engine is updated to handle this message.
void _onFocusChange(bool isFocused, UiKitViewController controller) {
if (!isFocused) {
// Unlike Android, we do not need to send "clearFocus" channel message
// to the engine, because focusing on another view will automatically
// cancel the focus on the previously focused platform view.
return;
}
SystemChannels.textInput.invokeMethod<void>(
'TextInput.setPlatformViewClient',
<String, dynamic>{'platformViewId': controller.id},
);
}
}

View file

@ -2064,6 +2064,45 @@ void main() {
expect(uiKitViewFocusNode.hasFocus, isTrue);
});
testWidgets('UiKitView sends TextInput.setPlatformViewClient when focused', (WidgetTester tester) async {
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
final FakeIosPlatformViewsController viewsController = FakeIosPlatformViewsController();
viewsController.registerViewType('webview');
await tester.pumpWidget(
const UiKitView(viewType: 'webview', layoutDirection: TextDirection.ltr)
);
// First frame is before the platform view was created so the render object
// is not yet in the tree.
await tester.pump();
final Focus uiKitViewFocusWidget = tester.widget(
find.descendant(
of: find.byType(UiKitView),
matching: find.byType(Focus),
),
);
final FocusNode uiKitViewFocusNode = uiKitViewFocusWidget.focusNode!;
late Map<String, dynamic> channelArguments;
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.textInput, (MethodCall call) {
if (call.method == 'TextInput.setPlatformViewClient') {
channelArguments = call.arguments as Map<String, dynamic>;
}
return null;
});
expect(uiKitViewFocusNode.hasFocus, false);
uiKitViewFocusNode.requestFocus();
await tester.pump();
expect(uiKitViewFocusNode.hasFocus, true);
expect(channelArguments['platformViewId'], currentViewId + 1);
});
testWidgets('UiKitView has correct semantics', (WidgetTester tester) async {
final SemanticsHandle handle = tester.ensureSemantics();
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();