diff --git a/dev/integration_tests/android_views/ios/Flutter/AppFrameworkInfo.plist b/dev/integration_tests/android_views/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000000..6b4c0f78a7 --- /dev/null +++ b/dev/integration_tests/android_views/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 8.0 + + diff --git a/dev/integration_tests/android_views/ios/Flutter/Debug.xcconfig b/dev/integration_tests/android_views/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000000..e8efba1146 --- /dev/null +++ b/dev/integration_tests/android_views/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/dev/integration_tests/android_views/ios/Flutter/Release.xcconfig b/dev/integration_tests/android_views/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000000..399e9340e6 --- /dev/null +++ b/dev/integration_tests/android_views/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/dev/integration_tests/android_views/ios/Runner.xcodeproj/project.pbxproj b/dev/integration_tests/android_views/ios/Runner.xcodeproj/project.pbxproj index c4840cd405..b5f300b5f6 100644 --- a/dev/integration_tests/android_views/ios/Runner.xcodeproj/project.pbxproj +++ b/dev/integration_tests/android_views/ios/Runner.xcodeproj/project.pbxproj @@ -14,12 +14,12 @@ 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; }; - 9740EEB51CF90195004384FC /* Generated.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB31CF90195004384FC /* Generated.xcconfig */; }; 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; 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 */; }; + E82AC4A900C312A21923945D /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F587E366BE7AF2672B05525E /* libPods-Runner.a */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -45,6 +45,7 @@ 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 7C09407F283942748BDF3120 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; }; @@ -54,6 +55,9 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + B11AF3E99D4F5D486C420DF0 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + B2650B490C790D76CEA2113C /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + F587E366BE7AF2672B05525E /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -63,12 +67,21 @@ files = ( 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */, 3B80C3941E831B6300D905FE /* App.framework in Frameworks */, + E82AC4A900C312A21923945D /* libPods-Runner.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 2F33CA3A53A8A6BEE1876983 /* Frameworks */ = { + isa = PBXGroup; + children = ( + F587E366BE7AF2672B05525E /* libPods-Runner.a */, + ); + name = Frameworks; + sourceTree = ""; + }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( @@ -88,6 +101,8 @@ 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, + A6C97EFD3D2B49B718A36762 /* Pods */, + 2F33CA3A53A8A6BEE1876983 /* Frameworks */, ); sourceTree = ""; }; @@ -123,6 +138,17 @@ name = "Supporting Files"; sourceTree = ""; }; + A6C97EFD3D2B49B718A36762 /* Pods */ = { + isa = PBXGroup; + children = ( + B2650B490C790D76CEA2113C /* Pods-Runner.debug.xcconfig */, + B11AF3E99D4F5D486C420DF0 /* Pods-Runner.release.xcconfig */, + 7C09407F283942748BDF3120 /* Pods-Runner.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -130,12 +156,14 @@ isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( + 65B7702AA4EEA53E0B44B116 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 9DDC76A7660D91401ED287A5 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -152,7 +180,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0910; + LastUpgradeCheck = 1020; ORGANIZATIONNAME = "The Chromium Authors"; TargetAttributes = { 97C146ED1CF9000F007C117D = { @@ -184,7 +212,6 @@ buildActionMask = 2147483647; files = ( 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, - 9740EEB51CF90195004384FC /* Generated.xcconfig in Resources */, 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, @@ -209,6 +236,28 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin"; }; + 65B7702AA4EEA53E0B44B116 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -223,6 +272,25 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; + 9DDC76A7660D91401ED287A5 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -258,7 +326,7 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ - 24D9CED621828E3300CB8465 /* Profile */ = { + 249021D3217E4FDB00AE95B9 /* Profile */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { @@ -272,12 +340,14 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; @@ -306,7 +376,7 @@ }; name = Profile; }; - 24D9CED721828E3300CB8465 /* Profile */ = { + 249021D4217E4FDB00AE95B9 /* Profile */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { @@ -343,12 +413,14 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; @@ -397,12 +469,14 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; @@ -485,7 +559,7 @@ buildConfigurations = ( 97C147031CF9000F007C117D /* Debug */, 97C147041CF9000F007C117D /* Release */, - 24D9CED621828E3300CB8465 /* Profile */, + 249021D3217E4FDB00AE95B9 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; @@ -495,7 +569,7 @@ buildConfigurations = ( 97C147061CF9000F007C117D /* Debug */, 97C147071CF9000F007C117D /* Release */, - 24D9CED721828E3300CB8465 /* Profile */, + 249021D4217E4FDB00AE95B9 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; diff --git a/dev/integration_tests/android_views/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/dev/integration_tests/android_views/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d981003d..0000000000 --- a/dev/integration_tests/android_views/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/dev/integration_tests/android_views/ios/Runner.xcodeproj/xcshareddata/IDEWorkspaceChecks.plist b/dev/integration_tests/android_views/ios/Runner.xcodeproj/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d981003d..0000000000 --- a/dev/integration_tests/android_views/ios/Runner.xcodeproj/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/dev/integration_tests/android_views/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/dev/integration_tests/android_views/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 1263ac84b1..a28140cfdb 100644 --- a/dev/integration_tests/android_views/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/dev/integration_tests/android_views/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ @@ -46,7 +45,6 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - language = "" launchStyle = "0" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" @@ -67,7 +65,7 @@ + + + + + + diff --git a/dev/integration_tests/android_views/ios/Runner/AppDelegate.h b/dev/integration_tests/android_views/ios/Runner/AppDelegate.h new file mode 100644 index 0000000000..d129e6e65e --- /dev/null +++ b/dev/integration_tests/android_views/ios/Runner/AppDelegate.h @@ -0,0 +1,10 @@ +// 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 +#import + +@interface AppDelegate : FlutterAppDelegate + +@end diff --git a/dev/integration_tests/android_views/ios/Runner/AppDelegate.m b/dev/integration_tests/android_views/ios/Runner/AppDelegate.m new file mode 100644 index 0000000000..e5b5ebef57 --- /dev/null +++ b/dev/integration_tests/android_views/ios/Runner/AppDelegate.m @@ -0,0 +1,17 @@ +// 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. + +#include "AppDelegate.h" +#include "GeneratedPluginRegistrant.h" + +@implementation AppDelegate + +- (BOOL)application:(UIApplication *)application + didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + [GeneratedPluginRegistrant registerWithRegistry:self]; + // Override point for customization after application launch. + return [super application:application didFinishLaunchingWithOptions:launchOptions]; +} + +@end diff --git a/dev/integration_tests/android_views/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/dev/integration_tests/android_views/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000000..d36b1fab2d --- /dev/null +++ b/dev/integration_tests/android_views/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/dev/integration_tests/android_views/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/dev/integration_tests/android_views/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000000..dc9ada4725 Binary files /dev/null and b/dev/integration_tests/android_views/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/dev/integration_tests/android_views/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/dev/integration_tests/android_views/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 0000000000..28c6bf0301 Binary files /dev/null and b/dev/integration_tests/android_views/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/dev/integration_tests/android_views/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/dev/integration_tests/android_views/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000000..2ccbfd967d Binary files /dev/null and b/dev/integration_tests/android_views/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/dev/integration_tests/android_views/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/dev/integration_tests/android_views/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000000..f091b6b0bc Binary files /dev/null and b/dev/integration_tests/android_views/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/dev/integration_tests/android_views/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/dev/integration_tests/android_views/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000000..4cde12118d Binary files /dev/null and b/dev/integration_tests/android_views/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/dev/integration_tests/android_views/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/dev/integration_tests/android_views/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000000..d0ef06e7ed Binary files /dev/null and b/dev/integration_tests/android_views/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/dev/integration_tests/android_views/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/dev/integration_tests/android_views/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 0000000000..dcdc2306c2 Binary files /dev/null and b/dev/integration_tests/android_views/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/dev/integration_tests/android_views/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/dev/integration_tests/android_views/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 0000000000..2ccbfd967d Binary files /dev/null and b/dev/integration_tests/android_views/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/dev/integration_tests/android_views/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/dev/integration_tests/android_views/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000000..c8f9ed8f5c Binary files /dev/null and b/dev/integration_tests/android_views/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/dev/integration_tests/android_views/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/dev/integration_tests/android_views/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000000..a6d6b8609d Binary files /dev/null and b/dev/integration_tests/android_views/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/dev/integration_tests/android_views/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/dev/integration_tests/android_views/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000000..a6d6b8609d Binary files /dev/null and b/dev/integration_tests/android_views/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/dev/integration_tests/android_views/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/dev/integration_tests/android_views/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000000..75b2d164a5 Binary files /dev/null and b/dev/integration_tests/android_views/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/dev/integration_tests/android_views/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/dev/integration_tests/android_views/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000000..c4df70d39d Binary files /dev/null and b/dev/integration_tests/android_views/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/dev/integration_tests/android_views/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/dev/integration_tests/android_views/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 0000000000..6a84f41e14 Binary files /dev/null and b/dev/integration_tests/android_views/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/dev/integration_tests/android_views/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/dev/integration_tests/android_views/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000000..d0e1f58536 Binary files /dev/null and b/dev/integration_tests/android_views/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/dev/integration_tests/android_views/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/dev/integration_tests/android_views/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000000..0bedcf2fd4 --- /dev/null +++ b/dev/integration_tests/android_views/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/dev/integration_tests/android_views/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/dev/integration_tests/android_views/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000000..9da19eacad Binary files /dev/null and b/dev/integration_tests/android_views/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/dev/integration_tests/android_views/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/dev/integration_tests/android_views/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000000..9da19eacad Binary files /dev/null and b/dev/integration_tests/android_views/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/dev/integration_tests/android_views/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/dev/integration_tests/android_views/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000000..9da19eacad Binary files /dev/null and b/dev/integration_tests/android_views/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/dev/integration_tests/android_views/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/dev/integration_tests/android_views/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000000..89c2725b70 --- /dev/null +++ b/dev/integration_tests/android_views/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/dev/integration_tests/android_views/ios/Runner/Base.lproj/LaunchScreen.storyboard b/dev/integration_tests/android_views/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000000..f2e259c7c9 --- /dev/null +++ b/dev/integration_tests/android_views/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/integration_tests/android_views/ios/Runner/Base.lproj/Main.storyboard b/dev/integration_tests/android_views/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000000..f3c28516fb --- /dev/null +++ b/dev/integration_tests/android_views/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/integration_tests/android_views/ios/Runner/Info.plist b/dev/integration_tests/android_views/ios/Runner/Info.plist new file mode 100644 index 0000000000..d9d4bdc812 --- /dev/null +++ b/dev/integration_tests/android_views/ios/Runner/Info.plist @@ -0,0 +1,45 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + android_views + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + + diff --git a/dev/integration_tests/android_views/ios/Runner/main.m b/dev/integration_tests/android_views/ios/Runner/main.m new file mode 100644 index 0000000000..dff6597e45 --- /dev/null +++ b/dev/integration_tests/android_views/ios/Runner/main.m @@ -0,0 +1,9 @@ +#import +#import +#import "AppDelegate.h" + +int main(int argc, char* argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} diff --git a/dev/integration_tests/android_views/lib/main.dart b/dev/integration_tests/android_views/lib/main.dart index 74b1ea592d..9697a6a549 100644 --- a/dev/integration_tests/android_views/lib/main.dart +++ b/dev/integration_tests/android_views/lib/main.dart @@ -1,298 +1,36 @@ -// 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 'dart:async'; -import 'dart:io'; -import 'dart:typed_data'; - import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_driver/driver_extension.dart'; -import 'package:path_provider/path_provider.dart'; +import 'motion_events_page.dart'; +import 'page.dart'; -import 'motion_event_diff.dart'; - -MethodChannel channel = const MethodChannel('android_views_integration'); - -const String kEventsFileName = 'touchEvents'; - -/// Wraps a flutter driver [DataHandler] with one that waits until a delegate is set. -/// -/// This allows the driver test to call [FlutterDriver.requestData] before the handler was -/// set by the app in which case the requestData call will only complete once the app is ready -/// for it. -class FutureDataHandler { - final Completer handlerCompleter = Completer(); - - Future handleMessage(String message) async { - final DataHandler handler = await handlerCompleter.future; - return handler(message); - } -} - -FutureDataHandler driverDataHandler = FutureDataHandler(); +final List _allPages = [ + const MotionEventsPage(), +]; void main() { enableFlutterDriverExtension(handler: driverDataHandler.handleMessage); - runApp(MyApp()); + runApp(MaterialApp(home: Home())); } -class MyApp extends StatelessWidget { +class Home extends StatelessWidget { @override Widget build(BuildContext context) { - return MaterialApp( - title: 'Android Views Integration Test', - home: Scaffold( - body: PlatformViewPage(), - ), - ); - } -} - -class PlatformViewPage extends StatefulWidget { - @override - State createState() => PlatformViewState(); -} - -class PlatformViewState extends State { - static const int kEventsBufferSize = 1000; - - MethodChannel viewChannel; - - /// The list of motion events that were passed to the FlutterView. - List> flutterViewEvents = >[]; - - /// The list of motion events that were passed to the embedded view. - List> embeddedViewEvents = >[]; - - @override - Widget build(BuildContext context) { - return Column( - children: [ - SizedBox( - height: 300.0, - child: AndroidView( - viewType: 'simple_view', - onPlatformViewCreated: onPlatformViewCreated), + return Scaffold( + body: ListView.builder( + itemCount: _allPages.length, + itemBuilder: (_, int index) => ListTile( + title: Text(_allPages[index].title), + key: _allPages[index].tileKey, + onTap: () => _pushPage(context, _allPages[index]), ), - Expanded( - child: ListView.builder( - itemBuilder: buildEventTile, - itemCount: flutterViewEvents.length, - ), - ), - Row( - children: [ - RaisedButton( - child: const Text('RECORD'), - onPressed: listenToFlutterViewEvents, - ), - RaisedButton( - child: const Text('CLEAR'), - onPressed: () { - setState(() { - flutterViewEvents.clear(); - embeddedViewEvents.clear(); - }); - }, - ), - RaisedButton( - child: const Text('SAVE'), - onPressed: () { - const StandardMessageCodec codec = StandardMessageCodec(); - saveRecordedEvents( - codec.encodeMessage(flutterViewEvents), context); - }, - ), - RaisedButton( - key: const ValueKey('play'), - child: const Text('PLAY FILE'), - onPressed: () { playEventsFile(); }, - ), - ], - ), - ], - ); - } - - Future playEventsFile() async { - const StandardMessageCodec codec = StandardMessageCodec(); - try { - final ByteData data = await rootBundle.load('packages/assets_for_android_views/assets/touchEvents'); - final List unTypedRecordedEvents = codec.decodeMessage(data); - final List> recordedEvents = unTypedRecordedEvents - .cast>() - .map>((Map e) =>e.cast()) - .toList(); - await channel.invokeMethod('pipeFlutterViewEvents'); - await viewChannel.invokeMethod('pipeTouchEvents'); - print('replaying ${recordedEvents.length} motion events'); - for (Map event in recordedEvents.reversed) { - await channel.invokeMethod('synthesizeEvent', event); - } - - await channel.invokeMethod('stopFlutterViewEvents'); - await viewChannel.invokeMethod('stopTouchEvents'); - - if (flutterViewEvents.length != embeddedViewEvents.length) - return 'Synthesized ${flutterViewEvents.length} events but the embedded view received ${embeddedViewEvents.length} events'; - - final StringBuffer diff = StringBuffer(); - for (int i = 0; i < flutterViewEvents.length; ++i) { - final String currentDiff = diffMotionEvents(flutterViewEvents[i], embeddedViewEvents[i]); - if (currentDiff.isEmpty) - continue; - if (diff.isNotEmpty) - diff.write(', '); - diff.write(currentDiff); - } - return diff.toString(); - } catch(e) { - return e.toString(); - } - } - - @override - void initState() { - super.initState(); - channel.setMethodCallHandler(onMethodChannelCall); - } - - Future saveRecordedEvents(ByteData data, BuildContext context) async { - if (!await channel.invokeMethod('getStoragePermission')) { - showMessage( - context, 'External storage permissions are required to save events'); - return; - } - try { - final Directory outDir = await getExternalStorageDirectory(); - // This test only runs on Android so we can assume path separator is '/'. - final File file = File('${outDir.path}/$kEventsFileName'); - await file.writeAsBytes(data.buffer.asUint8List(0, data.lengthInBytes), flush: true); - showMessage(context, 'Saved original events to ${file.path}'); - } catch (e) { - showMessage(context, 'Failed saving ${e.toString()}'); - } - } - - void showMessage(BuildContext context, String message) { - Scaffold.of(context).showSnackBar(SnackBar( - content: Text(message), - duration: const Duration(seconds: 3), - )); - } - - void onPlatformViewCreated(int id) { - viewChannel = MethodChannel('simple_view/$id'); - viewChannel.setMethodCallHandler(onViewMethodChannelCall); - driverDataHandler.handlerCompleter.complete(handleDriverMessage); - } - - void listenToFlutterViewEvents() { - channel.invokeMethod('pipeFlutterViewEvents'); - viewChannel.invokeMethod('pipeTouchEvents'); - Timer(const Duration(seconds: 3), () { - channel.invokeMethod('stopFlutterViewEvents'); - viewChannel.invokeMethod('stopTouchEvents'); - }); - } - - Future handleDriverMessage(String message) async { - switch (message) { - case 'run test': - return playEventsFile(); - } - return 'unknown message: "$message"'; - } - - Future onMethodChannelCall(MethodCall call) { - switch (call.method) { - case 'onTouch': - final Map map = call.arguments; - flutterViewEvents.insert(0, map.cast()); - if (flutterViewEvents.length > kEventsBufferSize) - flutterViewEvents.removeLast(); - setState(() {}); - break; - } - return Future.sync(null); - } - - Future onViewMethodChannelCall(MethodCall call) { - switch (call.method) { - case 'onTouch': - final Map map = call.arguments; - embeddedViewEvents.insert(0, map.cast()); - if (embeddedViewEvents.length > kEventsBufferSize) - embeddedViewEvents.removeLast(); - setState(() {}); - break; - } - return Future.sync(null); - } - - Widget buildEventTile(BuildContext context, int index) { - if (embeddedViewEvents.length > index) - return TouchEventDiff( - flutterViewEvents[index], embeddedViewEvents[index]); - return Text( - 'Unmatched event, action: ${flutterViewEvents[index]['action']}'); - } -} - -class TouchEventDiff extends StatelessWidget { - const TouchEventDiff(this.originalEvent, this.synthesizedEvent); - - final Map originalEvent; - final Map synthesizedEvent; - - @override - Widget build(BuildContext context) { - - Color color; - final String diff = diffMotionEvents(originalEvent, synthesizedEvent); - String msg; - final int action = synthesizedEvent['action']; - final String actionName = getActionName(getActionMasked(action), action); - if (diff.isEmpty) { - color = Colors.green; - msg = 'Matched event (action $actionName)'; - } else { - color = Colors.red; - msg = '[$actionName] $diff'; - } - return GestureDetector( - onLongPress: () { - print('expected:'); - prettyPrintEvent(originalEvent); - print('\nactual:'); - prettyPrintEvent(synthesizedEvent); - }, - child: Container( - color: color, - margin: const EdgeInsets.only(bottom: 2.0), - child: Text(msg), ), ); } - void prettyPrintEvent(Map event) { - final StringBuffer buffer = StringBuffer(); - final int action = event['action']; - final int maskedAction = getActionMasked(action); - final String actionName = getActionName(maskedAction, action); - - buffer.write('$actionName '); - if (maskedAction == 5 || maskedAction == 6) { - buffer.write('pointer: ${getPointerIdx(action)} '); - } - - final List> coords = event['pointerCoords'].cast>(); - for (int i = 0; i < coords.length; i++) { - buffer.write('p$i x: ${coords[i]['x']} y: ${coords[i]['y']}, pressure: ${coords[i]['pressure']} '); - } - print(buffer.toString()); + void _pushPage(BuildContext context, Page page) { + Navigator.of(context).push(MaterialPageRoute( + builder: (_) => Scaffold( + body: page, + ))); } } - diff --git a/dev/integration_tests/android_views/lib/motion_events_page.dart b/dev/integration_tests/android_views/lib/motion_events_page.dart new file mode 100644 index 0000000000..4d5d7addaf --- /dev/null +++ b/dev/integration_tests/android_views/lib/motion_events_page.dart @@ -0,0 +1,292 @@ +// 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 'dart:async'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_driver/driver_extension.dart'; +import 'package:path_provider/path_provider.dart'; + +import 'motion_event_diff.dart'; +import 'page.dart'; + +MethodChannel channel = const MethodChannel('android_views_integration'); + +const String kEventsFileName = 'touchEvents'; + +class MotionEventsPage extends Page { + const MotionEventsPage() + : super('Motion Event Tests', const ValueKey('MotionEventsListTile')); + + @override + Widget build(BuildContext context) { + return MotionEventsBody(); + } +} + +/// Wraps a flutter driver [DataHandler] with one that waits until a delegate is set. +/// +/// This allows the driver test to call [FlutterDriver.requestData] before the handler was +/// set by the app in which case the requestData call will only complete once the app is ready +/// for it. +class FutureDataHandler { + final Completer handlerCompleter = Completer(); + + Future handleMessage(String message) async { + final DataHandler handler = await handlerCompleter.future; + return handler(message); + } +} + +FutureDataHandler driverDataHandler = FutureDataHandler(); + +class MotionEventsBody extends StatefulWidget { + @override + State createState() => MotionEventsBodyState(); +} + +class MotionEventsBodyState extends State { + static const int kEventsBufferSize = 1000; + + MethodChannel viewChannel; + + /// The list of motion events that were passed to the FlutterView. + List> flutterViewEvents = >[]; + + /// The list of motion events that were passed to the embedded view. + List> embeddedViewEvents = >[]; + + @override + Widget build(BuildContext context) { + return Column( + children: [ + SizedBox( + height: 300.0, + child: AndroidView( + key: const ValueKey('PlatformView'), + viewType: 'simple_view', + onPlatformViewCreated: onPlatformViewCreated), + ), + Expanded( + child: ListView.builder( + itemBuilder: buildEventTile, + itemCount: flutterViewEvents.length, + ), + ), + Row( + children: [ + RaisedButton( + child: const Text('RECORD'), + onPressed: listenToFlutterViewEvents, + ), + RaisedButton( + child: const Text('CLEAR'), + onPressed: () { + setState(() { + flutterViewEvents.clear(); + embeddedViewEvents.clear(); + }); + }, + ), + RaisedButton( + child: const Text('SAVE'), + onPressed: () { + const StandardMessageCodec codec = StandardMessageCodec(); + saveRecordedEvents( + codec.encodeMessage(flutterViewEvents), context); + }, + ), + RaisedButton( + key: const ValueKey('play'), + child: const Text('PLAY FILE'), + onPressed: () { playEventsFile(); }, + ), + ], + ), + ], + ); + } + + Future playEventsFile() async { + const StandardMessageCodec codec = StandardMessageCodec(); + try { + final ByteData data = await rootBundle.load('packages/assets_for_android_views/assets/touchEvents'); + final List unTypedRecordedEvents = codec.decodeMessage(data); + final List> recordedEvents = unTypedRecordedEvents + .cast>() + .map>((Map e) =>e.cast()) + .toList(); + await channel.invokeMethod('pipeFlutterViewEvents'); + await viewChannel.invokeMethod('pipeTouchEvents'); + print('replaying ${recordedEvents.length} motion events'); + for (Map event in recordedEvents.reversed) { + await channel.invokeMethod('synthesizeEvent', event); + } + + await channel.invokeMethod('stopFlutterViewEvents'); + await viewChannel.invokeMethod('stopTouchEvents'); + + if (flutterViewEvents.length != embeddedViewEvents.length) + return 'Synthesized ${flutterViewEvents.length} events but the embedded view received ${embeddedViewEvents.length} events'; + + final StringBuffer diff = StringBuffer(); + for (int i = 0; i < flutterViewEvents.length; ++i) { + final String currentDiff = diffMotionEvents(flutterViewEvents[i], embeddedViewEvents[i]); + if (currentDiff.isEmpty) + continue; + if (diff.isNotEmpty) + diff.write(', '); + diff.write(currentDiff); + } + return diff.toString(); + } catch(e) { + return e.toString(); + } + } + + @override + void initState() { + super.initState(); + channel.setMethodCallHandler(onMethodChannelCall); + } + + Future saveRecordedEvents(ByteData data, BuildContext context) async { + if (!await channel.invokeMethod('getStoragePermission')) { + showMessage( + context, 'External storage permissions are required to save events'); + return; + } + try { + final Directory outDir = await getExternalStorageDirectory(); + // This test only runs on Android so we can assume path separator is '/'. + final File file = File('${outDir.path}/$kEventsFileName'); + await file.writeAsBytes(data.buffer.asUint8List(0, data.lengthInBytes), flush: true); + showMessage(context, 'Saved original events to ${file.path}'); + } catch (e) { + showMessage(context, 'Failed saving ${e.toString()}'); + } + } + + void showMessage(BuildContext context, String message) { + Scaffold.of(context).showSnackBar(SnackBar( + content: Text(message), + duration: const Duration(seconds: 3), + )); + } + + void onPlatformViewCreated(int id) { + viewChannel = MethodChannel('simple_view/$id'); + viewChannel.setMethodCallHandler(onViewMethodChannelCall); + driverDataHandler.handlerCompleter.complete(handleDriverMessage); + } + + void listenToFlutterViewEvents() { + channel.invokeMethod('pipeFlutterViewEvents'); + viewChannel.invokeMethod('pipeTouchEvents'); + Timer(const Duration(seconds: 3), () { + channel.invokeMethod('stopFlutterViewEvents'); + viewChannel.invokeMethod('stopTouchEvents'); + }); + } + + Future handleDriverMessage(String message) async { + switch (message) { + case 'run test': + return playEventsFile(); + } + return 'unknown message: "$message"'; + } + + Future onMethodChannelCall(MethodCall call) { + switch (call.method) { + case 'onTouch': + final Map map = call.arguments; + flutterViewEvents.insert(0, map.cast()); + if (flutterViewEvents.length > kEventsBufferSize) + flutterViewEvents.removeLast(); + setState(() {}); + break; + } + return Future.sync(null); + } + + Future onViewMethodChannelCall(MethodCall call) { + switch (call.method) { + case 'onTouch': + final Map map = call.arguments; + embeddedViewEvents.insert(0, map.cast()); + if (embeddedViewEvents.length > kEventsBufferSize) + embeddedViewEvents.removeLast(); + setState(() {}); + break; + } + return Future.sync(null); + } + + Widget buildEventTile(BuildContext context, int index) { + if (embeddedViewEvents.length > index) + return TouchEventDiff( + flutterViewEvents[index], embeddedViewEvents[index]); + return Text( + 'Unmatched event, action: ${flutterViewEvents[index]['action']}'); + } +} + +class TouchEventDiff extends StatelessWidget { + const TouchEventDiff(this.originalEvent, this.synthesizedEvent); + + final Map originalEvent; + final Map synthesizedEvent; + + @override + Widget build(BuildContext context) { + + Color color; + final String diff = diffMotionEvents(originalEvent, synthesizedEvent); + String msg; + final int action = synthesizedEvent['action']; + final String actionName = getActionName(getActionMasked(action), action); + if (diff.isEmpty) { + color = Colors.green; + msg = 'Matched event (action $actionName)'; + } else { + color = Colors.red; + msg = '[$actionName] $diff'; + } + return GestureDetector( + onLongPress: () { + print('expected:'); + prettyPrintEvent(originalEvent); + print('\nactual:'); + prettyPrintEvent(synthesizedEvent); + }, + child: Container( + color: color, + margin: const EdgeInsets.only(bottom: 2.0), + child: Text(msg), + ), + ); + } + + void prettyPrintEvent(Map event) { + final StringBuffer buffer = StringBuffer(); + final int action = event['action']; + final int maskedAction = getActionMasked(action); + final String actionName = getActionName(maskedAction, action); + + buffer.write('$actionName '); + if (maskedAction == 5 || maskedAction == 6) { + buffer.write('pointer: ${getPointerIdx(action)} '); + } + + final List> coords = event['pointerCoords'].cast>(); + for (int i = 0; i < coords.length; i++) { + buffer.write('p$i x: ${coords[i]['x']} y: ${coords[i]['y']}, pressure: ${coords[i]['pressure']} '); + } + print(buffer.toString()); + } +} diff --git a/dev/integration_tests/android_views/lib/page.dart b/dev/integration_tests/android_views/lib/page.dart new file mode 100644 index 0000000000..c75bfaed16 --- /dev/null +++ b/dev/integration_tests/android_views/lib/page.dart @@ -0,0 +1,22 @@ +// 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 'package:flutter/material.dart'; + +/// The base class of all the testing pages +// +/// A testing page has to override this in order to be put as one of the items in the main page. +abstract class Page extends StatelessWidget { + const Page(this.title, this.tileKey); + + /// The title of the testing page + /// + /// It will be shown on the main page as the text on the link which opens the page. + final String title; + + /// The key of the ListTile that navigates to the page. + /// + /// Used by the integration test to navigate to the corresponding page. + final ValueKey tileKey; +} diff --git a/dev/integration_tests/android_views/test_driver/main_test.dart b/dev/integration_tests/android_views/test_driver/main_test.dart index 4c193ed6fc..70af1d8e7c 100644 --- a/dev/integration_tests/android_views/test_driver/main_test.dart +++ b/dev/integration_tests/android_views/test_driver/main_test.dart @@ -3,17 +3,31 @@ // found in the LICENSE file. import 'dart:async'; - +import 'dart:io' show Platform; import 'package:flutter_driver/flutter_driver.dart'; import 'package:test/test.dart' hide TypeMatcher, isInstanceOf; Future main() async { - test('MotionEvents recomposition', () async { - final FlutterDriver driver = await FlutterDriver.connect(); - final String errorMessage = await driver.requestData('run test'); + FlutterDriver driver; - expect(errorMessage, ''); - driver?.close(); + setUpAll(() async { + driver = await FlutterDriver.connect(); + }); + + tearDownAll(() { + driver.close(); + }); + + group('MotionEvents tests ', () { + test('recomposition', () async { + if (Platform.isAndroid) { + final SerializableFinder motionEventsListTile = + find.byValueKey('MotionEventsListTile'); + await driver.tap(motionEventsListTile); + await driver.waitFor(find.byValueKey('PlatformView')); + final String errorMessage = await driver.requestData('run test'); + expect(errorMessage, ''); + } + }); }); } -