From fab5ec617d2198eddee5cfbd5f18a4ddaeb79e81 Mon Sep 17 00:00:00 2001 From: Alex Kotliarskyi Date: Tue, 10 Mar 2015 19:11:28 -0700 Subject: [PATCH] Updates from Tue Mar 10 - [ReactNative] Make tests run on TravisCI | Alex Kotliarskyi - [Relay] Update Relay + ES6 class containers | Christoph Pojer - [React Native] Add RCTAdSupport.xcodeproj | Alexsander Akers - [ReactNative][Android] Fix after a new React version was downstreamed | Philipp von Weitershausen - [React Native] Add preliminary animation API | Alex Akers - [ReactKit] Create test for OSS ReactKit | Alex Kotliarskyi - [React Native][Device ID][wip] implement most basic js access | Alex Akers - [ReactNative] OSS Picker | Spencer Ahrens - [ReactNative] Fix typo in RCTUIManager | Tadeu Zagallo - [ReactNative] Fix GeoLocation files letter case | Tadeu Zagallo - Unified the method signature for addUIBlock: to further simplify porting ViewManagers | Nick Lockwood - [ReactNative] Oss GeoMap | Tadeu Zagallo - [ReactNative] OSS CameraRoll | Tadeu Zagallo - [ReactNative] allowLossyConversion on NSString->NSData conversion | Andrew Rasmussen - [React Native][RFC] Print __DEV__ value on app start | Alex Kotliarskyi --- .travis.yml | 10 +- Examples/2048/2048.xcodeproj/project.pbxproj | 357 ++++++++++++++++++ Examples/2048/AppDelegate.h | 9 + Examples/2048/AppDelegate.m | 44 +++ Examples/2048/Base.lproj/LaunchScreen.xib | 41 ++ Examples/2048/Game2048.js | 298 +++++++++++++++ Examples/2048/GameBoard.js | 189 ++++++++++ .../AppIcon.appiconset/Contents.json | 38 ++ Examples/2048/Info.plist | 38 ++ Examples/2048/main.m | 11 + Examples/Movies/AppDelegate.m | 2 +- Examples/TicTacToe/AppDelegate.m | 2 +- Examples/UIExplorer/AdSupportIOSExample.js | 73 ++++ Examples/UIExplorer/AppDelegate.m | 2 +- Examples/UIExplorer/CameraRollExample.ios.js | 115 ++++++ Examples/UIExplorer/CameraRollView.ios.js | 231 ++++++++++++ ...cationExample.js => GeolocationExample.js} | 2 +- Examples/UIExplorer/MapViewExample.js | 196 ++++++++++ Examples/UIExplorer/PickerExample.js | 113 ++++++ .../UIExplorer.xcodeproj/project.pbxproj | 164 +++++++- .../xcschemes/UIExplorer.xcscheme | 24 ++ Examples/UIExplorer/UIExplorerList.js | 6 +- .../UIExplorer/UIExplorerTests/Info.plist | 24 ++ .../UIExplorerTests/UIExplorerTests.m | 52 +++ Libraries/AdSupport/AdSupportIOS.js | 14 + Libraries/AdSupport/RCTAdSupport.h | 7 + Libraries/AdSupport/RCTAdSupport.m | 20 + .../RCTAdSupport.xcodeproj/project.pbxproj | 248 ++++++++++++ Libraries/Animation/Animation.js | 34 ++ Libraries/Animation/AnimationMixin.js | 45 +++ Libraries/Animation/AnimationUtils.js | 226 +++++++++++ Libraries/AppRegistry/AppRegistry.js | 8 +- Libraries/CameraRoll/CameraRoll.js | 149 ++++++++ Libraries/Components/MapView/MapView.js | 165 ++++++++ .../Components/Touchable/TouchableBounce.js | 124 ++++++ .../Geolocation.ios.js | 0 .../RCTGeolocation.xcodeproj/project.pbxproj | 0 .../RCTLocationObserver.h | 0 .../RCTLocationObserver.m | 0 Libraries/Image/RCTCameraRollManager.h | 7 + Libraries/Image/RCTCameraRollManager.m | 148 ++++++++ .../Image/RCTImage.xcodeproj/project.pbxproj | 12 + Libraries/Image/RCTImageLoader.h | 13 + Libraries/Image/RCTImageLoader.m | 98 +++++ Libraries/Image/RCTStaticImage.m | 2 +- Libraries/Image/RCTStaticImageManager.m | 15 + Libraries/Picker/PickerIOS.android.js | 10 + Libraries/Picker/PickerIOS.ios.js | 120 ++++++ Libraries/ReactIOS/ReactIOS.js | 1 + ...pplication.ios.js => renderApplication.js} | 0 Libraries/Utilities/groupByEveryN.js | 46 +++ Libraries/react-native/react-native.js | 4 + ReactKit/Base/RCTAssert.h | 4 +- ReactKit/Base/RCTConvert.h | 6 + ReactKit/Base/RCTConvert.m | 108 ++++-- ReactKit/Base/RCTUtils.m | 27 +- ReactKit/Modules/RCTAnimationManager.h | 9 + ReactKit/Modules/RCTAnimationManager.m | 203 ++++++++++ ReactKit/Modules/RCTUIManager.m | 7 +- ReactKit/ReactKit.xcodeproj/project.pbxproj | 84 +++-- ReactKit/Views/RCTMap.h | 24 ++ ReactKit/Views/RCTMap.m | 130 +++++++ ReactKit/Views/RCTMapManager.h | 7 + ReactKit/Views/RCTMapManager.m | 119 ++++++ ReactKit/Views/RCTPicker.h | 11 + ReactKit/Views/RCTPicker.m | 91 +++++ ReactKit/Views/RCTPickerManager.h | 7 + ReactKit/Views/RCTPickerManager.m | 28 ++ ReactKit/Views/RCTTextField.m | 9 +- ReactKit/Views/RCTTextFieldManager.m | 1 - package.json | 3 +- 71 files changed, 4353 insertions(+), 82 deletions(-) create mode 100644 Examples/2048/2048.xcodeproj/project.pbxproj create mode 100644 Examples/2048/AppDelegate.h create mode 100644 Examples/2048/AppDelegate.m create mode 100644 Examples/2048/Base.lproj/LaunchScreen.xib create mode 100644 Examples/2048/Game2048.js create mode 100644 Examples/2048/GameBoard.js create mode 100644 Examples/2048/Images.xcassets/AppIcon.appiconset/Contents.json create mode 100644 Examples/2048/Info.plist create mode 100644 Examples/2048/main.m create mode 100644 Examples/UIExplorer/AdSupportIOSExample.js create mode 100644 Examples/UIExplorer/CameraRollExample.ios.js create mode 100644 Examples/UIExplorer/CameraRollView.ios.js rename Examples/UIExplorer/{GeoLocationExample.js => GeolocationExample.js} (97%) create mode 100644 Examples/UIExplorer/MapViewExample.js create mode 100644 Examples/UIExplorer/PickerExample.js create mode 100644 Examples/UIExplorer/UIExplorerTests/Info.plist create mode 100644 Examples/UIExplorer/UIExplorerTests/UIExplorerTests.m create mode 100644 Libraries/AdSupport/AdSupportIOS.js create mode 100644 Libraries/AdSupport/RCTAdSupport.h create mode 100644 Libraries/AdSupport/RCTAdSupport.m create mode 100644 Libraries/AdSupport/RCTAdSupport.xcodeproj/project.pbxproj create mode 100644 Libraries/Animation/Animation.js create mode 100644 Libraries/Animation/AnimationMixin.js create mode 100644 Libraries/Animation/AnimationUtils.js create mode 100644 Libraries/CameraRoll/CameraRoll.js create mode 100644 Libraries/Components/MapView/MapView.js create mode 100644 Libraries/Components/Touchable/TouchableBounce.js rename Libraries/{GeoLocation => Geolocation}/Geolocation.ios.js (100%) rename Libraries/{GeoLocation => Geolocation}/RCTGeolocation.xcodeproj/project.pbxproj (100%) rename Libraries/{GeoLocation => Geolocation}/RCTLocationObserver.h (100%) rename Libraries/{GeoLocation => Geolocation}/RCTLocationObserver.m (100%) create mode 100644 Libraries/Image/RCTCameraRollManager.h create mode 100644 Libraries/Image/RCTCameraRollManager.m create mode 100644 Libraries/Image/RCTImageLoader.h create mode 100644 Libraries/Image/RCTImageLoader.m create mode 100644 Libraries/Picker/PickerIOS.android.js create mode 100644 Libraries/Picker/PickerIOS.ios.js rename Libraries/ReactIOS/{renderApplication.ios.js => renderApplication.js} (100%) create mode 100644 Libraries/Utilities/groupByEveryN.js create mode 100644 ReactKit/Modules/RCTAnimationManager.h create mode 100644 ReactKit/Modules/RCTAnimationManager.m create mode 100644 ReactKit/Views/RCTMap.h create mode 100644 ReactKit/Views/RCTMap.m create mode 100644 ReactKit/Views/RCTMapManager.h create mode 100644 ReactKit/Views/RCTMapManager.m create mode 100644 ReactKit/Views/RCTPicker.h create mode 100644 ReactKit/Views/RCTPicker.m create mode 100644 ReactKit/Views/RCTPickerManager.h create mode 100644 ReactKit/Views/RCTPickerManager.m diff --git a/.travis.yml b/.travis.yml index 6e5919de3..98aeb094f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,7 @@ -language: node_js -node_js: - - "0.10" +language: objective-c +xcode_project: Examples/UIExplorer/UIExplorer.xcodeproj +xcode_scheme: UIExplorer +xcode_sdk: iphonesimulator8.1 +install: + - npm install + - npm test diff --git a/Examples/2048/2048.xcodeproj/project.pbxproj b/Examples/2048/2048.xcodeproj/project.pbxproj new file mode 100644 index 000000000..256cd7aef --- /dev/null +++ b/Examples/2048/2048.xcodeproj/project.pbxproj @@ -0,0 +1,357 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; }; + 13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB11A68108700A75B9A /* LaunchScreen.xib */; }; + 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; + 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; + 832341BD1AAA6AB300B99B32 /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 832341B51AAA6A8300B99B32 /* libRCTText.a */; }; + 8323482C1A77B59500B55238 /* libReactKit.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 832348291A77B50100B55238 /* libReactKit.a */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 832341B41AAA6A8300B99B32 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 58B5119B1A9E6C1200147676; + remoteInfo = RCTText; + }; + 832348281A77B50100B55238 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 834D32361A76971A00F38302 /* ReactKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 83CBBA2E1A601D0E00E9B192; + remoteInfo = ReactKit; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 13B07F961A680F5B00A75B9A /* 2048.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = 2048.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + 13B07FB01A68108700A75B9A /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 13B07FB21A68108700A75B9A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; + 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; + 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = ../../Libraries/Text/RCTText.xcodeproj; sourceTree = ""; }; + 834D32361A76971A00F38302 /* ReactKit.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ReactKit.xcodeproj; path = ../../ReactKit/ReactKit.xcodeproj; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 13B07F8C1A680F5B00A75B9A /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 8323482C1A77B59500B55238 /* libReactKit.a in Frameworks */, + 832341BD1AAA6AB300B99B32 /* libRCTText.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 13B07FAE1A68108700A75B9A /* 2048 */ = { + isa = PBXGroup; + children = ( + 13B07FAF1A68108700A75B9A /* AppDelegate.h */, + 13B07FB01A68108700A75B9A /* AppDelegate.m */, + 13B07FB51A68108700A75B9A /* Images.xcassets */, + 13B07FB61A68108700A75B9A /* Info.plist */, + 13B07FB11A68108700A75B9A /* LaunchScreen.xib */, + 13B07FB71A68108700A75B9A /* main.m */, + ); + name = 2048; + sourceTree = ""; + }; + 832341AE1AAA6A7D00B99B32 /* Libraries */ = { + isa = PBXGroup; + children = ( + 834D32361A76971A00F38302 /* ReactKit.xcodeproj */, + 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */, + ); + name = Libraries; + sourceTree = ""; + }; + 832341B11AAA6A8300B99B32 /* Products */ = { + isa = PBXGroup; + children = ( + 832341B51AAA6A8300B99B32 /* libRCTText.a */, + ); + name = Products; + sourceTree = ""; + }; + 832348241A77B50100B55238 /* Products */ = { + isa = PBXGroup; + children = ( + 832348291A77B50100B55238 /* libReactKit.a */, + ); + name = Products; + sourceTree = ""; + }; + 83CBB9F61A601CBA00E9B192 = { + isa = PBXGroup; + children = ( + 13B07FAE1A68108700A75B9A /* 2048 */, + 832341AE1AAA6A7D00B99B32 /* Libraries */, + 83CBBA001A601CBA00E9B192 /* Products */, + ); + sourceTree = ""; + }; + 83CBBA001A601CBA00E9B192 /* Products */ = { + isa = PBXGroup; + children = ( + 13B07F961A680F5B00A75B9A /* 2048.app */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 13B07F861A680F5B00A75B9A /* 2048 */ = { + isa = PBXNativeTarget; + buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "2048" */; + buildPhases = ( + 13B07F871A680F5B00A75B9A /* Sources */, + 13B07F8C1A680F5B00A75B9A /* Frameworks */, + 13B07F8E1A680F5B00A75B9A /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = 2048; + productName = "Hello World"; + productReference = 13B07F961A680F5B00A75B9A /* 2048.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 83CBB9F71A601CBA00E9B192 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0610; + ORGANIZATIONNAME = Facebook; + }; + buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "2048" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 83CBB9F61A601CBA00E9B192; + productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */; + projectDirPath = ""; + projectReferences = ( + { + ProductGroup = 832341B11AAA6A8300B99B32 /* Products */; + ProjectRef = 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */; + }, + { + ProductGroup = 832348241A77B50100B55238 /* Products */; + ProjectRef = 834D32361A76971A00F38302 /* ReactKit.xcodeproj */; + }, + ); + projectRoot = ""; + targets = ( + 13B07F861A680F5B00A75B9A /* 2048 */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXReferenceProxy section */ + 832341B51AAA6A8300B99B32 /* libRCTText.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTText.a; + remoteRef = 832341B41AAA6A8300B99B32 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 832348291A77B50100B55238 /* libReactKit.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libReactKit.a; + remoteRef = 832348281A77B50100B55238 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; +/* End PBXReferenceProxy section */ + +/* Begin PBXResourcesBuildPhase section */ + 13B07F8E1A680F5B00A75B9A /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */, + 13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 13B07F871A680F5B00A75B9A /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */, + 13B07FC11A68108700A75B9A /* main.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 13B07FB11A68108700A75B9A /* LaunchScreen.xib */ = { + isa = PBXVariantGroup; + children = ( + 13B07FB21A68108700A75B9A /* Base */, + ); + name = LaunchScreen.xib; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 13B07F941A680F5B00A75B9A /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = "$(SRCROOT)/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = 2048; + }; + name = Debug; + }; + 13B07F951A680F5B00A75B9A /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = "$(SRCROOT)/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = 2048; + }; + name = Release; + }; + 83CBBA201A601CBA00E9B192 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "$(SRCROOT)/../../ReactKit/**", + ); + IPHONEOS_DEPLOYMENT_TARGET = 7.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + }; + name = Debug; + }; + 83CBBA211A601CBA00E9B192 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = YES; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "$(SRCROOT)/../../ReactKit/**", + ); + IPHONEOS_DEPLOYMENT_TARGET = 7.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "2048" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 13B07F941A680F5B00A75B9A /* Debug */, + 13B07F951A680F5B00A75B9A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "2048" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 83CBBA201A601CBA00E9B192 /* Debug */, + 83CBBA211A601CBA00E9B192 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 83CBB9F71A601CBA00E9B192 /* Project object */; +} diff --git a/Examples/2048/AppDelegate.h b/Examples/2048/AppDelegate.h new file mode 100644 index 000000000..062fb99c0 --- /dev/null +++ b/Examples/2048/AppDelegate.h @@ -0,0 +1,9 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#import + +@interface AppDelegate : UIResponder + +@property (nonatomic, strong) UIWindow *window; + +@end diff --git a/Examples/2048/AppDelegate.m b/Examples/2048/AppDelegate.m new file mode 100644 index 000000000..76172139c --- /dev/null +++ b/Examples/2048/AppDelegate.m @@ -0,0 +1,44 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#import "AppDelegate.h" + +#import "RCTRootView.h" + +@implementation AppDelegate + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions +{ + NSURL *jsCodeLocation; + RCTRootView *rootView = [[RCTRootView alloc] init]; + + // Loading JavaScript code - uncomment the one you want. + + // OPTION 1 + // Load from development server. Start the server from the repository root: + // + // $ npm start + // + // To run on device, change `localhost` to the IP address of your computer, and make sure your computer and + // iOS device are on the same Wi-Fi network. + jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/Examples/2048/Game2048.includeRequire.runModule.bundle?dev=true"]; + + // OPTION 2 + // Load from pre-bundled file on disk. To re-generate the static bundle, run + // + // $ curl http://localhost:8081/Examples/2048/Game2048.includeRequire.runModule.bundle -o main.jsbundle + // + // and uncomment the next following line + // jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; + + rootView.scriptURL = jsCodeLocation; + rootView.moduleName = @"Game2048"; + + self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; + UIViewController *rootViewController = [[UIViewController alloc] init]; + rootViewController.view = rootView; + self.window.rootViewController = rootViewController; + [self.window makeKeyAndVisible]; + return YES; +} + +@end diff --git a/Examples/2048/Base.lproj/LaunchScreen.xib b/Examples/2048/Base.lproj/LaunchScreen.xib new file mode 100644 index 000000000..351e21c59 --- /dev/null +++ b/Examples/2048/Base.lproj/LaunchScreen.xib @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Examples/2048/Game2048.js b/Examples/2048/Game2048.js new file mode 100644 index 000000000..4337aee91 --- /dev/null +++ b/Examples/2048/Game2048.js @@ -0,0 +1,298 @@ +/** + * Copyright 2004-present Facebook. All Rights Reserved. + * + * @providesModule Game2048 + * @flow + */ +'use strict'; + +var React = require('react-native'); +var { + Animation, + AppRegistry, + StyleSheet, + Text, + View, +} = React; + +var GameBoard = require('./GameBoard'); +var TouchableBounce = require('TouchableBounce'); + +var BOARD_PADDING = 3; +var CELL_MARGIN = 4; +var CELL_SIZE = 60; + +var Cell = React.createClass({ + render: function() { + return ; + } +}); + +var Board = React.createClass({ + render() { + return ( + + + + + + {this.props.children} + + ); + } +}); + +var Tile = React.createClass({ + mixins: [Animation.Mixin], + + calculateOffset() { + var tile = this.props.tile; + + var pos = (i) => { + return BOARD_PADDING + (i * (CELL_SIZE + CELL_MARGIN * 2) + CELL_MARGIN); + }; + + var animationPosition = (i) => { + return pos(i) + (CELL_SIZE / 2); + }; + + var offset = { + top: pos(tile.toRow()), + left: pos(tile.toColumn()), + }; + + if (tile.isNew()) { + offset.opacity = 0; + } else { + var point = [ + animationPosition(tile.toColumn()), + animationPosition(tile.toRow()), + ]; + this.startAnimation('this', 100, 0, 'easeInOutQuad', {position: point}); + } + + return offset; + }, + + componentDidMount() { + setTimeout(() => { + this.startAnimation('this', 300, 0, 'easeInOutQuad', {scaleXY: [1, 1]}); + this.startAnimation('this', 100, 0, 'easeInOutQuad', {opacity: 1}); + }, 0); + }, + + render() { + var tile = this.props.tile; + + var tileStyles = [ + styles.tile, + styles['tile' + tile.value], + this.calculateOffset() + ]; + + var textStyles = [ + styles.value, + tile.value > 4 && styles.whiteText, + tile.value > 100 && styles.threeDigits, + tile.value > 1000 && styles.fourDigits, + ]; + + return ( + + {tile.value} + + ); + } +}); + +var GameEndOverlay = React.createClass({ + render() { + var board = this.props.board; + + if (!board.hasWon() && !board.hasLost()) { + return ; + } + + var message = board.hasWon() ? + 'Good Job!' : 'Game Over'; + + return ( + + {message} + + + Try Again? + + + + ); + } +}); + +var Game2048 = React.createClass({ + getInitialState() { + return { board: new GameBoard() }; + }, + + restartGame() { + this.setState(this.getInitialState()); + }, + + handleTouchStart(event) { + if (this.state.board.hasWon()) { + return; + } + + this.startX = event.nativeEvent.pageX; + this.startY = event.nativeEvent.pageY; + }, + + handleTouchEnd(event) { + if (this.state.board.hasWon()) { + return; + } + + var deltaX = event.nativeEvent.pageX - this.startX; + var deltaY = event.nativeEvent.pageY - this.startY; + + var direction = -1; + if (Math.abs(deltaX) > 3 * Math.abs(deltaY) && Math.abs(deltaX) > 30) { + direction = deltaX > 0 ? 2 : 0; + } else if (Math.abs(deltaY) > 3 * Math.abs(deltaX) && Math.abs(deltaY) > 30) { + direction = deltaY > 0 ? 3 : 1; + } + + if (direction !== -1) { + this.setState({board: this.state.board.move(direction)}); + } + }, + + render() { + var tiles = this.state.board.tiles + .filter((tile) => tile.value) + .map((tile) => ); + + return ( + + + {tiles} + + + + ); + } +}); + +var styles = StyleSheet.create({ + container: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, + board: { + padding: BOARD_PADDING, + backgroundColor: '#bbaaaa', + borderRadius: 5, + }, + overlay: { + position: 'absolute', + top: 0, + bottom: 0, + left: 0, + right: 0, + backgroundColor: 'rgba(221, 221, 221, 0.5)', + flex: 1, + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center', + }, + overlayMessage: { + fontSize: 40, + marginBottom: 20, + }, + tryAgain: { + backgroundColor: '#887766', + padding: 20, + borderRadius: 5, + }, + tryAgainText: { + color: '#ffffff', + fontSize: 20, + fontWeight: 'bold', + }, + cell: { + width: CELL_SIZE, + height: CELL_SIZE, + borderRadius: 5, + backgroundColor: '#ddccbb', + margin: CELL_MARGIN, + }, + row: { + flexDirection: 'row', + }, + tile: { + position: 'absolute', + width: CELL_SIZE, + height: CELL_SIZE, + backgroundColor: '#ddccbb', + borderRadius: 5, + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, + value: { + fontSize: 24, + color: '#776666', + fontFamily: 'Verdana', + fontWeight: 'bold', + }, + tile2: { + backgroundColor: '#eeeeee', + }, + tile4: { + backgroundColor: '#eeeecc', + }, + tile8: { + backgroundColor: '#ffbb88', + }, + tile16: { + backgroundColor: '#ff9966', + }, + tile32: { + backgroundColor: '#ff7755', + }, + tile64: { + backgroundColor: '#ff5533', + }, + tile128: { + backgroundColor: '#eecc77', + }, + tile256: { + backgroundColor: '#eecc66', + }, + tile512: { + backgroundColor: '#eecc55', + }, + tile1024: { + backgroundColor: '#eecc33', + }, + tile2048: { + backgroundColor: '#eecc22', + }, + whiteText: { + color: '#ffffff', + }, + threeDigits: { + fontSize: 20, + }, + fourDigits: { + fontSize: 18, + }, +}); + +AppRegistry.registerComponent('Game2048', () => Game2048); + +module.exports = Game2048; diff --git a/Examples/2048/GameBoard.js b/Examples/2048/GameBoard.js new file mode 100644 index 000000000..8db899116 --- /dev/null +++ b/Examples/2048/GameBoard.js @@ -0,0 +1,189 @@ +/** + * Copyright 2004-present Facebook. All Rights Reserved. + * + * @providesModule GameBoard + */ +'use strict'; + +// NB: Taken straight from: https://github.com/IvanVergiliev/2048-react/blob/master/src/board.js +// with no modificiation except to format it for CommonJS and fix lint errors + +var rotateLeft = function (matrix) { + var rows = matrix.length; + var columns = matrix[0].length; + var res = []; + for (var row = 0; row < rows; ++row) { + res.push([]); + for (var column = 0; column < columns; ++column) { + res[row][column] = matrix[column][columns - row - 1]; + } + } + return res; +}; + +var Tile = function (value, row, column) { + this.value = value || 0; + this.row = row || -1; + this.column = column || -1; + this.oldRow = -1; + this.oldColumn = -1; + this.markForDeletion = false; + this.mergedInto = null; + this.id = Tile.id++; +}; + +Tile.id = 0; + +Tile.prototype.moveTo = function (row, column) { + this.oldRow = this.row; + this.oldColumn = this.column; + this.row = row; + this.column = column; +}; + +Tile.prototype.isNew = function () { + return this.oldRow === -1 && !this.mergedInto; +}; + +Tile.prototype.hasMoved = function () { + return (this.fromRow() !== -1 && (this.fromRow() !== this.toRow() || this.fromColumn() !== this.toColumn())) || + this.mergedInto; +}; + +Tile.prototype.fromRow = function () { + return this.mergedInto ? this.row : this.oldRow; +}; + +Tile.prototype.fromColumn = function () { + return this.mergedInto ? this.column : this.oldColumn; +}; + +Tile.prototype.toRow = function () { + return this.mergedInto ? this.mergedInto.row : this.row; +}; + +Tile.prototype.toColumn = function () { + return this.mergedInto ? this.mergedInto.column : this.column; +}; + +var Board = function () { + this.tiles = []; + this.cells = []; + for (var i = 0; i < Board.size; ++i) { + this.cells[i] = [this.addTile(), this.addTile(), this.addTile(), this.addTile()]; + } + this.addRandomTile(); + this.setPositions(); + this.won = false; +}; + +Board.prototype.addTile = function () { + var res = new Tile(); + Tile.apply(res, arguments); + this.tiles.push(res); + return res; +}; + +Board.size = 4; + +Board.prototype.moveLeft = function () { + var hasChanged = false; + for (var row = 0; row < Board.size; ++row) { + var currentRow = this.cells[row].filter(function (tile) { return tile.value !== 0; }); + var resultRow = []; + for (var target = 0; target < Board.size; ++target) { + var targetTile = currentRow.length ? currentRow.shift() : this.addTile(); + if (currentRow.length > 0 && currentRow[0].value === targetTile.value) { + var tile1 = targetTile; + targetTile = this.addTile(targetTile.value); + tile1.mergedInto = targetTile; + var tile2 = currentRow.shift(); + tile2.mergedInto = targetTile; + targetTile.value += tile2.value; + } + resultRow[target] = targetTile; + this.won |= (targetTile.value === 2048); + hasChanged |= (targetTile.value !== this.cells[row][target].value); + } + this.cells[row] = resultRow; + } + return hasChanged; +}; + +Board.prototype.setPositions = function () { + this.cells.forEach(function (row, rowIndex) { + row.forEach(function (tile, columnIndex) { + tile.oldRow = tile.row; + tile.oldColumn = tile.column; + tile.row = rowIndex; + tile.column = columnIndex; + tile.markForDeletion = false; + }); + }); +}; + +Board.fourProbability = 0.1; + +Board.prototype.addRandomTile = function () { + var emptyCells = []; + for (var r = 0; r < Board.size; ++r) { + for (var c = 0; c < Board.size; ++c) { + if (this.cells[r][c].value === 0) { + emptyCells.push({r: r, c: c}); + } + } + } + var index = Math.floor(Math.random() * emptyCells.length); + var cell = emptyCells[index]; + var newValue = Math.random() < Board.fourProbability ? 4 : 2; + this.cells[cell.r][cell.c] = this.addTile(newValue); +}; + +Board.prototype.move = function (direction) { + // 0 -> left, 1 -> up, 2 -> right, 3 -> down + this.clearOldTiles(); + for (var i = 0; i < direction; ++i) { + this.cells = rotateLeft(this.cells); + } + var hasChanged = this.moveLeft(); + for (var i = direction; i < 4; ++i) { + this.cells = rotateLeft(this.cells); + } + if (hasChanged) { + this.addRandomTile(); + } + this.setPositions(); + return this; +}; + +Board.prototype.clearOldTiles = function () { + this.tiles = this.tiles.filter(function (tile) { return tile.markForDeletion === false; }); + this.tiles.forEach(function (tile) { tile.markForDeletion = true; }); +}; + +Board.prototype.hasWon = function () { + return this.won; +}; + +Board.deltaX = [-1, 0, 1, 0]; +Board.deltaY = [0, -1, 0, 1]; + +Board.prototype.hasLost = function () { + var canMove = false; + for (var row = 0; row < Board.size; ++row) { + for (var column = 0; column < Board.size; ++column) { + canMove |= (this.cells[row][column].value === 0); + for (var dir = 0; dir < 4; ++dir) { + var newRow = row + Board.deltaX[dir]; + var newColumn = column + Board.deltaY[dir]; + if (newRow < 0 || newRow >= Board.size || newColumn < 0 || newColumn >= Board.size) { + continue; + } + canMove |= (this.cells[row][column].value === this.cells[newRow][newColumn].value); + } + } + } + return !canMove; +}; + +module.exports = Board; diff --git a/Examples/2048/Images.xcassets/AppIcon.appiconset/Contents.json b/Examples/2048/Images.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..118c98f74 --- /dev/null +++ b/Examples/2048/Images.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,38 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Examples/2048/Info.plist b/Examples/2048/Info.plist new file mode 100644 index 000000000..1c298405a --- /dev/null +++ b/Examples/2048/Info.plist @@ -0,0 +1,38 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + com.facebook.$(PRODUCT_NAME:rfc1034identifier) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/Examples/2048/main.m b/Examples/2048/main.m new file mode 100644 index 000000000..a43b55738 --- /dev/null +++ b/Examples/2048/main.m @@ -0,0 +1,11 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#import + +#import "AppDelegate.h" + +int main(int argc, char * argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} diff --git a/Examples/Movies/AppDelegate.m b/Examples/Movies/AppDelegate.m index c01fc2ca9..c53f8edc4 100644 --- a/Examples/Movies/AppDelegate.m +++ b/Examples/Movies/AppDelegate.m @@ -20,7 +20,7 @@ // // To run on device, change `localhost` to the IP address of your computer, and make sure your computer and // iOS device are on the same Wi-Fi network. - jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/Examples/Movies/MoviesApp.includeRequire.runModule.bundle"]; + jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/Examples/Movies/MoviesApp.includeRequire.runModule.bundle?dev=true"]; // OPTION 2 // Load from pre-bundled file on disk. To re-generate the static bundle, run diff --git a/Examples/TicTacToe/AppDelegate.m b/Examples/TicTacToe/AppDelegate.m index 52e682752..d2add4fac 100644 --- a/Examples/TicTacToe/AppDelegate.m +++ b/Examples/TicTacToe/AppDelegate.m @@ -20,7 +20,7 @@ // // To run on device, change `localhost` to the IP address of your computer, and make sure your computer and // iOS device are on the same Wi-Fi network. - jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/Examples/TicTacToe/TicTacToeApp.includeRequire.runModule.bundle"]; + jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/Examples/TicTacToe/TicTacToeApp.includeRequire.runModule.bundle?dev=true"]; // OPTION 2 // Load from pre-bundled file on disk. To re-generate the static bundle, run diff --git a/Examples/UIExplorer/AdSupportIOSExample.js b/Examples/UIExplorer/AdSupportIOSExample.js new file mode 100644 index 000000000..1da508fd9 --- /dev/null +++ b/Examples/UIExplorer/AdSupportIOSExample.js @@ -0,0 +1,73 @@ +/** + * Copyright 2004-present Facebook. All Rights Reserved. + * + * @providesModule AdSupportIOSExample + */ +/* eslint no-console: 0 */ +'use strict'; + +var AdSupportIOS = require('AdSupportIOS'); + +var React = require('react-native'); +var { + StyleSheet, + Text, + View, +} = React; + +exports.framework = 'React'; +exports.title = 'Advertising ID'; +exports.description = 'Example of using the ad support API.'; + +exports.examples = [ + { + title: 'Ad Support IOS', + render: function() { + return ; + }, + } +]; + +var AdSupportIOSExample = React.createClass({ + getInitialState: function() { + return { + deviceID: 'No IDFA yet', + }; + }, + + componentDidMount: function() { + AdSupportIOS.getAdvertisingId( + this._onSuccess, + this._onFailure + ); + }, + + _onSuccess: function(deviceID) { + this.setState({ + 'deviceID': deviceID, + }); + }, + + _onFailure: function(e) { + this.setState({ + 'deviceID': 'Error!', + }); + }, + + render: function() { + return ( + + + Advertising ID: + {JSON.stringify(this.state.deviceID)} + + + ); + } +}); + +var styles = StyleSheet.create({ + title: { + fontWeight: 'bold', + }, +}); diff --git a/Examples/UIExplorer/AppDelegate.m b/Examples/UIExplorer/AppDelegate.m index a3fdbd629..b280f1357 100644 --- a/Examples/UIExplorer/AppDelegate.m +++ b/Examples/UIExplorer/AppDelegate.m @@ -20,7 +20,7 @@ // // To run on device, change `localhost` to the IP address of your computer, and make sure your computer and // iOS device are on the same Wi-Fi network. - jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/Examples/UIExplorer/UIExplorerApp.includeRequire.runModule.bundle"]; + jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/Examples/UIExplorer/UIExplorerApp.includeRequire.runModule.bundle?dev=true"]; // OPTION 2 // Load from pre-bundled file on disk. To re-generate the static bundle, run diff --git a/Examples/UIExplorer/CameraRollExample.ios.js b/Examples/UIExplorer/CameraRollExample.ios.js new file mode 100644 index 000000000..8037f536f --- /dev/null +++ b/Examples/UIExplorer/CameraRollExample.ios.js @@ -0,0 +1,115 @@ +/** + * Copyright 2004-present Facebook. All Rights Reserved. + * + * @providesModule CameraRollExample + */ +'use strict'; + +var React = require('react-native'); +var { + CameraRoll, + Image, + Slider, + StyleSheet, + SwitchIOS, + Text, + View, +} = React; + +var CameraRollView = require('./CameraRollView.ios'); + +var CAMERA_ROLL_VIEW = 'camera_roll_view'; + +var CameraRollExample = React.createClass({ + + getInitialState() { + return { + groupTypes: 'SavedPhotos', + sliderValue: 1, + bigImages: true, + }; + }, + + render() { + return ( + + + {(this.state.bigImages ? 'Big' : 'Small') + ' Images'} + + {'Group Type: ' + this.state.groupTypes} + + + ); + }, + + _renderImage(asset) { + var imageSize = this.state.bigImages ? 150 : 75; + var imageStyle = [styles.image, {width: imageSize, height: imageSize}]; + var location = asset.node.location.longitude ? + JSON.stringify(asset.node.location) : 'Unknown location'; + return ( + + + + {asset.node.image.uri} + {location} + {asset.node.group_name} + {new Date(asset.node.timestamp).toString()} + + + ); + }, + + _onSliderChange(value) { + var options = CameraRoll.GroupTypesOptions; + var index = Math.floor(value * options.length * 0.99); + var groupTypes = options[index]; + if (groupTypes !== this.state.groupTypes) { + this.setState({groupTypes: groupTypes}); + } + }, + + _onSwitchChange(value) { + this.refs[CAMERA_ROLL_VIEW].rendererChanged(); + this.setState({ bigImages: value }); + } +}); + +var styles = StyleSheet.create({ + row: { + flexDirection: 'row', + flex: 1, + }, + url: { + fontSize: 9, + marginBottom: 14, + }, + image: { + margin: 4, + }, + info: { + flex: 1, + }, +}); + +exports.title = ''; +exports.description = 'Example component that uses CameraRoll to list user\'s photos'; +exports.examples = [ + { + title: 'Photos', + render() { return ; } + } +]; diff --git a/Examples/UIExplorer/CameraRollView.ios.js b/Examples/UIExplorer/CameraRollView.ios.js new file mode 100644 index 000000000..f0ee92afc --- /dev/null +++ b/Examples/UIExplorer/CameraRollView.ios.js @@ -0,0 +1,231 @@ +/** + * Copyright 2004-present Facebook. All Rights Reserved. + * + * @providesModule CameraRollView + */ +'use strict'; + +var React = require('react-native'); +var { + ActivityIndicatorIOS, + CameraRoll, + Image, + ListView, + ListViewDataSource, + StyleSheet, + View, +} = React; + +var groupByEveryN = require('groupByEveryN'); +var logError = require('logError'); + +var propTypes = { + /** + * The group where the photos will be fetched from. Possible + * values are 'Album', 'All', 'Event', 'Faces', 'Library', 'PhotoStream' + * and SavedPhotos. + */ + groupTypes: React.PropTypes.oneOf([ + 'Album', + 'All', + 'Event', + 'Faces', + 'Library', + 'PhotoStream', + 'SavedPhotos', + ]), + + /** + * Number of images that will be fetched in one page. + */ + batchSize: React.PropTypes.number, + + /** + * A function that takes a single image as a parameter and renders it. + */ + renderImage: React.PropTypes.func, + + /** + * imagesPerRow: Number of images to be shown in each row. + */ + imagesPerRow: React.PropTypes.number, +}; + +var CameraRollView = React.createClass({ + propTypes: propTypes, + + getDefaultProps: function() { + return { + groupTypes: 'SavedPhotos', + batchSize: 5, + imagesPerRow: 1, + renderImage: function(asset) { + var imageSize = 150; + var imageStyle = [styles.image, {width: imageSize, height: imageSize}]; + return ( + + ); + }, + }; + }, + + getInitialState: function() { + var ds = new ListViewDataSource({rowHasChanged: this._rowHasChanged}); + + return { + assets: [], + groupTypes: this.props.groupTypes, + lastCursor: null, + noMore: false, + loadingMore: false, + dataSource: ds, + }; + }, + + /** + * This should be called when the image renderer is changed to tell the + * component to re-render its assets. + */ + rendererChanged: function() { + var ds = new ListViewDataSource({rowHasChanged: this._rowHasChanged}); + this.state.dataSource = ds.cloneWithRows( + groupByEveryN(this.state.assets, this.props.imagesPerRow) + ); + }, + + componentDidMount: function() { + this.fetch(); + }, + + componentWillReceiveProps: function(nextProps) { + if (this.props.groupTypes !== nextProps.groupTypes) { + this.fetch(true); + } + }, + + _fetch: function(clear) { + if (clear) { + this.setState(this.getInitialState(), this.fetch); + return; + } + + var fetchParams = { + first: this.props.batchSize, + groupTypes: this.props.groupTypes, + }; + if (this.state.lastCursor) { + fetchParams.after = this.state.lastCursor; + } + + CameraRoll.getPhotos(fetchParams, this._appendAssets, logError); + }, + + /** + * Fetches more images from the camera roll. If clear is set to true, it will + * set the component to its initial state and re-fetch the images. + */ + fetch: function(clear) { + if (!this.state.loadingMore) { + this.setState({loadingMore: true}, () => { this._fetch(clear); }); + } + }, + + render: function() { + return ( + + ); + }, + + _rowHasChanged: function(r1, r2) { + if (r1.length !== r2.length) { + return true; + } + + for (var i = 0; i < r1.length; i++) { + if (r1[i] !== r2[i]) { + return true; + } + } + + return false; + }, + + _renderFooterSpinner: function() { + if (!this.state.noMore) { + return ; + } + return null; + }, + + // rowData is an array of images + _renderRow: function(rowData, sectionID, rowID) { + var images = rowData.map((image) => { + if (image === null) { + return null; + } + return this.props.renderImage(image); + }); + + return ( + + {images} + + ); + }, + + _appendAssets: function(data) { + var assets = data.edges; + var newState = { loadingMore: false }; + + if (!data.page_info.has_next_page) { + newState.noMore = true; + } + + if (assets.length > 0) { + newState.lastCursor = data.page_info.end_cursor; + newState.assets = this.state.assets.concat(assets); + newState.dataSource = this.state.dataSource.cloneWithRows( + groupByEveryN(newState.assets, this.props.imagesPerRow) + ); + } + + this.setState(newState); + }, + + _onEndReached: function() { + if (!this.state.noMore) { + this.fetch(); + } + }, +}); + +var styles = StyleSheet.create({ + row: { + flexDirection: 'row', + flex: 1, + }, + url: { + fontSize: 9, + marginBottom: 14, + }, + image: { + margin: 4, + }, + info: { + flex: 1, + }, + container: { + flex: 1, + }, +}); + +module.exports = CameraRollView; diff --git a/Examples/UIExplorer/GeoLocationExample.js b/Examples/UIExplorer/GeolocationExample.js similarity index 97% rename from Examples/UIExplorer/GeoLocationExample.js rename to Examples/UIExplorer/GeolocationExample.js index 561808149..fac3dd205 100644 --- a/Examples/UIExplorer/GeoLocationExample.js +++ b/Examples/UIExplorer/GeolocationExample.js @@ -1,7 +1,7 @@ /** * Copyright 2004-present Facebook. All Rights Reserved. * - * @providesModule GeoLocationExample + * @providesModule GeolocationExample */ /* eslint no-console: 0 */ 'use strict'; diff --git a/Examples/UIExplorer/MapViewExample.js b/Examples/UIExplorer/MapViewExample.js new file mode 100644 index 000000000..2094624be --- /dev/null +++ b/Examples/UIExplorer/MapViewExample.js @@ -0,0 +1,196 @@ +/** + * Copyright 2004-present Facebook. All Rights Reserved. + * + * @providesModule MapViewExample + */ +'use strict'; + +var React = require('react-native'); +var StyleSheet = require('StyleSheet'); +var { + MapView, + Text, + TextInput, + View, +} = React; + +var MapRegionInput = React.createClass({ + + propTypes: { + region: React.PropTypes.shape({ + latitude: React.PropTypes.number, + longitude: React.PropTypes.number, + latitudeDelta: React.PropTypes.number, + longitudeDelta: React.PropTypes.number, + }), + onChange: React.PropTypes.func.isRequired, + }, + + getInitialState: function() { + return { + latitude: 0, + longitude: 0, + latitudeDelta: 0, + longitudeDelta: 0, + }; + }, + + componentWillReceiveProps: function(nextProps) { + this.setState(nextProps.region); + }, + + render: function() { + var region = this.state; + return ( + + + + {'Latitude'} + + + + + + {'Longitude'} + + + + + + {'Latitude delta'} + + + + + + {'Longitude delta'} + + + + + + {'Change'} + + + + ); + }, + + _onChangeLatitude: function(e) { + this.setState({latitude: parseFloat(e.nativeEvent.text)}); + }, + + _onChangeLongitude: function(e) { + this.setState({longitude: parseFloat(e.nativeEvent.text)}); + }, + + _onChangeLatitudeDelta: function(e) { + this.setState({latitudeDelta: parseFloat(e.nativeEvent.text)}); + }, + + _onChangeLongitudeDelta: function(e) { + this.setState({longitudeDelta: parseFloat(e.nativeEvent.text)}); + }, + + _change: function() { + this.props.onChange(this.state); + }, + +}); + +var MapViewExample = React.createClass({ + + getInitialState() { + return { + mapRegion: null, + mapRegionInput: null, + }; + }, + + render() { + return ( + + + + + ); + }, + + _onRegionChanged(region) { + this.setState({mapRegionInput: region}); + }, + + _onRegionInputChanged(region) { + this.setState({ + mapRegion: region, + mapRegionInput: region, + }); + }, + +}); + +var styles = StyleSheet.create({ + map: { + height: 150, + margin: 10, + borderWidth: 1, + borderColor: '#000000', + }, + row: { + flexDirection: 'row', + justifyContent: 'space-between', + }, + textInput: { + width: 150, + height: 20, + borderWidth: 0.5, + borderColor: '#aaaaaa', + fontSize: 13, + padding: 4, + }, + changeButton: { + alignSelf: 'center', + marginTop: 5, + padding: 3, + borderWidth: 0.5, + borderColor: '#777777', + }, +}); + +exports.title = ''; +exports.description = 'Base component to display maps'; +exports.examples = [ + { + title: 'Map', + render() { return ; } + }, + { + title: 'Map shows user location', + render() { + return ; + } + } +]; diff --git a/Examples/UIExplorer/PickerExample.js b/Examples/UIExplorer/PickerExample.js new file mode 100644 index 000000000..3a77fa74d --- /dev/null +++ b/Examples/UIExplorer/PickerExample.js @@ -0,0 +1,113 @@ +/** + * Copyright 2004-present Facebook. All Rights Reserved. + * + * @providesModule PickerExample + */ +'use strict'; + +var React = require('react-native'); +var { + PickerIOS, + Text, + View, +} = React; + +var PickerItemIOS = PickerIOS.Item; + +var CAR_MAKES_AND_MODELS = { + amc: { + name: 'AMC', + models: ['AMX', 'Concord', 'Eagle', 'Gremlin', 'Matador', 'Pacer'], + }, + alfa: { + name: 'Alfa-Romeo', + models: ['159', '4C', 'Alfasud', 'Brera', 'GTV6', 'Giulia', 'MiTo', 'Spider'], + }, + aston: { + name: 'Aston Martin', + models: ['DB5', 'DB9', 'DBS', 'Rapide', 'Vanquish', 'Vantage'], + }, + audi: { + name: 'Audi', + models: ['90', '4000', '5000', 'A3', 'A4', 'A5', 'A6', 'A7', 'A8', 'Q5', 'Q7'], + }, + austin: { + name: 'Austin', + models: ['America', 'Maestro', 'Maxi', 'Mini', 'Montego', 'Princess'], + }, + borgward: { + name: 'Borgward', + models: ['Hansa', 'Isabella', 'P100'], + }, + buick: { + name: 'Buick', + models: ['Electra', 'LaCrosse', 'LeSabre', 'Park Avenue', 'Regal', + 'Roadmaster', 'Skylark'], + }, + cadillac: { + name: 'Cadillac', + models: ['Catera', 'Cimarron', 'Eldorado', 'Fleetwood', 'Sedan de Ville'], + }, + chevrolet: { + name: 'Chevrolet', + models: ['Astro', 'Aveo', 'Bel Air', 'Captiva', 'Cavalier', 'Chevelle', + 'Corvair', 'Corvette', 'Cruze', 'Nova', 'SS', 'Vega', 'Volt'], + }, +}; + +var PickerExample = React.createClass({ + getInitialState: function() { + return { + carMake: 'cadillac', + modelIndex: 3, + }; + }, + + render: function() { + var make = CAR_MAKES_AND_MODELS[this.state.carMake]; + var selectionString = make.name + ' ' + make.models[this.state.modelIndex]; + return ( + + Please choose a make for your car: + this.setState({carMake, modelIndex: 0})}> + {Object.keys(CAR_MAKES_AND_MODELS).map((carMake) => ( + + ) + )} + + Please choose a model of {make.name}: + this.setState({modelIndex})}> + {CAR_MAKES_AND_MODELS[this.state.carMake].models.map( + (modelName, modelIndex) => ( + + )) + } + + You selected: {selectionString} + + ); + }, +}); + +exports.title = ''; +exports.description = 'Render lists of selectable options with UIPickerView.'; +exports.examples = [ +{ + title: '', + render: function() { + return ; + }, +}]; diff --git a/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj b/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj index ea13d28e3..d9a201502 100644 --- a/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj +++ b/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 004D28A31AAF61C70097A701 /* UIExplorerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 004D28A21AAF61C70097A701 /* UIExplorerTests.m */; }; 13417FE91AA91432003F314A /* libRCTImage.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 13417FE81AA91428003F314A /* libRCTImage.a */; }; 134180011AA9153C003F314A /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 13417FEF1AA914B8003F314A /* libRCTText.a */; }; 134180021AA9153C003F314A /* libReactKit.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 13417FFF1AA91531003F314A /* libReactKit.a */; }; @@ -16,9 +17,17 @@ 13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB11A68108700A75B9A /* LaunchScreen.xib */; }; 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; + 832C81C71AAF73C5007FA2F7 /* libRCTAdSupport.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 832C81A61AAF6EFF007FA2F7 /* libRCTAdSupport.a */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + 004D28A41AAF61C70097A701 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 13B07F861A680F5B00A75B9A; + remoteInfo = UIExplorer; + }; 13417FE71AA91428003F314A /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 13417FE31AA91428003F314A /* RCTImage.xcodeproj */; @@ -54,9 +63,19 @@ remoteGlobalIDString = 134814201AA4EA6300B7C361; remoteInfo = RCTGeolocation; }; + 832C81A51AAF6EFF007FA2F7 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 832C81A11AAF6EFE007FA2F7 /* RCTAdSupport.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 832C81801AAF6DEF007FA2F7; + remoteInfo = RCTAdSupport; + }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 004D289E1AAF61C70097A701 /* UIExplorerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = UIExplorerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 004D28A11AAF61C70097A701 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 004D28A21AAF61C70097A701 /* UIExplorerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = UIExplorerTests.m; sourceTree = ""; }; 13417FE31AA91428003F314A /* RCTImage.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTImage.xcodeproj; path = ../../Libraries/Image/RCTImage.xcodeproj; sourceTree = ""; }; 13417FEA1AA914B8003F314A /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = ../../Libraries/Text/RCTText.xcodeproj; sourceTree = ""; }; 13417FFA1AA91531003F314A /* ReactKit.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ReactKit.xcodeproj; path = ../../ReactKit/ReactKit.xcodeproj; sourceTree = ""; }; @@ -69,13 +88,22 @@ 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 832C81A11AAF6EFE007FA2F7 /* RCTAdSupport.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTAdSupport.xcodeproj; path = ../../Libraries/AdSupport/RCTAdSupport.xcodeproj; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 004D289B1AAF61C70097A701 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 13B07F8C1A680F5B00A75B9A /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 832C81C71AAF73C5007FA2F7 /* libRCTAdSupport.a in Frameworks */, 134A8A2A1AACED7A00945AAE /* libRCTGeolocation.a in Frameworks */, 1341802C1AA9178B003F314A /* libRCTNetwork.a in Frameworks */, 134180011AA9153C003F314A /* libRCTText.a in Frameworks */, @@ -87,14 +115,32 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 004D289F1AAF61C70097A701 /* UIExplorerTests */ = { + isa = PBXGroup; + children = ( + 004D28A21AAF61C70097A701 /* UIExplorerTests.m */, + 004D28A01AAF61C70097A701 /* Supporting Files */, + ); + path = UIExplorerTests; + sourceTree = ""; + }; + 004D28A01AAF61C70097A701 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 004D28A11AAF61C70097A701 /* Info.plist */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; 1316A21D1AA397F400C0188E /* Libraries */ = { isa = PBXGroup; children = ( - 134A8A201AACED6A00945AAE /* RCTGeolocation.xcodeproj */, 13417FFA1AA91531003F314A /* ReactKit.xcodeproj */, + 832C81A11AAF6EFE007FA2F7 /* RCTAdSupport.xcodeproj */, + 134A8A201AACED6A00945AAE /* RCTGeolocation.xcodeproj */, + 13417FE31AA91428003F314A /* RCTImage.xcodeproj */, 134180261AA91779003F314A /* RCTNetwork.xcodeproj */, 13417FEA1AA914B8003F314A /* RCTText.xcodeproj */, - 13417FE31AA91428003F314A /* RCTImage.xcodeproj */, ); name = Libraries; sourceTree = ""; @@ -152,11 +198,20 @@ name = UIExplorer; sourceTree = ""; }; + 832C81A21AAF6EFE007FA2F7 /* Products */ = { + isa = PBXGroup; + children = ( + 832C81A61AAF6EFF007FA2F7 /* libRCTAdSupport.a */, + ); + name = Products; + sourceTree = ""; + }; 83CBB9F61A601CBA00E9B192 = { isa = PBXGroup; children = ( 13B07FAE1A68108700A75B9A /* UIExplorer */, 1316A21D1AA397F400C0188E /* Libraries */, + 004D289F1AAF61C70097A701 /* UIExplorerTests */, 83CBBA001A601CBA00E9B192 /* Products */, ); sourceTree = ""; @@ -165,6 +220,7 @@ isa = PBXGroup; children = ( 13B07F961A680F5B00A75B9A /* UIExplorer.app */, + 004D289E1AAF61C70097A701 /* UIExplorerTests.xctest */, ); name = Products; sourceTree = ""; @@ -172,6 +228,24 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 004D289D1AAF61C70097A701 /* UIExplorerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 004D28AD1AAF61C70097A701 /* Build configuration list for PBXNativeTarget "UIExplorerTests" */; + buildPhases = ( + 004D289A1AAF61C70097A701 /* Sources */, + 004D289B1AAF61C70097A701 /* Frameworks */, + 004D289C1AAF61C70097A701 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 004D28A51AAF61C70097A701 /* PBXTargetDependency */, + ); + name = UIExplorerTests; + productName = UIExplorerTests; + productReference = 004D289E1AAF61C70097A701 /* UIExplorerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 13B07F861A680F5B00A75B9A /* UIExplorer */ = { isa = PBXNativeTarget; buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "UIExplorer" */; @@ -197,6 +271,12 @@ attributes = { LastUpgradeCheck = 0610; ORGANIZATIONNAME = Facebook; + TargetAttributes = { + 004D289D1AAF61C70097A701 = { + CreatedOnToolsVersion = 6.1.1; + TestTargetID = 13B07F861A680F5B00A75B9A; + }; + }; }; buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "UIExplorer" */; compatibilityVersion = "Xcode 3.2"; @@ -210,6 +290,10 @@ productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */; projectDirPath = ""; projectReferences = ( + { + ProductGroup = 832C81A21AAF6EFE007FA2F7 /* Products */; + ProjectRef = 832C81A11AAF6EFE007FA2F7 /* RCTAdSupport.xcodeproj */; + }, { ProductGroup = 134A8A211AACED6A00945AAE /* Products */; ProjectRef = 134A8A201AACED6A00945AAE /* RCTGeolocation.xcodeproj */; @@ -234,6 +318,7 @@ projectRoot = ""; targets = ( 13B07F861A680F5B00A75B9A /* UIExplorer */, + 004D289D1AAF61C70097A701 /* UIExplorerTests */, ); }; /* End PBXProject section */ @@ -274,9 +359,23 @@ remoteRef = 134A8A241AACED6A00945AAE /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; + 832C81A61AAF6EFF007FA2F7 /* libRCTAdSupport.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTAdSupport.a; + remoteRef = 832C81A51AAF6EFF007FA2F7 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; /* End PBXReferenceProxy section */ /* Begin PBXResourcesBuildPhase section */ + 004D289C1AAF61C70097A701 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 13B07F8E1A680F5B00A75B9A /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -289,6 +388,14 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 004D289A1AAF61C70097A701 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 004D28A31AAF61C70097A701 /* UIExplorerTests.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 13B07F871A680F5B00A75B9A /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -300,6 +407,14 @@ }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 004D28A51AAF61C70097A701 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 13B07F861A680F5B00A75B9A /* UIExplorer */; + targetProxy = 004D28A41AAF61C70097A701 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin PBXVariantGroup section */ 13B07FB11A68108700A75B9A /* LaunchScreen.xib */ = { isa = PBXVariantGroup; @@ -312,6 +427,42 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + 004D28A61AAF61C70097A701 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + FRAMEWORK_SEARCH_PATHS = ( + "$(SDKROOT)/Developer/Library/Frameworks", + "$(inherited)", + ); + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + INFOPLIST_FILE = UIExplorerTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 8.1; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/UIExplorer.app/UIExplorer"; + }; + name = Debug; + }; + 004D28A71AAF61C70097A701 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + FRAMEWORK_SEARCH_PATHS = ( + "$(SDKROOT)/Developer/Library/Frameworks", + "$(inherited)", + ); + INFOPLIST_FILE = UIExplorerTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 8.1; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/UIExplorer.app/UIExplorer"; + }; + name = Release; + }; 13B07F941A680F5B00A75B9A /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -435,6 +586,15 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 004D28AD1AAF61C70097A701 /* Build configuration list for PBXNativeTarget "UIExplorerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 004D28A61AAF61C70097A701 /* Debug */, + 004D28A71AAF61C70097A701 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "UIExplorer" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/Examples/UIExplorer/UIExplorer.xcodeproj/xcshareddata/xcschemes/UIExplorer.xcscheme b/Examples/UIExplorer/UIExplorer.xcodeproj/xcshareddata/xcschemes/UIExplorer.xcscheme index 403b11e6f..2189d2d0e 100644 --- a/Examples/UIExplorer/UIExplorer.xcodeproj/xcshareddata/xcschemes/UIExplorer.xcscheme +++ b/Examples/UIExplorer/UIExplorer.xcodeproj/xcshareddata/xcschemes/UIExplorer.xcscheme @@ -20,6 +20,20 @@ ReferencedContainer = "container:UIExplorer.xcodeproj"> + + + + + + + + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + com.facebook.$(PRODUCT_NAME:rfc1034identifier) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + + diff --git a/Examples/UIExplorer/UIExplorerTests/UIExplorerTests.m b/Examples/UIExplorer/UIExplorerTests/UIExplorerTests.m new file mode 100644 index 000000000..1a7a70d78 --- /dev/null +++ b/Examples/UIExplorer/UIExplorerTests/UIExplorerTests.m @@ -0,0 +1,52 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#import +#import + +#define TIMEOUT_SECONDS 30 + +@interface UIExplorerTests : XCTestCase + +@end + +@implementation UIExplorerTests + +- (BOOL)findSubviewInView:(UIView *)view matching:(BOOL(^)(UIView *view))test +{ + if (test(view)) { + return YES; + } + for (UIView *subview in [view subviews]) { + if ([self findSubviewInView:subview matching:test]) { + return YES; + } + } + return NO; +} + +- (void)testRootViewLoadsAndRenders { + UIViewController *vc = [[[[UIApplication sharedApplication] delegate] window] rootViewController]; + + NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS]; + BOOL foundElement = NO; + + while ([date timeIntervalSinceNow] > 0 && !foundElement) { + [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:date]; + [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:date]; + + foundElement = [self findSubviewInView:vc.view matching:^BOOL(UIView *view) { + if ([view respondsToSelector:@selector(attributedText)]) { + NSString *text = [(id)view attributedText].string; + if ([text isEqualToString:@""]) { + return YES; + } + } + return NO; + }]; + } + + XCTAssertTrue(foundElement, @"Cound't find element with '' text in %d seconds", TIMEOUT_SECONDS); +} + + +@end diff --git a/Libraries/AdSupport/AdSupportIOS.js b/Libraries/AdSupport/AdSupportIOS.js new file mode 100644 index 000000000..598befd31 --- /dev/null +++ b/Libraries/AdSupport/AdSupportIOS.js @@ -0,0 +1,14 @@ +/** + * Copyright 2004-present Facebook. All Rights Reserved. + * + * @providesModule AdSupportIOS + */ +'use strict'; + +var AdSupport = require('NativeModules').RCTAdSupport; + +module.exports = { + getAdvertisingId: function(onSuccess, onFailure) { + AdSupport.getAdvertisingId(onSuccess, onFailure); + }, +}; diff --git a/Libraries/AdSupport/RCTAdSupport.h b/Libraries/AdSupport/RCTAdSupport.h new file mode 100644 index 000000000..2fbd3a8c0 --- /dev/null +++ b/Libraries/AdSupport/RCTAdSupport.h @@ -0,0 +1,7 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#import "RCTBridgeModule.h" + +@interface RCTAdSupport : NSObject + +@end diff --git a/Libraries/AdSupport/RCTAdSupport.m b/Libraries/AdSupport/RCTAdSupport.m new file mode 100644 index 000000000..0cff24012 --- /dev/null +++ b/Libraries/AdSupport/RCTAdSupport.m @@ -0,0 +1,20 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#import "RCTAdSupport.h" + +#import + +@implementation RCTAdSupport + +- (void)getAdvertisingId:(RCTResponseSenderBlock)callback withErrorCallback:(RCTResponseSenderBlock)errorCallback +{ + RCT_EXPORT(); + + if ([ASIdentifierManager class]) { + callback(@[[[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString]]); + } else { + return errorCallback(@[@"as_identifier_unavailable"]); + } +} + +@end diff --git a/Libraries/AdSupport/RCTAdSupport.xcodeproj/project.pbxproj b/Libraries/AdSupport/RCTAdSupport.xcodeproj/project.pbxproj new file mode 100644 index 000000000..92576cd45 --- /dev/null +++ b/Libraries/AdSupport/RCTAdSupport.xcodeproj/project.pbxproj @@ -0,0 +1,248 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 832C819C1AAF6E1A007FA2F7 /* RCTAdSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 832C819B1AAF6E1A007FA2F7 /* RCTAdSupport.m */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 832C817E1AAF6DEF007FA2F7 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = "include/$(PRODUCT_NAME)"; + dstSubfolderSpec = 16; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 832C81801AAF6DEF007FA2F7 /* libRCTAdSupport.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTAdSupport.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 832C819A1AAF6E1A007FA2F7 /* RCTAdSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTAdSupport.h; sourceTree = ""; }; + 832C819B1AAF6E1A007FA2F7 /* RCTAdSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTAdSupport.m; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 832C817D1AAF6DEF007FA2F7 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 832C81771AAF6DEF007FA2F7 = { + isa = PBXGroup; + children = ( + 832C819A1AAF6E1A007FA2F7 /* RCTAdSupport.h */, + 832C819B1AAF6E1A007FA2F7 /* RCTAdSupport.m */, + 832C81811AAF6DEF007FA2F7 /* Products */, + ); + sourceTree = ""; + }; + 832C81811AAF6DEF007FA2F7 /* Products */ = { + isa = PBXGroup; + children = ( + 832C81801AAF6DEF007FA2F7 /* libRCTAdSupport.a */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 832C817F1AAF6DEF007FA2F7 /* RCTAdSupport */ = { + isa = PBXNativeTarget; + buildConfigurationList = 832C81941AAF6DF0007FA2F7 /* Build configuration list for PBXNativeTarget "RCTAdSupport" */; + buildPhases = ( + 832C817C1AAF6DEF007FA2F7 /* Sources */, + 832C817D1AAF6DEF007FA2F7 /* Frameworks */, + 832C817E1AAF6DEF007FA2F7 /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = RCTAdSupport; + productName = RCTAdSupport; + productReference = 832C81801AAF6DEF007FA2F7 /* libRCTAdSupport.a */; + productType = "com.apple.product-type.library.static"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 832C81781AAF6DEF007FA2F7 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0620; + ORGANIZATIONNAME = Facebook; + TargetAttributes = { + 832C817F1AAF6DEF007FA2F7 = { + CreatedOnToolsVersion = 6.2; + }; + }; + }; + buildConfigurationList = 832C817B1AAF6DEF007FA2F7 /* Build configuration list for PBXProject "RCTAdSupport" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = 832C81771AAF6DEF007FA2F7; + productRefGroup = 832C81811AAF6DEF007FA2F7 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 832C817F1AAF6DEF007FA2F7 /* RCTAdSupport */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXSourcesBuildPhase section */ + 832C817C1AAF6DEF007FA2F7 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 832C819C1AAF6E1A007FA2F7 /* RCTAdSupport.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 832C81921AAF6DF0007FA2F7 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "$(SRCROOT)/../../ReactKit/**", + ); + IPHONEOS_DEPLOYMENT_TARGET = 8.2; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + }; + name = Debug; + }; + 832C81931AAF6DF0007FA2F7 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "$(SRCROOT)/../../ReactKit/**", + ); + IPHONEOS_DEPLOYMENT_TARGET = 8.2; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 832C81951AAF6DF0007FA2F7 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + }; + name = Debug; + }; + 832C81961AAF6DF0007FA2F7 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 832C817B1AAF6DEF007FA2F7 /* Build configuration list for PBXProject "RCTAdSupport" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 832C81921AAF6DF0007FA2F7 /* Debug */, + 832C81931AAF6DF0007FA2F7 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 832C81941AAF6DF0007FA2F7 /* Build configuration list for PBXNativeTarget "RCTAdSupport" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 832C81951AAF6DF0007FA2F7 /* Debug */, + 832C81961AAF6DF0007FA2F7 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 832C81781AAF6DEF007FA2F7 /* Project object */; +} diff --git a/Libraries/Animation/Animation.js b/Libraries/Animation/Animation.js new file mode 100644 index 000000000..80029f148 --- /dev/null +++ b/Libraries/Animation/Animation.js @@ -0,0 +1,34 @@ +/** + * Copyright 2004-present Facebook. All Rights Reserved. + * + * @providesModule Animation + * @flow + */ +'use strict'; + +var { RCTAnimationManager } = require('NativeModules'); +var AnimationUtils = require('AnimationUtils'); + +type EasingFunction = (t: number) => number; + +var Animation = { + Mixin: require('AnimationMixin'), + + startAnimation: function( + node: any, + duration: number, + delay: number, + easing: (string | EasingFunction), + properties: {[key: string]: any} + ): number { + var nodeHandle = +node.getNodeHandle(); + var easingSample = AnimationUtils.evaluateEasingFunction(duration, easing); + RCTAnimationManager.startAnimation(nodeHandle, AnimationUtils.allocateTag(), duration, delay, easingSample, properties); + }, + + stopAnimation: function(tag) { + RCTAnimationManager.stopAnimation(tag); + }, +}; + +module.exports = Animation; diff --git a/Libraries/Animation/AnimationMixin.js b/Libraries/Animation/AnimationMixin.js new file mode 100644 index 000000000..ff29e2735 --- /dev/null +++ b/Libraries/Animation/AnimationMixin.js @@ -0,0 +1,45 @@ +/** + * Copyright 2004-present Facebook. All Rights Reserved. + * + * @providesModule AnimationMixin + * @flow + */ +'use strict'; + +var AnimationUtils = require('AnimationUtils'); +var { RCTAnimationManager } = require('NativeModules'); + +var invariant = require('invariant'); + +type EasingFunction = (t: number) => number; + +var AnimationMixin = { + getInitialState: function(): Object { + return {}; + }, + + startAnimation: function( + refKey: string, + duration: number, + delay: number, + easing: (string | EasingFunction), + properties: {[key: string]: any} + ): number { + var ref = this.refs[refKey]; + invariant( + ref, + 'Invalid refKey ' + refKey + '; ' + + 'valid refs: ' + JSON.stringify(Object.keys(this.refs)) + ); + + var nodeHandle = +ref.getNodeHandle(); + var easingSample = AnimationUtils.evaluateEasingFunction(duration, easing); + RCTAnimationManager.startAnimation(nodeHandle, AnimationUtils.allocateTag(), duration, delay, easingSample, properties); + }, + + stopAnimation: function(tag: number) { + RCTAnimationManager.stopAnimation(tag); + }, +}; + +module.exports = AnimationMixin; diff --git a/Libraries/Animation/AnimationUtils.js b/Libraries/Animation/AnimationUtils.js new file mode 100644 index 000000000..cf986abe8 --- /dev/null +++ b/Libraries/Animation/AnimationUtils.js @@ -0,0 +1,226 @@ +/** + * Copyright 2004-present Facebook. All Rights Reserved. + * + * @providesModule AnimationUtils + * @flow + */ +'use strict'; + +type EasingFunction = (t: number) => number; + +var b = 0, + c = 1, + d = 1; +var defaults = { + easeInQuad: function(t) { + return c * (t /= 1) * t + b; + }, + easeOutQuad: function(t) { + return -c * (t /= d) * (t - 2) + b; + }, + easeInOutQuad: function(t) { + if ((t /= d / 2) < 1) { + return c / 2 * t * t + b; + } + return -c / 2 * ((--t) * (t - 2) - 1) + b; + }, + easeInCubic: function(t) { + return c * (t /= d) * t * t + b; + }, + easeOutCubic: function(t) { + return c * ((t = t / d - 1) * t * t + 1) + b; + }, + easeInOutCubic: function(t) { + if ((t /= d / 2) < 1) { + return c / 2 * t * t * t + b; + } + return c / 2 * ((t -= 2) * t * t + 2) + b; + }, + easeInQuart: function(t) { + return c * (t /= d) * t * t * t + b; + }, + easeOutQuart: function(t) { + return -c * ((t = t / d - 1) * t * t * t - 1) + b; + }, + easeInOutQuart: function(t) { + if ((t /= d / 2) < 1) { + return c / 2 * t * t * t * t + b; + } + return -c / 2 * ((t -= 2) * t * t * t - 2) + b; + }, + easeInQuint: function(t) { + return c * (t /= d) * t * t * t * t + b; + }, + easeOutQuint: function(t) { + return c * ((t = t / d - 1) * t * t * t * t + 1) + b; + }, + easeInOutQuint: function(t) { + if ((t /= d / 2) < 1) { + return c / 2 * t * t * t * t * t + b; + } + return c / 2 * ((t -= 2) * t * t * t * t + 2) + b; + }, + easeInSine: function(t) { + return -c * Math.cos(t / d * (Math.PI / 2)) + c + b; + }, + easeOutSine: function(t) { + return c * Math.sin(t / d * (Math.PI / 2)) + b; + }, + easeInOutSine: function(t) { + return -c / 2 * (Math.cos(Math.PI * t / d) - 1) + b; + }, + easeInExpo: function(t) { + return (t === 0) ? b : c * Math.pow(2, 10 * (t / d - 1)) + b; + }, + easeOutExpo: function(t) { + return (t === d) ? b + c : c * (-Math.pow(2, -10 * t / d) + 1) + b; + }, + easeInOutExpo: function(t) { + if (t === 0) { + return b; + } + if (t === d) { + return b + c; + } + if ((t /= d / 2) < 1) { + return c / 2 * Math.pow(2, 10 * (t - 1)) + b; + } + return c / 2 * (-Math.pow(2, -10 * --t) + 2) + b; + }, + easeInCirc: function(t) { + return -c * (Math.sqrt(1 - (t /= d) * t) - 1) + b; + }, + easeOutCirc: function(t) { + return c * Math.sqrt(1 - (t = t / d - 1) * t) + b; + }, + easeInOutCirc: function(t) { + if ((t /= d / 2) < 1) { + return -c / 2 * (Math.sqrt(1 - t * t) - 1) + b; + } + return c / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1) + b; + }, + easeInElastic: function(t) { + var s = 1.70158; + var p = 0; + var a = c; + if (t === 0) { + return b; + } + if ((t /= d) === 1) { + return b + c; + } + if (!p) { + p = d * 0.3; + } + if (a < Math.abs(c)) { + a = c; + var s = p / 4; + } else { + var s = p / (2 * Math.PI) * Math.asin(c / a); + } + return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p)) + b; + }, + easeOutElastic: function(t) { + var s = 1.70158; + var p = 0; + var a = c; + if (t === 0) { + return b; + } + if ((t /= d) === 1) { + return b + c; + } + if (!p) { + p = d * 0.3; + } + if (a < Math.abs(c)) { + a = c; + var s = p / 4; + } else { + var s = p / (2 * Math.PI) * Math.asin(c / a); + } + return a * Math.pow(2, -10 * t) * Math.sin((t * d - s) * (2 * Math.PI) / p) + c + b; + }, + easeInOutElastic: function(t) { + var s = 1.70158; + var p = 0; + var a = c; + if (t === 0) { + return b; + } + if ((t /= d / 2) === 2) { + return b + c; + } + if (!p) { + p = d * (0.3 * 1.5); + } + if (a < Math.abs(c)) { + a = c; + var s = p / 4; + } else { + var s = p / (2 * Math.PI) * Math.asin(c / a); + } + if (t < 1) { + return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p)) + b; + } + return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p) * 0.5 + c + b; + }, + easeInBack: function(t) { + var s = 1.70158; + return c * (t /= d) * t * ((s + 1) * t - s) + b; + }, + easeOutBack: function(t) { + var s = 1.70158; + return c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b; + }, + easeInOutBack: function(t) { + var s = 1.70158; + if ((t /= d / 2) < 1) { + return c / 2 * (t * t * (((s *= (1.525)) + 1) * t - s)) + b; + } + return c / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2) + b; + }, + easeInBounce: function(t) { + return c - this.easeOutBounce(d - t) + b; + }, + easeOutBounce: function(t) { + if ((t /= d) < (1 / 2.75)) { + return c * (7.5625 * t * t) + b; + } else if (t < (2 / 2.75)) { + return c * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75) + b; + } else if (t < (2.5 / 2.75)) { + return c * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375) + b; + } else { + return c * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375) + b; + } + }, + easeInOutBounce: function(t) { + if (t < d / 2) { + return this.easeInBounce(t * 2) * 0.5 + b; + } + return this.easeOutBounce(t * 2 - d) * 0.5 + c * 0.5 + b; + }, +}; + +var ticksPerSecond = 60; +var lastUsedTag = 0; + +module.exports = { + allocateTag: function(): number { + return ++lastUsedTag; + }, + + evaluateEasingFunction: function(duration: number, easing: string | EasingFunction): Array { + if (typeof easing === 'string') { + easing = defaults[easing] || defaults.easeOutQuad; + } + + var tickCount = Math.round(duration * ticksPerSecond / 1000); + var sample = []; + for (var i = 0; i <= tickCount; i++) { + sample.push(easing(i / tickCount)); + } + + return sample; + }, +}; diff --git a/Libraries/AppRegistry/AppRegistry.js b/Libraries/AppRegistry/AppRegistry.js index a11f5d494..346bc302c 100644 --- a/Libraries/AppRegistry/AppRegistry.js +++ b/Libraries/AppRegistry/AppRegistry.js @@ -53,10 +53,12 @@ var AppRegistry = { runApplication: function(appKey, appParameters) { console.log( - 'Running application "' + appKey + '" with appParams: ', - appParameters + 'Running application "' + appKey + '" with appParams: ' + + JSON.stringify(appParameters) + '. ' + + '__DEV__ === ' + __DEV__ + + ', development-level warning are ' + (__DEV__ ? 'ON' : 'OFF') + + ', performance optimizations are ' + (__DEV__ ? 'OFF' : 'ON') ); - invariant( runnables[appKey] && runnables[appKey].run, 'Application ' + appKey + ' has not been registered.' diff --git a/Libraries/CameraRoll/CameraRoll.js b/Libraries/CameraRoll/CameraRoll.js new file mode 100644 index 000000000..54295fc52 --- /dev/null +++ b/Libraries/CameraRoll/CameraRoll.js @@ -0,0 +1,149 @@ +/** + * Copyright 2004-present Facebook. All Rights Reserved. + * + * @providesModule CameraRoll + */ +'use strict'; + +var ReactPropTypes = require('ReactPropTypes'); +var RKCameraRollManager = require('NativeModules').RKCameraRollManager; + +var createStrictShapeTypeChecker = require('createStrictShapeTypeChecker'); +var deepFreezeAndThrowOnMutationInDev = + require('deepFreezeAndThrowOnMutationInDev'); +var invariant = require('invariant'); + +var GROUP_TYPES_OPTIONS = [ + 'Album', + 'All', + 'Event', + 'Faces', + 'Library', + 'PhotoStream', + 'SavedPhotos', // default +]; + +deepFreezeAndThrowOnMutationInDev(GROUP_TYPES_OPTIONS); + +/** + * Shape of the param arg for the `getPhotos` function. + */ +var getPhotosParamChecker = createStrictShapeTypeChecker({ + /** + * The number of photos wanted in reverse order of the photo application + * (i.e. most recent first for SavedPhotos). + */ + first: ReactPropTypes.number.isRequired, + + /** + * A cursor that matches `page_info { end_cursor }` returned from a previous + * call to `getPhotos` + */ + after: ReactPropTypes.string, + + /** + * Specifies which group types to filter the results to. + */ + groupTypes: ReactPropTypes.oneOf(GROUP_TYPES_OPTIONS), + + /** + * Specifies filter on group names, like 'Recent Photos' or custom album + * titles. + */ + groupName: ReactPropTypes.string, +}); + +/** + * Shape of the return value of the `getPhotos` function. + */ +var getPhotosReturnChecker = createStrictShapeTypeChecker({ + edges: ReactPropTypes.arrayOf(createStrictShapeTypeChecker({ + node: createStrictShapeTypeChecker({ + type: ReactPropTypes.string.isRequired, + group_name: ReactPropTypes.string.isRequired, + image: createStrictShapeTypeChecker({ + uri: ReactPropTypes.string.isRequired, + height: ReactPropTypes.number.isRequired, + width: ReactPropTypes.number.isRequired, + isStored: ReactPropTypes.bool, + }).isRequired, + timestamp: ReactPropTypes.number.isRequired, + location: createStrictShapeTypeChecker({ + latitude: ReactPropTypes.number, + longitude: ReactPropTypes.number, + altitude: ReactPropTypes.number, + heading: ReactPropTypes.number, + speed: ReactPropTypes.number, + }), + }).isRequired, + })).isRequired, + page_info: createStrictShapeTypeChecker({ + has_next_page: ReactPropTypes.bool.isRequired, + start_cursor: ReactPropTypes.string, + end_cursor: ReactPropTypes.string, + }).isRequired, +}); + +class CameraRoll { + /** + * Saves the image with tag `tag` to the camera roll. + * + * @param {string} tag - Can be any of the three kinds of tags we accept: + * 1. URL + * 2. assets-library tag + * 3. tag returned from storing an image in memory + */ + static saveImageWithTag(tag, successCallback, errorCallback) { + invariant( + typeof tag === 'string', + 'CameraRoll.saveImageWithTag tag must be a valid string.' + ); + RKCameraRollManager.saveImageWithTag( + tag, + (imageTag) => { + successCallback && successCallback(imageTag); + }, + (errorMessage) => { + errorCallback && errorCallback(errorMessage); + }); + } + + /** + * Invokes `callback` with photo identifier objects from the local camera + * roll of the device matching shape defined by `getPhotosReturnChecker`. + * + * @param {object} params - See `getPhotosParamChecker`. + * @param {function} callback - Invoked with arg of shape defined by + * `getPhotosReturnChecker` on success. + * @param {function} errorCallback - Invoked with error message on error. + */ + static getPhotos(params, callback, errorCallback) { + var metaCallback = callback; + if (__DEV__) { + getPhotosParamChecker({params}, 'params', 'CameraRoll.getPhotos'); + invariant( + typeof callback === 'function', + 'CameraRoll.getPhotos callback must be a valid function.' + ); + invariant( + typeof errorCallback === 'function', + 'CameraRoll.getPhotos errorCallback must be a valid function.' + ); + } + if (__DEV__) { + metaCallback = (response) => { + getPhotosReturnChecker( + {response}, + 'response', + 'CameraRoll.getPhotos callback' + ); + callback(response); + }; + } + RKCameraRollManager.getPhotos(params, metaCallback, errorCallback); + } +} + +CameraRoll.GroupTypesOptions = GROUP_TYPES_OPTIONS; + +module.exports = CameraRoll; diff --git a/Libraries/Components/MapView/MapView.js b/Libraries/Components/MapView/MapView.js new file mode 100644 index 000000000..105a80a31 --- /dev/null +++ b/Libraries/Components/MapView/MapView.js @@ -0,0 +1,165 @@ +/** + * Copyright 2004-present Facebook. All Rights Reserved. + * + * @providesModule MapView + */ +'use strict'; + +var EdgeInsetsPropType = require('EdgeInsetsPropType'); +var NativeMethodsMixin = require('NativeMethodsMixin'); +var React = require('React'); +var ReactIOSViewAttributes = require('ReactIOSViewAttributes'); +var View = require('View'); + +var createReactIOSNativeComponentClass = require('createReactIOSNativeComponentClass'); +var deepDiffer = require('deepDiffer'); +var insetsDiffer = require('insetsDiffer'); +var merge = require('merge'); + +var MapView = React.createClass({ + mixins: [NativeMethodsMixin], + + propTypes: { + /** + * Used to style and layout the `MapView`. See `StyleSheet.js` and + * `ViewStylePropTypes.js` for more info. + */ + style: View.propTypes.style, + + /** + * If `true` the app will ask for the user's location and focus on it. + * Default value is `false`. + * + * **NOTE**: You need to add NSLocationWhenInUseUsageDescription key in + * Info.plist to enable geolocation, otherwise it is going + * to *fail silently*! + */ + showsUserLocation: React.PropTypes.bool, + + /** + * If `false` the user won't be able to pinch/zoom the map. + * Default `value` is true. + */ + zoomEnabled: React.PropTypes.bool, + + /** + * When this property is set to `true` and a valid camera is associated with + * the map, the camera’s heading angle is used to rotate the plane of the + * map around its center point. When this property is set to `false`, the + * camera’s heading angle is ignored and the map is always oriented so + * that true north is situated at the top of the map view + */ + rotateEnabled: React.PropTypes.bool, + + /** + * When this property is set to `true` and a valid camera is associated + * with the map, the camera’s pitch angle is used to tilt the plane + * of the map. When this property is set to `false`, the camera’s pitch + * angle is ignored and the map is always displayed as if the user + * is looking straight down onto it. + */ + pitchEnabled: React.PropTypes.bool, + + /** + * If `false` the user won't be able to change the map region being displayed. + * Default value is `true`. + */ + scrollEnabled: React.PropTypes.bool, + + /** + * The region to be displayed by the map. + * + * The region is defined by the center coordinates and the span of + * coordinates to display. + */ + region: React.PropTypes.shape({ + /** + * Coordinates for the center of the map. + */ + latitude: React.PropTypes.number.isRequired, + longitude: React.PropTypes.number.isRequired, + + /** + * Distance between the minimun and the maximum latitude/longitude + * to be displayed. + */ + latitudeDelta: React.PropTypes.number.isRequired, + longitudeDelta: React.PropTypes.number.isRequired, + }), + + /** + * Maximum size of area that can be displayed. + */ + maxDelta: React.PropTypes.number, + + /** + * Minimum size of area that can be displayed. + */ + minDelta: React.PropTypes.number, + + /** + * Insets for the map's legal label, originally at bottom left of the map. + * See `EdgeInsetsPropType.js` for more information. + */ + legalLabelInsets: EdgeInsetsPropType, + + /** + * Callback that is called continuously when the user is dragging the map. + */ + onRegionChange: React.PropTypes.func, + + /** + * Callback that is called once, when the user is done moving the map. + */ + onRegionChangeComplete: React.PropTypes.func, + }, + + _onChange: function(event) { + if (event.nativeEvent.continuous) { + this.props.onRegionChange && + this.props.onRegionChange(event.nativeEvent.region); + } else { + this.props.onRegionChangeComplete && + this.props.onRegionChangeComplete(event.nativeEvent.region); + } + }, + + render: function() { + return ( + + ); + }, + +}); + +var RKMap = createReactIOSNativeComponentClass({ + validAttributes: merge( + ReactIOSViewAttributes.UIView, { + showsUserLocation: true, + zoomEnabled: true, + rotateEnabled: true, + pitchEnabled: true, + scrollEnabled: true, + region: {diff: deepDiffer}, + maxDelta: true, + minDelta: true, + legalLabelInsets: {diff: insetsDiffer}, + } + ), + uiViewClassName: 'RCTMap', +}); + +module.exports = MapView; diff --git a/Libraries/Components/Touchable/TouchableBounce.js b/Libraries/Components/Touchable/TouchableBounce.js new file mode 100644 index 000000000..2eb052fd8 --- /dev/null +++ b/Libraries/Components/Touchable/TouchableBounce.js @@ -0,0 +1,124 @@ +/** + * Copyright 2004-present Facebook. All Rights Reserved. + * + * @providesModule TouchableBounce + */ +'use strict'; + +var NativeMethodsMixin = require('NativeMethodsMixin'); +var React = require('React'); +var POPAnimation = require('POPAnimation'); +var Animation = require('Animation'); +var Touchable = require('Touchable'); + +var merge = require('merge'); +var copyProperties = require('copyProperties'); +var onlyChild = require('onlyChild'); + +/** + * When the scroll view is disabled, this defines how far your touch may move + * off of the button, before deactivating the button. Once deactivated, try + * moving it back and you'll see that the button is once again reactivated! + * Move it back and forth several times while the scroll view is disabled. + */ +var PRESS_RECT_OFFSET = {top: 20, left: 20, right: 20, bottom: 30}; +/** + * Example of using the `TouchableMixin` to play well with other responder + * locking views including `ScrollView`. `TouchableMixin` provides touchable + * hooks (`this.touchableHandle*`) that we forward events to. In turn, + * `TouchableMixin` expects us to implement some abstract methods to handle + * interesting interactions such as `handleTouchablePress`. + */ +var TouchableBounce = React.createClass({ + mixins: [Touchable.Mixin, NativeMethodsMixin], + + propTypes: { + onPress: React.PropTypes.func, + // The function passed takes a callback to start the animation which should + // be run after this onPress handler is done. You can use this (for example) + // to update UI before starting the animation. + onPressWithCompletion: React.PropTypes.func, + // the function passed is called after the animation is complete + onPressAnimationComplete: React.PropTypes.func, + }, + + getInitialState: function() { + return merge(this.touchableGetInitialState(), {animationID: null}); + }, + + bounceTo: function(value, velocity, bounciness, fromValue, callback) { + if (POPAnimation) { + this.state.animationID && this.removeAnimation(this.state.animationID); + var anim = { + property: POPAnimation.Properties.scaleXY, + dynamicsTension: 0, + toValue: [value, value], + velocity: [velocity, velocity], + springBounciness: bounciness, + }; + if (fromValue) { + anim.fromValue = [fromValue, fromValue]; + } + this.state.animationID = POPAnimation.createSpringAnimation(anim); + this.addAnimation(this.state.animationID, callback); + } else { + Animation.startAnimation(this, 300, 0, 'easeOutBack', {scaleXY: [value, value]}); + if (fromValue && typeof fromValue === 'function') { + callback = fromValue; + } + if (callback) { + setTimeout(callback, 300); + } + } + }, + + /** + * `Touchable.Mixin` self callbacks. The mixin will invoke these if they are + * defined on your component. + */ + touchableHandleActivePressIn: function() { + this.bounceTo(0.93, 0.1, 0); + }, + + touchableHandleActivePressOut: function() { + this.bounceTo(1, 0.4, 0); + }, + + touchableHandlePress: function() { + if (this.props.onPressWithCompletion) { + this.props.onPressWithCompletion( + this.bounceTo.bind(this, 1, 10, 10, 0.93, this.props.onPressAnimationComplete) + ); + return; + } + + this.bounceTo(1, 10, 10, undefined, this.props.onPressAnimationComplete); + this.props.onPress && this.props.onPress(); + }, + + touchableGetPressRectOffset: function() { + return PRESS_RECT_OFFSET; // Always make sure to predeclare a constant! + }, + + touchableGetHighlightDelayMS: function() { + return 0; + }, + + render: function() { + // Note(vjeux): use cloneWithProps once React has been upgraded + var child = onlyChild(this.props.children); + copyProperties(child.props, { + accessible: true, + testID: this.props.testID, + onStartShouldSetResponder: this.touchableHandleStartShouldSetResponder, + onResponderTerminationRequest: this.touchableHandleResponderTerminationRequest, + onResponderGrant: this.touchableHandleResponderGrant, + onResponderMove: this.touchableHandleResponderMove, + onResponderRelease: this.touchableHandleResponderRelease, + onResponderTerminate: this.touchableHandleResponderTerminate + }); + return child; + } +}); + +module.exports = TouchableBounce; diff --git a/Libraries/GeoLocation/Geolocation.ios.js b/Libraries/Geolocation/Geolocation.ios.js similarity index 100% rename from Libraries/GeoLocation/Geolocation.ios.js rename to Libraries/Geolocation/Geolocation.ios.js diff --git a/Libraries/GeoLocation/RCTGeolocation.xcodeproj/project.pbxproj b/Libraries/Geolocation/RCTGeolocation.xcodeproj/project.pbxproj similarity index 100% rename from Libraries/GeoLocation/RCTGeolocation.xcodeproj/project.pbxproj rename to Libraries/Geolocation/RCTGeolocation.xcodeproj/project.pbxproj diff --git a/Libraries/GeoLocation/RCTLocationObserver.h b/Libraries/Geolocation/RCTLocationObserver.h similarity index 100% rename from Libraries/GeoLocation/RCTLocationObserver.h rename to Libraries/Geolocation/RCTLocationObserver.h diff --git a/Libraries/GeoLocation/RCTLocationObserver.m b/Libraries/Geolocation/RCTLocationObserver.m similarity index 100% rename from Libraries/GeoLocation/RCTLocationObserver.m rename to Libraries/Geolocation/RCTLocationObserver.m diff --git a/Libraries/Image/RCTCameraRollManager.h b/Libraries/Image/RCTCameraRollManager.h new file mode 100644 index 000000000..4a957d6a2 --- /dev/null +++ b/Libraries/Image/RCTCameraRollManager.h @@ -0,0 +1,7 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#import "RCTBridgeModule.h" + +@interface RCTCameraRollManager : NSObject + +@end diff --git a/Libraries/Image/RCTCameraRollManager.m b/Libraries/Image/RCTCameraRollManager.m new file mode 100644 index 000000000..9f86ffb69 --- /dev/null +++ b/Libraries/Image/RCTCameraRollManager.m @@ -0,0 +1,148 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#import "RCTCameraRollManager.h" + +#import +#import +#import +#import + + #import "RCTImageLoader.h" +#import "RCTLog.h" + +@implementation RCTCameraRollManager + +- (void)saveImageWithTag:(NSString *)imageTag successCallback:(RCTResponseSenderBlock)successCallback errorCallback:(RCTResponseSenderBlock)errorCallback +{ + RCT_EXPORT(); + + [RCTImageLoader loadImageWithTag:imageTag callback:^(NSError *loadError, UIImage *loadedImage) { + if (loadError) { + errorCallback(@[[loadError localizedDescription]]); + return; + } + [[RCTImageLoader assetsLibrary] writeImageToSavedPhotosAlbum:[loadedImage CGImage] metadata:nil completionBlock:^(NSURL *assetURL, NSError *saveError) { + if (saveError) { + NSString *errorMessage = [NSString stringWithFormat:@"Error saving cropped image: %@", saveError]; + RCTLogWarn(@"%@", errorMessage); + errorCallback(@[errorMessage]); + return; + } + successCallback(@[[assetURL absoluteString]]); + }]; + }]; +} + +- (void)callCallback:(RCTResponseSenderBlock)callback withAssets:(NSArray *)assets hasNextPage:(BOOL)hasNextPage +{ + if (![assets count]) { + callback(@[@{ + @"edges": assets, + @"page_info": @{ + @"has_next_page": @NO} + }]); + return; + } + callback(@[@{ + @"edges": assets, + @"page_info": @{ + @"start_cursor": assets[0][@"node"][@"image"][@"uri"], + @"end_cursor": assets[assets.count - 1][@"node"][@"image"][@"uri"], + @"has_next_page": @(hasNextPage)} + }]); +} + +- (void)getPhotos:(NSDictionary *)params callback:(RCTResponseSenderBlock)callback errorCallback:(RCTResponseSenderBlock)errorCallback +{ + RCT_EXPORT(); + + NSUInteger first = [params[@"first"] integerValue]; + NSString *afterCursor = params[@"after"]; + NSString *groupTypesStr = params[@"groupTypes"]; + NSString *groupName = params[@"groupName"]; + ALAssetsGroupType groupTypes; + if ([groupTypesStr isEqualToString:@"Album"]) { + groupTypes = ALAssetsGroupAlbum; + } else if ([groupTypesStr isEqualToString:@"All"]) { + groupTypes = ALAssetsGroupAll; + } else if ([groupTypesStr isEqualToString:@"Event"]) { + groupTypes = ALAssetsGroupEvent; + } else if ([groupTypesStr isEqualToString:@"Faces"]) { + groupTypes = ALAssetsGroupFaces; + } else if ([groupTypesStr isEqualToString:@"Library"]) { + groupTypes = ALAssetsGroupLibrary; + } else if ([groupTypesStr isEqualToString:@"PhotoStream"]) { + groupTypes = ALAssetsGroupPhotoStream; + } else { + groupTypes = ALAssetsGroupSavedPhotos; + } + + BOOL __block foundAfter = NO; + BOOL __block hasNextPage = NO; + BOOL __block calledCallback = NO; + NSMutableArray *assets = [[NSMutableArray alloc] init]; + + [[RCTImageLoader assetsLibrary] enumerateGroupsWithTypes:groupTypes usingBlock:^(ALAssetsGroup *group, BOOL *stopGroups) { + if (group && (groupName == nil || [groupName isEqualToString:[group valueForProperty:ALAssetsGroupPropertyName]])) { + [group setAssetsFilter:ALAssetsFilter.allPhotos]; + [group enumerateAssetsWithOptions:NSEnumerationReverse usingBlock:^(ALAsset *result, NSUInteger index, BOOL *stopAssets) { + if (result) { + NSString *uri = [(NSURL *)[result valueForProperty:ALAssetPropertyAssetURL] absoluteString]; + if (afterCursor && !foundAfter) { + if ([afterCursor isEqualToString:uri]) { + foundAfter = YES; + } + return; // Skip until we get to the first one + } + if (first == [assets count]) { + *stopAssets = YES; + *stopGroups = YES; + hasNextPage = YES; + RCTAssert(calledCallback == NO, @"Called the callback before we finished processing the results."); + [self callCallback:callback withAssets:assets hasNextPage:hasNextPage]; + calledCallback = YES; + return; + } + CGSize dimensions = [result defaultRepresentation].dimensions; + CLLocation *loc = [result valueForProperty:ALAssetPropertyLocation]; + NSDate *date = [result valueForProperty:ALAssetPropertyDate]; + [assets addObject:@{ + @"node": @{ + @"type": [result valueForProperty:ALAssetPropertyType], + @"group_name": [group valueForProperty:ALAssetsGroupPropertyName], + @"image": @{ + @"uri": uri, + @"height": @(dimensions.height), + @"width": @(dimensions.width), + @"isStored": @YES, + }, + @"timestamp": @([date timeIntervalSince1970]), + @"location": loc ? + @{ + @"latitude": @(loc.coordinate.latitude), + @"longitude": @(loc.coordinate.longitude), + @"altitude": @(loc.altitude), + @"heading": @(loc.course), + @"speed": @(loc.speed), + } : @{}, + } + }]; + } + }]; + } else { + // Sometimes the enumeration continues even if we set stop above, so we guard against calling the callback + // multiple times here. + if (!calledCallback) { + [self callCallback:callback withAssets:assets hasNextPage:hasNextPage]; + calledCallback = YES; + } + } + } failureBlock:^(NSError *error) { + if (error.code != ALAssetsLibraryAccessUserDeniedError) { + RCTLogError(@"Failure while iterating through asset groups %@", error); + } + errorCallback(@[error.description]); + }]; +} + +@end diff --git a/Libraries/Image/RCTImage.xcodeproj/project.pbxproj b/Libraries/Image/RCTImage.xcodeproj/project.pbxproj index 409d61d32..dea9cb419 100644 --- a/Libraries/Image/RCTImage.xcodeproj/project.pbxproj +++ b/Libraries/Image/RCTImage.xcodeproj/project.pbxproj @@ -10,6 +10,8 @@ 1304D5AB1AA8C4A30002E2BE /* RCTStaticImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 1304D5A81AA8C4A30002E2BE /* RCTStaticImage.m */; }; 1304D5AC1AA8C4A30002E2BE /* RCTStaticImageManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 1304D5AA1AA8C4A30002E2BE /* RCTStaticImageManager.m */; }; 1304D5B21AA8C50D0002E2BE /* RCTGIFImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 1304D5B11AA8C50D0002E2BE /* RCTGIFImage.m */; }; + 143879351AAD238D00F088A5 /* RCTCameraRollManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 143879341AAD238D00F088A5 /* RCTCameraRollManager.m */; }; + 143879381AAD32A300F088A5 /* RCTImageLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 143879371AAD32A300F088A5 /* RCTImageLoader.m */; }; 58B5118F1A9E6BD600147676 /* RCTImageDownloader.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B5118A1A9E6BD600147676 /* RCTImageDownloader.m */; }; 58B511901A9E6BD600147676 /* RCTNetworkImageView.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B5118C1A9E6BD600147676 /* RCTNetworkImageView.m */; }; 58B511911A9E6BD600147676 /* RCTNetworkImageViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B5118E1A9E6BD600147676 /* RCTNetworkImageViewManager.m */; }; @@ -34,6 +36,10 @@ 1304D5AA1AA8C4A30002E2BE /* RCTStaticImageManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTStaticImageManager.m; sourceTree = ""; }; 1304D5B01AA8C50D0002E2BE /* RCTGIFImage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTGIFImage.h; sourceTree = ""; }; 1304D5B11AA8C50D0002E2BE /* RCTGIFImage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTGIFImage.m; sourceTree = ""; }; + 143879331AAD238D00F088A5 /* RCTCameraRollManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTCameraRollManager.h; sourceTree = ""; }; + 143879341AAD238D00F088A5 /* RCTCameraRollManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTCameraRollManager.m; sourceTree = ""; }; + 143879361AAD32A300F088A5 /* RCTImageLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageLoader.h; sourceTree = ""; }; + 143879371AAD32A300F088A5 /* RCTImageLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageLoader.m; sourceTree = ""; }; 58B5115D1A9E6B3D00147676 /* libRCTImage.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTImage.a; sourceTree = BUILT_PRODUCTS_DIR; }; 58B511891A9E6BD600147676 /* RCTImageDownloader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageDownloader.h; sourceTree = ""; }; 58B5118A1A9E6BD600147676 /* RCTImageDownloader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageDownloader.m; sourceTree = ""; }; @@ -57,6 +63,10 @@ 58B511541A9E6B3D00147676 = { isa = PBXGroup; children = ( + 143879361AAD32A300F088A5 /* RCTImageLoader.h */, + 143879371AAD32A300F088A5 /* RCTImageLoader.m */, + 143879331AAD238D00F088A5 /* RCTCameraRollManager.h */, + 143879341AAD238D00F088A5 /* RCTCameraRollManager.m */, 1304D5B01AA8C50D0002E2BE /* RCTGIFImage.h */, 1304D5B11AA8C50D0002E2BE /* RCTGIFImage.m */, 58B511891A9E6BD600147676 /* RCTImageDownloader.h */, @@ -142,6 +152,8 @@ 1304D5AC1AA8C4A30002E2BE /* RCTStaticImageManager.m in Sources */, 58B511901A9E6BD600147676 /* RCTNetworkImageView.m in Sources */, 1304D5B21AA8C50D0002E2BE /* RCTGIFImage.m in Sources */, + 143879351AAD238D00F088A5 /* RCTCameraRollManager.m in Sources */, + 143879381AAD32A300F088A5 /* RCTImageLoader.m in Sources */, 1304D5AB1AA8C4A30002E2BE /* RCTStaticImage.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Libraries/Image/RCTImageLoader.h b/Libraries/Image/RCTImageLoader.h new file mode 100644 index 000000000..3554f4b46 --- /dev/null +++ b/Libraries/Image/RCTImageLoader.h @@ -0,0 +1,13 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#import + +@class ALAssetsLibrary; +@class UIImage; + +@interface RCTImageLoader : NSObject + ++ (ALAssetsLibrary *)assetsLibrary; ++ (void)loadImageWithTag:(NSString *)tag callback:(void (^)(NSError *error, UIImage *image))callback; + +@end diff --git a/Libraries/Image/RCTImageLoader.m b/Libraries/Image/RCTImageLoader.m new file mode 100644 index 000000000..ec3e1dda2 --- /dev/null +++ b/Libraries/Image/RCTImageLoader.m @@ -0,0 +1,98 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#import "RCTImageLoader.h" + +#import +#import +#import +#import +#import + +#import "RCTConvert.h" +#import "RCTImageDownloader.h" +#import "RCTLog.h" + +NSError *errorWithMessage(NSString *message) { + NSDictionary *errorInfo = @{NSLocalizedDescriptionKey: message}; + NSError *error = [[NSError alloc] initWithDomain:RCTErrorDomain code:0 userInfo:errorInfo]; + return error; +} + +@implementation RCTImageLoader + ++ (ALAssetsLibrary *)assetsLibrary +{ + static ALAssetsLibrary *assetsLibrary = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + assetsLibrary = [[ALAssetsLibrary alloc] init]; + }); + return assetsLibrary; +} + ++ (void)loadImageWithTag:(NSString *)imageTag callback:(void (^)(NSError *error, UIImage *image))callback +{ + if ([imageTag hasPrefix:@"assets-library"]) { + [[RCTImageLoader assetsLibrary] assetForURL:[NSURL URLWithString:imageTag] resultBlock:^(ALAsset *asset) { + if (asset) { + ALAssetRepresentation *representation = [asset defaultRepresentation]; + ALAssetOrientation orientation = [representation orientation]; + UIImage *image = [UIImage imageWithCGImage:[representation fullResolutionImage] scale:1.0f orientation:(UIImageOrientation)orientation]; + callback(nil, image); + } else { + NSString *errorText = [NSString stringWithFormat:@"Failed to load asset at URL %@ with no error message.", imageTag]; + NSError *error = errorWithMessage(errorText); + callback(error, nil); + } + } failureBlock:^(NSError *loadError) { + NSString *errorText = [NSString stringWithFormat:@"Failed to load asset at URL %@.\niOS Error: %@", imageTag, loadError]; + NSError *error = errorWithMessage(errorText); + callback(error, nil); + }]; + } else if ([imageTag hasPrefix:@"ph://"]) { + // Using PhotoKit for iOS 8+ + // 'ph://' prefix is used by FBMediaKit to differentiate between assets-library. It is prepended to the local ID so that it + // is in the form of NSURL which is what assets-library is based on. + // This means if we use any FB standard photo picker, we will get this prefix =( + NSString *phAssetID = [imageTag substringFromIndex:[@"ph://" length]]; + PHFetchResult *results = [PHAsset fetchAssetsWithLocalIdentifiers:@[phAssetID] options:nil]; + if (results.count == 0) { + NSString *errorText = [NSString stringWithFormat:@"Failed to fetch PHAsset with local identifier %@ with no error message.", phAssetID]; + NSError *error = errorWithMessage(errorText); + callback(error, nil); + return; + } + + PHAsset *asset = [results firstObject]; + [[PHImageManager defaultManager] requestImageForAsset:asset targetSize:PHImageManagerMaximumSize contentMode:PHImageContentModeDefault options:nil resultHandler:^(UIImage *result, NSDictionary *info) { + if (result) { + callback(nil, result); + } else { + NSString *errorText = [NSString stringWithFormat:@"Failed to load PHAsset with local identifier %@ with no error message.", phAssetID]; + NSError *error = errorWithMessage(errorText); + callback(error, nil); + return; + } + }]; + } else if ([imageTag hasPrefix:@"http"]) { + NSURL *url = [NSURL URLWithString:imageTag]; + if (!url) { + NSString *errorMessage = [NSString stringWithFormat:@"Invalid URL: %@", imageTag]; + callback(errorWithMessage(errorMessage), nil); + return; + } + [[RCTImageDownloader sharedInstance] downloadDataForURL:url block:^(NSData *data, NSError *error) { + if (error) { + callback(error, nil); + } else { + callback(nil, [UIImage imageWithData:data]); + } + }]; + } else { + NSString *errorMessage = [NSString stringWithFormat:@"Unrecognized tag protocol: %@", imageTag]; + NSError *error = errorWithMessage(errorMessage); + callback(error, nil); + } +} + +@end diff --git a/Libraries/Image/RCTStaticImage.m b/Libraries/Image/RCTStaticImage.m index b57b763ed..e8378fc72 100644 --- a/Libraries/Image/RCTStaticImage.m +++ b/Libraries/Image/RCTStaticImage.m @@ -24,7 +24,7 @@ // Apply trilinear filtering to smooth out mis-sized images self.layer.minificationFilter = kCAFilterTrilinear; self.layer.magnificationFilter = kCAFilterTrilinear; - + super.image = image; } diff --git a/Libraries/Image/RCTStaticImageManager.m b/Libraries/Image/RCTStaticImageManager.m index b83d8c42b..ef60247f2 100644 --- a/Libraries/Image/RCTStaticImageManager.m +++ b/Libraries/Image/RCTStaticImageManager.m @@ -6,6 +6,7 @@ #import "RCTConvert.h" #import "RCTGIFImage.h" +#import "RCTImageLoader.h" #import "RCTStaticImage.h" @implementation RCTStaticImageManager @@ -39,5 +40,19 @@ RCT_CUSTOM_VIEW_PROPERTY(tintColor, RCTStaticImage *) view.tintColor = defaultView.tintColor; } } +RCT_CUSTOM_VIEW_PROPERTY(imageTag, RCTStaticImage *) +{ + if (json) { + [RCTImageLoader loadImageWithTag:[RCTConvert NSString:json] callback:^(NSError *error, UIImage *image) { + if (error) { + RCTLogWarn(@"%@", error.localizedDescription); + } else { + view.image = image; + } + }]; + } else { + view.image = defaultView.image; + } +} @end diff --git a/Libraries/Picker/PickerIOS.android.js b/Libraries/Picker/PickerIOS.android.js new file mode 100644 index 000000000..72cc87a75 --- /dev/null +++ b/Libraries/Picker/PickerIOS.android.js @@ -0,0 +1,10 @@ +/** + * Copyright 2004-present Facebook. All Rights Reserved. + * + * @providesModule PickerIOS + * + * This is a controlled component version of RKPickerIOS + */ +'use strict'; + +module.exports = require('UnimplementedView'); diff --git a/Libraries/Picker/PickerIOS.ios.js b/Libraries/Picker/PickerIOS.ios.js new file mode 100644 index 000000000..04559a700 --- /dev/null +++ b/Libraries/Picker/PickerIOS.ios.js @@ -0,0 +1,120 @@ +/** + * Copyright 2004-present Facebook. All Rights Reserved. + * + * @providesModule PickerIOS + * + * This is a controlled component version of RKPickerIOS + */ +'use strict'; + +var NativeMethodsMixin = require('NativeMethodsMixin'); +var React = require('React'); +var ReactChildren = require('ReactChildren'); +var ReactIOSViewAttributes = require('ReactIOSViewAttributes'); +var RKPickerIOSConsts = require('NativeModules').RKUIManager.RCTPicker.Constants; +var StyleSheet = require('StyleSheet'); +var View = require('View'); + +var createReactIOSNativeComponentClass = + require('createReactIOSNativeComponentClass'); +var merge = require('merge'); + +var PICKER = 'picker'; + +var PickerIOS = React.createClass({ + mixins: [NativeMethodsMixin], + + propTypes: { + onValueChange: React.PropTypes.func, + selectedValue: React.PropTypes.any, // string or integer basically + }, + + getInitialState: function() { + return this._stateFromProps(this.props); + }, + + componentWillReceiveProps: function(nextProps) { + this.setState(this._stateFromProps(nextProps)); + }, + + // Translate PickerIOS prop and children into stuff that RKPickerIOS understands. + _stateFromProps: function(props) { + var selectedIndex = 0; + var items = []; + ReactChildren.forEach(props.children, function (child, index) { + if (child.props.value === props.selectedValue) { + selectedIndex = index; + } + items.push({value: child.props.value, label: child.props.label}); + }); + return {selectedIndex, items}; + }, + + render: function() { + return ( + + + + ); + }, + + _onChange: function(event) { + if (this.props.onChange) { + this.props.onChange(event); + } + if (this.props.onValueChange) { + this.props.onValueChange(event.nativeEvent.newValue); + } + + // The picker is a controlled component. This means we expect the + // on*Change handlers to be in charge of updating our + // `selectedValue` prop. That way they can also + // disallow/undo/mutate the selection of certain values. In other + // words, the embedder of this component should be the source of + // truth, not the native component. + if (this.state.selectedIndex !== event.nativeEvent.newIndex) { + this.refs[PICKER].setNativeProps({ + selectedIndex: this.state.selectedIndex + }); + } + }, +}); + +PickerIOS.Item = React.createClass({ + propTypes: { + value: React.PropTypes.any, // string or integer basically + label: React.PropTypes.string, + }, + + render: function() { + // These items don't get rendered directly. + return null; + }, +}); + +var styles = StyleSheet.create({ + rkPickerIOS: { + // The picker will conform to whatever width is given, but we do + // have to set the component's height explicitly on the + // surrounding view to ensure it gets rendered. + height: RKPickerIOSConsts.ComponentHeight, + }, +}); + +var rkPickerIOSAttributes = merge(ReactIOSViewAttributes.UIView, { + items: true, + selectedIndex: true, +}); + +var RKPickerIOS = createReactIOSNativeComponentClass({ + validAttributes: rkPickerIOSAttributes, + uiViewClassName: 'RCTPicker', +}); + +module.exports = PickerIOS; diff --git a/Libraries/ReactIOS/ReactIOS.js b/Libraries/ReactIOS/ReactIOS.js index 4b4b19db0..2dfcb2604 100644 --- a/Libraries/ReactIOS/ReactIOS.js +++ b/Libraries/ReactIOS/ReactIOS.js @@ -74,6 +74,7 @@ var ReactIOS = { count: ReactChildren.count, only: onlyChild }, + Component: ReactComponent, PropTypes: ReactPropTypes, createClass: ReactClass.createClass, createElement: createElement, diff --git a/Libraries/ReactIOS/renderApplication.ios.js b/Libraries/ReactIOS/renderApplication.js similarity index 100% rename from Libraries/ReactIOS/renderApplication.ios.js rename to Libraries/ReactIOS/renderApplication.js diff --git a/Libraries/Utilities/groupByEveryN.js b/Libraries/Utilities/groupByEveryN.js new file mode 100644 index 000000000..e85e58ed0 --- /dev/null +++ b/Libraries/Utilities/groupByEveryN.js @@ -0,0 +1,46 @@ +/** + * Copyright 2004-present Facebook. All Rights Reserved. + * + * @providesModule groupByEveryN + */ + +/** + * Useful method to split an array into groups of the same number of elements. + * You can use it to generate grids, rows, pages... + * + * If the input length is not a multiple of the count, it'll fill the last + * array with null so you can display a placeholder. + * + * Example: + * groupByEveryN([1, 2, 3, 4, 5], 3) + * => [[1, 2, 3], [4, 5, null]] + * + * groupByEveryN([1, 2, 3], 2).map(elems => { + * return {elems.map(elem => {elem})}; + * }) + */ +'use strict'; + +function groupByEveryN(array, n) { + var result = []; + var temp = []; + + for (var i = 0; i < array.length; ++i) { + if (i > 0 && i % n === 0) { + result.push(temp); + temp = []; + } + temp.push(array[i]); + } + + if (temp.length > 0) { + while (temp.length !== n) { + temp.push(null); + } + result.push(temp); + } + + return result; +} + +module.exports = groupByEveryN; diff --git a/Libraries/react-native/react-native.js b/Libraries/react-native/react-native.js index 7f7edaf72..4d4eee73d 100644 --- a/Libraries/react-native/react-native.js +++ b/Libraries/react-native/react-native.js @@ -7,14 +7,18 @@ var ReactNative = { ...require('React'), + Animation: require('Animation'), AppRegistry: require('AppRegistry'), + CameraRoll: require('CameraRoll'), DatePickerIOS: require('DatePickerIOS'), ExpandingText: require('ExpandingText'), + MapView: require('MapView'), Image: require('Image'), LayoutAnimation: require('LayoutAnimation'), ListView: require('ListView'), ListViewDataSource: require('ListViewDataSource'), NavigatorIOS: require('NavigatorIOS'), + PickerIOS: require('PickerIOS'), PixelRatio: require('PixelRatio'), ScrollView: require('ScrollView'), ActivityIndicatorIOS: require('ActivityIndicatorIOS'), diff --git a/ReactKit/Base/RCTAssert.h b/ReactKit/Base/RCTAssert.h index 66f670430..0aaf176f0 100644 --- a/ReactKit/Base/RCTAssert.h +++ b/ReactKit/Base/RCTAssert.h @@ -4,8 +4,8 @@ #define RCTErrorDomain @"RCTErrorDomain" -#define RCTAssert(condition, message, ...) _RCTAssert(condition, message, ##__VA_ARGS__) -#define RCTCAssert(condition, message, ...) _RCTCAssert(condition, message, ##__VA_ARGS__) +#define RCTAssert(condition, message, ...) _RCTAssert((condition) != 0, message, ##__VA_ARGS__) +#define RCTCAssert(condition, message, ...) _RCTCAssert((condition) != 0, message, ##__VA_ARGS__) typedef void (^RCTAssertFunction)(BOOL condition, NSString *message, ...); diff --git a/ReactKit/Base/RCTConvert.h b/ReactKit/Base/RCTConvert.h index 16cd6967b..709410f17 100644 --- a/ReactKit/Base/RCTConvert.h +++ b/ReactKit/Base/RCTConvert.h @@ -88,6 +88,12 @@ BOOL RCTSetProperty(id target, NSString *keypath, id json); */ BOOL RCTCopyProperty(id target, id source, NSString *keypath); +/** + * This function attempts to convert a JSON value to an object that can be used + * in KVC with the specific target and key path. + */ +id RCTConvertValue(id target, NSString *keypath, id json); + #ifdef __cplusplus } #endif diff --git a/ReactKit/Base/RCTConvert.m b/ReactKit/Base/RCTConvert.m index 760f8284d..a2dcea511 100644 --- a/ReactKit/Base/RCTConvert.m +++ b/ReactKit/Base/RCTConvert.m @@ -608,7 +608,7 @@ static NSString *RCTGuessTypeEncoding(id target, NSString *key, id value, NSStri return nil; } -static NSDictionary *RCTConvertValue(id value, NSString *encoding) +static id RCTConvertValueWithEncoding(id value, NSString *encoding) { static NSDictionary *converters = nil; static dispatch_once_t onceToken; @@ -690,18 +690,7 @@ static NSDictionary *RCTConvertValue(id value, NSString *encoding) return converter ? converter(value) : value; } -BOOL RCTSetProperty(id target, NSString *keypath, id value) -{ - // Split keypath - NSArray *parts = [keypath componentsSeparatedByString:@"."]; - NSString *key = [parts lastObject]; - for (NSUInteger i = 0; i < parts.count - 1; i++) { - target = [target valueForKey:parts[i]]; - if (!target) { - return NO; - } - } - +static NSString *RCTPropertyEncoding(id target, NSString *key, id value) { // Check target class for property definition NSString *encoding = nil; objc_property_t property = class_getProperty([target class], [key UTF8String]); @@ -720,7 +709,7 @@ BOOL RCTSetProperty(id target, NSString *keypath, id value) [key substringFromIndex:1]]); if (![target respondsToSelector:setter]) { - return NO; + return nil; } // Get type of first method argument @@ -730,17 +719,92 @@ BOOL RCTSetProperty(id target, NSString *keypath, id value) encoding = @(typeEncoding); free(typeEncoding); } + + if (encoding.length == 0 || [encoding isEqualToString:@(@encode(id))]) { + // Not enough info about the type encoding to be useful, so + // try to guess the type from the value and property name + encoding = RCTGuessTypeEncoding(target, key, value, encoding); + } + } - if (encoding.length == 0 || [encoding isEqualToString:@(@encode(id))]) { - // Not enough info about the type encoding to be useful, so - // try to guess the type from the value and property name - encoding = RCTGuessTypeEncoding(target, key, value, encoding); + return encoding; +} + +static id RCTConvertValueWithExplicitEncoding(id target, NSString *key, id json, NSString *encoding) { + if (!encoding) return nil; + + // Special case for numeric encodings, which may be enums + if ([json isKindOfClass:[NSString class]] && + [@"iIsSlLqQ" rangeOfString:[encoding substringToIndex:1]].length) { + + /** + * NOTE: the property names below may seem weird, but it's + * because they are tested as case-sensitive suffixes, so + * "apitalizationType" will match any of the following + * + * - capitalizationType + * - autocapitalizationType + * - autoCapitalizationType + * - titleCapitalizationType + * - etc. + */ + static NSDictionary *converters = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + converters = + @{ + @"apitalizationType": ^(id val) { + return [RCTConvert UITextAutocapitalizationType:val]; + }, + @"eyboardType": ^(id val) { + return [RCTConvert UIKeyboardType:val]; + }, + @"extAlignment": ^(id val) { + return [RCTConvert NSTextAlignment:val]; + }, + @"ointerEvents": ^(id val) { + return [RCTConvert RCTPointerEvents:val]; + }, + }; + }); + for (NSString *subkey in converters) { + if ([key hasSuffix:subkey]) { + NSInteger (^converter)(NSString *) = converters[subkey]; + json = @(converter(json)); + break; + } + } } + return RCTConvertValueWithEncoding(json, encoding); +} + +id RCTConvertValue(id target, NSString *key, id json) { + NSString *encoding = RCTPropertyEncoding(target, key, json); + return RCTConvertValueWithExplicitEncoding(target, key, json, encoding); +} + +BOOL RCTSetProperty(id target, NSString *keypath, id value) +{ + // Split keypath + NSArray *parts = [keypath componentsSeparatedByString:@"."]; + NSString *key = [parts lastObject]; + for (NSUInteger i = 0; i < parts.count - 1; i++) { + target = [target valueForKey:parts[i]]; + if (!target) { + return NO; + } + } + + NSString *encoding = RCTPropertyEncoding(target, key, value); + if (!encoding) return NO; + + value = RCTConvertValueWithExplicitEncoding(target, keypath, value, encoding); + // Special case for numeric encodings, which may be enums if ([value isKindOfClass:[NSString class]] && - [@"iIsSlLqQ" rangeOfString:[encoding substringToIndex:1]].length) { + [@"iIsSlLqQ" rangeOfString:[encoding substringToIndex:1]].location != NSNotFound) { /** * NOTE: the property names below may seem weird, but it's @@ -798,15 +862,15 @@ BOOL RCTSetProperty(id target, NSString *keypath, id value) }); void (^block)(UITextField *f, NSInteger v) = specialCases[key]; - if (block) - { + if (block) { block(target, [value integerValue]); return YES; } } // Set converted value - [target setValue:RCTConvertValue(value, encoding) forKey:key]; + [target setValue:value forKey:key]; + return YES; } diff --git a/ReactKit/Base/RCTUtils.m b/ReactKit/Base/RCTUtils.m index 1b686008f..40007a69b 100644 --- a/ReactKit/Base/RCTUtils.m +++ b/ReactKit/Base/RCTUtils.m @@ -2,11 +2,15 @@ #import "RCTUtils.h" -#import #import #import + #import +#import + +#import "RCTLog.h" + NSString *RCTJSONStringify(id jsonObject, NSError **error) { NSData *jsonData = [NSJSONSerialization dataWithJSONObject:jsonObject options:0 error:error]; @@ -15,7 +19,14 @@ NSString *RCTJSONStringify(id jsonObject, NSError **error) id RCTJSONParse(NSString *jsonString, NSError **error) { - NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding]; + if (!jsonString) { + return nil; + } + NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:NO]; + if (!jsonData) { + RCTLog(@"RCTJSONParse received the following string, which could not be losslessly converted to UTF8 data: '%@'", jsonString); + jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES]; + } return [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingAllowFragments error:error]; } @@ -63,7 +74,7 @@ CGSize RCTScreenSize() size = [UIScreen mainScreen].bounds.size; } }); - + return size; } @@ -93,7 +104,7 @@ NSTimeInterval RCTTGetAbsoluteTime(void) int ret = mach_timebase_info(&tb_info); assert(0 == ret); }); - + uint64_t timeInNanoseconds = (mach_absolute_time() * tb_info.numer) / tb_info.denom; return ((NSTimeInterval)timeInNanoseconds) / 1000000; } @@ -103,11 +114,11 @@ void RCTSwapClassMethods(Class cls, SEL original, SEL replacement) Method originalMethod = class_getClassMethod(cls, original); IMP originalImplementation = method_getImplementation(originalMethod); const char *originalArgTypes = method_getTypeEncoding(originalMethod); - + Method replacementMethod = class_getClassMethod(cls, replacement); IMP replacementImplementation = method_getImplementation(replacementMethod); const char *replacementArgTypes = method_getTypeEncoding(replacementMethod); - + if (class_addMethod(cls, original, replacementImplementation, replacementArgTypes)) { class_replaceMethod(cls, replacement, originalImplementation, originalArgTypes); @@ -123,11 +134,11 @@ void RCTSwapInstanceMethods(Class cls, SEL original, SEL replacement) Method originalMethod = class_getInstanceMethod(cls, original); IMP originalImplementation = method_getImplementation(originalMethod); const char *originalArgTypes = method_getTypeEncoding(originalMethod); - + Method replacementMethod = class_getInstanceMethod(cls, replacement); IMP replacementImplementation = method_getImplementation(replacementMethod); const char *replacementArgTypes = method_getTypeEncoding(replacementMethod); - + if (class_addMethod(cls, original, replacementImplementation, replacementArgTypes)) { class_replaceMethod(cls, replacement, originalImplementation, originalArgTypes); diff --git a/ReactKit/Modules/RCTAnimationManager.h b/ReactKit/Modules/RCTAnimationManager.h new file mode 100644 index 000000000..9f2391514 --- /dev/null +++ b/ReactKit/Modules/RCTAnimationManager.h @@ -0,0 +1,9 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#import + +#import "RCTBridgeModule.h" + +@interface RCTAnimationManager : NSObject + +@end diff --git a/ReactKit/Modules/RCTAnimationManager.m b/ReactKit/Modules/RCTAnimationManager.m new file mode 100644 index 000000000..a5de9aa29 --- /dev/null +++ b/ReactKit/Modules/RCTAnimationManager.m @@ -0,0 +1,203 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#import "RCTAnimationManager.h" + +#import +#import + +#import "RCTSparseArray.h" +#import "RCTUIManager.h" + +#if CGFLOAT_IS_DOUBLE + #define CG_APPEND(PREFIX, SUFFIX_F, SUFFIX_D) PREFIX##SUFFIX_D +#else + #define CG_APPEND(PREFIX, SUFFIX_F, SUFFIX_D) PREFIX##SUFFIX_F +#endif + +@implementation RCTAnimationManager +{ + RCTSparseArray *_animationRegistry; // Main thread only; animation tag -> view tag +} + +@synthesize bridge = _bridge; + +- (instancetype)init +{ + if ((self = [super init])) { + _animationRegistry = [[RCTSparseArray alloc] init]; + } + + return self; +} + +- (id (^)(CGFloat))interpolateFrom:(CGFloat[])fromArray to:(CGFloat[])toArray count:(NSUInteger)count typeName:(const char *)typeName +{ + if (count == 1) { + CGFloat from = *fromArray, to = *toArray, delta = to - from; + return ^(CGFloat t) { + return @(from + t * delta); + }; + } + + CG_APPEND(vDSP_vsub,,D)(fromArray, 1, toArray, 1, toArray, 1, count); + + const size_t size = count * sizeof(CGFloat); + NSData *deltaData = [NSData dataWithBytes:toArray length:size]; + NSData *fromData = [NSData dataWithBytes:fromArray length:size]; + + return ^(CGFloat t) { + const CGFloat *delta = deltaData.bytes; + const CGFloat *fromArray = fromData.bytes; + + CGFloat value[count]; + CG_APPEND(vDSP_vma,,D)(delta, 1, &t, 0, fromArray, 1, value, 1, count); + return [NSValue valueWithBytes:value objCType:typeName]; + }; +} + +- (void)startAnimationForTag:(NSNumber *)reactTag animationTag:(NSNumber *)animationTag duration:(double)duration delay:(double)delay easingSample:(NSArray *)easingSample properties:(NSDictionary *)properties +{ + RCT_EXPORT(startAnimation); + + __weak RCTAnimationManager *weakSelf = self; + [_bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) { + RCTAnimationManager *strongSelf = weakSelf; + + UIView *view = viewRegistry[reactTag]; + if (!view) { + RCTLogWarn(@"React tag %@ is not registered with the view registry", reactTag); + return; + } + + [properties enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, BOOL *stop) { + NSValue *toValue = nil; + if ([key isEqualToString:@"scaleXY"]) { + key = @"transform.scale"; + toValue = obj[0]; + } else if ([obj respondsToSelector:@selector(count)]) { + switch ([obj count]) { + case 2: + if ([obj respondsToSelector:@selector(objectForKey:)] && [obj objectForKey:@"w"]) { + toValue = [NSValue valueWithCGSize:[RCTConvert CGSize:obj]]; + } else { + toValue = [NSValue valueWithCGPoint:[RCTConvert CGPoint:obj]]; + } + break; + case 4: + toValue = [NSValue valueWithCGRect:[RCTConvert CGRect:obj]]; + break; + case 16: + toValue = [NSValue valueWithCGAffineTransform:[RCTConvert CGAffineTransform:obj]]; + break; + } + } + + if (!toValue) toValue = obj; + + const char *typeName = toValue.objCType; + + size_t count; + switch (typeName[0]) { + case 'i': + case 'I': + case 's': + case 'S': + case 'l': + case 'L': + case 'q': + case 'Q': + count = 1; + break; + + default: { + NSUInteger size; + NSGetSizeAndAlignment(typeName, &size, NULL); + count = size / sizeof(CGFloat); + break; + } + } + + CGFloat toFields[count]; + + switch (typeName[0]) { +#define CASE(encoding, type) \ + case encoding: { \ + type value; \ + [toValue getValue:&value]; \ + toFields[0] = value; \ + break; \ + } + + CASE('i', int) + CASE('I', unsigned int) + CASE('s', short) + CASE('S', unsigned short) + CASE('l', long) + CASE('L', unsigned long) + CASE('q', long long) + CASE('Q', unsigned long long) + +#undef CASE + + default: + [toValue getValue:toFields]; + break; + } + + NSValue *fromValue = [view.layer.presentationLayer valueForKeyPath:key]; + CGFloat fromFields[count]; + [fromValue getValue:fromFields]; + + id (^interpolationBlock)(CGFloat t) = [strongSelf interpolateFrom:fromFields to:toFields count:count typeName:typeName]; + + NSMutableArray *sampledValues = [NSMutableArray arrayWithCapacity:easingSample.count]; + for (NSNumber *sample in easingSample) { + CGFloat t = sample.CG_APPEND(, floatValue, doubleValue); + [sampledValues addObject:interpolationBlock(t)]; + } + + CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:key]; + animation.beginTime = CACurrentMediaTime() + delay / 1000.0; + animation.duration = duration / 1000.0; + animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; + animation.values = sampledValues; + + [view.layer setValue:toValue forKey:key]; + + NSString *animationKey = [NSString stringWithFormat:@"RCT.%@.%@", animationTag, key]; + [view.layer addAnimation:animation forKey:animationKey]; + }]; + + strongSelf->_animationRegistry[animationTag] = reactTag; + }]; +} + +- (void)stopAnimation:(NSNumber *)animationTag +{ + RCT_EXPORT(stopAnimation); + + __weak RCTAnimationManager *weakSelf = self; + [_bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) { + RCTAnimationManager *strongSelf = weakSelf; + + NSNumber *reactTag = strongSelf->_animationRegistry[animationTag]; + if (!reactTag) return; + + UIView *view = viewRegistry[reactTag]; + for (NSString *animationKey in view.layer.animationKeys) { + if ([animationKey hasPrefix:@"RCT"]) { + NSRange periodLocation = [animationKey rangeOfString:@"." options:0 range:NSMakeRange(3, animationKey.length - 3)]; + if (periodLocation.location != NSNotFound) { + NSInteger integerTag = [[animationKey substringWithRange:NSMakeRange(3, periodLocation.location)] integerValue]; + if (animationTag.integerValue == integerTag) { + [view.layer removeAnimationForKey:animationKey]; + } + } + } + } + + strongSelf->_animationRegistry[animationTag] = nil; + }]; +} + +@end diff --git a/ReactKit/Modules/RCTUIManager.m b/ReactKit/Modules/RCTUIManager.m index a22f24540..ae69890d3 100644 --- a/ReactKit/Modules/RCTUIManager.m +++ b/ReactKit/Modules/RCTUIManager.m @@ -2,24 +2,25 @@ #import "RCTUIManager.h" -#import #import +#import + #import "Layout.h" #import "RCTAnimationType.h" #import "RCTAssert.h" #import "RCTBridge.h" #import "RCTConvert.h" -#import "RCTRootView.h" #import "RCTLog.h" #import "RCTNavigator.h" +#import "RCTRootView.h" #import "RCTScrollableProtocol.h" #import "RCTShadowView.h" #import "RCTSparseArray.h" #import "RCTUtils.h" #import "RCTView.h" -#import "RCTViewNodeProtocol.h" #import "RCTViewManager.h" +#import "RCTViewNodeProtocol.h" #import "UIView+ReactKit.h" typedef void (^react_view_node_block_t)(id); diff --git a/ReactKit/ReactKit.xcodeproj/project.pbxproj b/ReactKit/ReactKit.xcodeproj/project.pbxproj index 48cac5ded..fc8e49ac3 100644 --- a/ReactKit/ReactKit.xcodeproj/project.pbxproj +++ b/ReactKit/ReactKit.xcodeproj/project.pbxproj @@ -34,13 +34,18 @@ 13E067561A70F44B002CDEE1 /* RCTViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E0674E1A70F44B002CDEE1 /* RCTViewManager.m */; }; 13E067571A70F44B002CDEE1 /* RCTView.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E067501A70F44B002CDEE1 /* RCTView.m */; }; 13E067591A70F44B002CDEE1 /* UIView+ReactKit.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E067541A70F44B002CDEE1 /* UIView+ReactKit.m */; }; - 58C571C11AA56C1900CDF9C8 /* RCTDatePickerManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 58C571BF1AA56C1900CDF9C8 /* RCTDatePickerManager.m */; }; 14F3620D1AABD06A001CE568 /* RCTSwitch.m in Sources */ = {isa = PBXBuildFile; fileRef = 14F362081AABD06A001CE568 /* RCTSwitch.m */; }; 14F3620E1AABD06A001CE568 /* RCTSwitchManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 14F3620A1AABD06A001CE568 /* RCTSwitchManager.m */; }; 14F484561AABFCE100FDF6B9 /* RCTSliderManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 14F484551AABFCE100FDF6B9 /* RCTSliderManager.m */; }; + 14435CE51AAC4AE100FC20F4 /* RCTMap.m in Sources */ = {isa = PBXBuildFile; fileRef = 14435CE21AAC4AE100FC20F4 /* RCTMap.m */; }; + 14435CE61AAC4AE100FC20F4 /* RCTMapManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 14435CE41AAC4AE100FC20F4 /* RCTMapManager.m */; }; + 58114A161AAE854800E7D092 /* RCTPicker.m in Sources */ = {isa = PBXBuildFile; fileRef = 58114A131AAE854800E7D092 /* RCTPicker.m */; }; + 58114A171AAE854800E7D092 /* RCTPickerManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 58114A151AAE854800E7D092 /* RCTPickerManager.m */; }; + 58C571C11AA56C1900CDF9C8 /* RCTDatePickerManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 58C571BF1AA56C1900CDF9C8 /* RCTDatePickerManager.m */; }; 830A229E1A66C68A008503DA /* RCTRootView.m in Sources */ = {isa = PBXBuildFile; fileRef = 830A229D1A66C68A008503DA /* RCTRootView.m */; }; 830BA4551A8E3BDA00D53203 /* RCTCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 830BA4541A8E3BDA00D53203 /* RCTCache.m */; }; 832348161A77A5AA00B55238 /* Layout.c in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FC71A68125100A75B9A /* Layout.c */; }; + 83C911101AAE6521001323A3 /* RCTAnimationManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 83C9110F1AAE6521001323A3 /* RCTAnimationManager.m */; }; 83CBBA511A601E3B00E9B192 /* RCTAssert.m in Sources */ = {isa = PBXBuildFile; fileRef = 83CBBA4B1A601E3B00E9B192 /* RCTAssert.m */; }; 83CBBA521A601E3B00E9B192 /* RCTLog.m in Sources */ = {isa = PBXBuildFile; fileRef = 83CBBA4E1A601E3B00E9B192 /* RCTLog.m */; }; 83CBBA531A601E3B00E9B192 /* RCTUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 83CBBA501A601E3B00E9B192 /* RCTUtils.m */; }; @@ -126,14 +131,22 @@ 13E067531A70F44B002CDEE1 /* UIView+ReactKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIView+ReactKit.h"; sourceTree = ""; }; 13E067541A70F44B002CDEE1 /* UIView+ReactKit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIView+ReactKit.m"; sourceTree = ""; }; 13EFFCCF1A98E6FE002607DC /* RCTJSMethodRegistrar.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTJSMethodRegistrar.h; sourceTree = ""; }; - 58C571BF1AA56C1900CDF9C8 /* RCTDatePickerManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTDatePickerManager.m; sourceTree = ""; }; - 58C571C01AA56C1900CDF9C8 /* RCTDatePickerManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTDatePickerManager.h; sourceTree = ""; }; 14F362071AABD06A001CE568 /* RCTSwitch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTSwitch.h; sourceTree = ""; }; 14F362081AABD06A001CE568 /* RCTSwitch.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTSwitch.m; sourceTree = ""; }; 14F362091AABD06A001CE568 /* RCTSwitchManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTSwitchManager.h; sourceTree = ""; }; 14F3620A1AABD06A001CE568 /* RCTSwitchManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTSwitchManager.m; sourceTree = ""; }; 14F484541AABFCE100FDF6B9 /* RCTSliderManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTSliderManager.h; sourceTree = ""; }; 14F484551AABFCE100FDF6B9 /* RCTSliderManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTSliderManager.m; sourceTree = ""; }; + 14435CE11AAC4AE100FC20F4 /* RCTMap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTMap.h; sourceTree = ""; }; + 14435CE21AAC4AE100FC20F4 /* RCTMap.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTMap.m; sourceTree = ""; }; + 14435CE31AAC4AE100FC20F4 /* RCTMapManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTMapManager.h; sourceTree = ""; }; + 14435CE41AAC4AE100FC20F4 /* RCTMapManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTMapManager.m; sourceTree = ""; }; + 58114A121AAE854800E7D092 /* RCTPicker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTPicker.h; sourceTree = ""; }; + 58114A131AAE854800E7D092 /* RCTPicker.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTPicker.m; sourceTree = ""; }; + 58114A141AAE854800E7D092 /* RCTPickerManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTPickerManager.h; sourceTree = ""; }; + 58114A151AAE854800E7D092 /* RCTPickerManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTPickerManager.m; sourceTree = ""; }; + 58C571BF1AA56C1900CDF9C8 /* RCTDatePickerManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTDatePickerManager.m; sourceTree = ""; }; + 58C571C01AA56C1900CDF9C8 /* RCTDatePickerManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTDatePickerManager.h; sourceTree = ""; }; 830213F31A654E0800B993E6 /* RCTBridgeModule.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTBridgeModule.h; sourceTree = ""; }; 830A229C1A66C68A008503DA /* RCTRootView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTRootView.h; sourceTree = ""; }; 830A229D1A66C68A008503DA /* RCTRootView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTRootView.m; sourceTree = ""; }; @@ -141,6 +154,8 @@ 830BA4541A8E3BDA00D53203 /* RCTCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTCache.m; sourceTree = ""; }; 83BEE46C1A6D19BC00B5863B /* RCTSparseArray.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTSparseArray.h; sourceTree = ""; }; 83BEE46D1A6D19BC00B5863B /* RCTSparseArray.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTSparseArray.m; sourceTree = ""; }; + 83C9110E1AAE6521001323A3 /* RCTAnimationManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTAnimationManager.h; sourceTree = ""; }; + 83C9110F1AAE6521001323A3 /* RCTAnimationManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTAnimationManager.m; sourceTree = ""; }; 83CBBA2E1A601D0E00E9B192 /* libReactKit.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libReactKit.a; sourceTree = BUILT_PRODUCTS_DIR; }; 83CBBA4A1A601E3B00E9B192 /* RCTAssert.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTAssert.h; sourceTree = ""; }; 83CBBA4B1A601E3B00E9B192 /* RCTAssert.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTAssert.m; sourceTree = ""; }; @@ -198,8 +213,12 @@ children = ( 13B07FE71A69327A00A75B9A /* RCTAlertManager.h */, 13B07FE81A69327A00A75B9A /* RCTAlertManager.m */, + 83C9110E1AAE6521001323A3 /* RCTAnimationManager.h */, + 83C9110F1AAE6521001323A3 /* RCTAnimationManager.m */, 13B07FE91A69327A00A75B9A /* RCTExceptionsManager.h */, 13B07FEA1A69327A00A75B9A /* RCTExceptionsManager.m */, + 5F5F0D971A9E456B001279FA /* RCTLocationObserver.h */, + 5F5F0D981A9E456B001279FA /* RCTLocationObserver.m */, 13723B4E1A82FD3C00F88898 /* RCTStatusBarManager.h */, 13723B4F1A82FD3C00F88898 /* RCTStatusBarManager.m */, 13B07FED1A69327A00A75B9A /* RCTTiming.h */, @@ -213,20 +232,14 @@ 13B07FF31A6947C200A75B9A /* Views */ = { isa = PBXGroup; children = ( - 14F362071AABD06A001CE568 /* RCTSwitch.h */, - 14F362081AABD06A001CE568 /* RCTSwitch.m */, - 14F362091AABD06A001CE568 /* RCTSwitchManager.h */, - 14F3620A1AABD06A001CE568 /* RCTSwitchManager.m */, - 14F484541AABFCE100FDF6B9 /* RCTSliderManager.h */, - 14F484551AABFCE100FDF6B9 /* RCTSliderManager.m */, 13442BF21AA90E0B0037E5B0 /* RCTAnimationType.h */, + 13C325261AA63B6A0048765F /* RCTAutoInsetsProtocol.h */, 58C571C01AA56C1900CDF9C8 /* RCTDatePickerManager.h */, 58C571BF1AA56C1900CDF9C8 /* RCTDatePickerManager.m */, - 13442BF31AA90E0B0037E5B0 /* RCTPointerEvents.h */, - 13442BF41AA90E0B0037E5B0 /* RCTViewControllerProtocol.h */, - 13C325261AA63B6A0048765F /* RCTAutoInsetsProtocol.h */, - 13C325271AA63B6A0048765F /* RCTScrollableProtocol.h */, - 13C325281AA63B6A0048765F /* RCTViewNodeProtocol.h */, + 14435CE11AAC4AE100FC20F4 /* RCTMap.h */, + 14435CE21AAC4AE100FC20F4 /* RCTMap.m */, + 14435CE31AAC4AE100FC20F4 /* RCTMapManager.h */, + 14435CE41AAC4AE100FC20F4 /* RCTMapManager.m */, 13B0800C1A69489C00A75B9A /* RCTNavigator.h */, 13B0800D1A69489C00A75B9A /* RCTNavigator.m */, 13B0800E1A69489C00A75B9A /* RCTNavigatorManager.h */, @@ -235,24 +248,24 @@ 13B080111A69489C00A75B9A /* RCTNavItem.m */, 13B080121A69489C00A75B9A /* RCTNavItemManager.h */, 13B080131A69489C00A75B9A /* RCTNavItemManager.m */, + 58114A121AAE854800E7D092 /* RCTPicker.h */, + 58114A131AAE854800E7D092 /* RCTPicker.m */, + 58114A141AAE854800E7D092 /* RCTPickerManager.h */, + 58114A151AAE854800E7D092 /* RCTPickerManager.m */, + 13442BF31AA90E0B0037E5B0 /* RCTPointerEvents.h */, 13B07FF61A6947C200A75B9A /* RCTScrollView.h */, 13B07FF71A6947C200A75B9A /* RCTScrollView.m */, 13B07FF81A6947C200A75B9A /* RCTScrollViewManager.h */, 13B07FF91A6947C200A75B9A /* RCTScrollViewManager.m */, + 13C325271AA63B6A0048765F /* RCTScrollableProtocol.h */, 13E0674B1A70F44B002CDEE1 /* RCTShadowView.h */, 13E0674C1A70F44B002CDEE1 /* RCTShadowView.m */, - 13B080141A69489C00A75B9A /* RCTTextField.h */, - 13B080151A69489C00A75B9A /* RCTTextField.m */, - 13B080161A69489C00A75B9A /* RCTTextFieldManager.h */, - 13B080171A69489C00A75B9A /* RCTTextFieldManager.m */, - 13B080181A69489C00A75B9A /* RCTUIActivityIndicatorViewManager.h */, - 13B080191A69489C00A75B9A /* RCTUIActivityIndicatorViewManager.m */, - 13E0674F1A70F44B002CDEE1 /* RCTView.h */, - 13E067501A70F44B002CDEE1 /* RCTView.m */, - 13E0674D1A70F44B002CDEE1 /* RCTViewManager.h */, - 13E0674E1A70F44B002CDEE1 /* RCTViewManager.m */, - 13B080231A694A8400A75B9A /* RCTWrapperViewController.h */, - 13B080241A694A8400A75B9A /* RCTWrapperViewController.m */, + 14F484541AABFCE100FDF6B9 /* RCTSliderManager.h */, + 14F484551AABFCE100FDF6B9 /* RCTSliderManager.m */, + 14F362071AABD06A001CE568 /* RCTSwitch.h */, + 14F362081AABD06A001CE568 /* RCTSwitch.m */, + 14F362091AABD06A001CE568 /* RCTSwitchManager.h */, + 14F3620A1AABD06A001CE568 /* RCTSwitchManager.m */, 137327DF1AA5CF210034F82E /* RCTTabBar.h */, 137327E01AA5CF210034F82E /* RCTTabBar.m */, 137327E11AA5CF210034F82E /* RCTTabBarItem.h */, @@ -261,6 +274,20 @@ 137327E41AA5CF210034F82E /* RCTTabBarItemManager.m */, 137327E51AA5CF210034F82E /* RCTTabBarManager.h */, 137327E61AA5CF210034F82E /* RCTTabBarManager.m */, + 13B080141A69489C00A75B9A /* RCTTextField.h */, + 13B080151A69489C00A75B9A /* RCTTextField.m */, + 13B080161A69489C00A75B9A /* RCTTextFieldManager.h */, + 13B080171A69489C00A75B9A /* RCTTextFieldManager.m */, + 13B080181A69489C00A75B9A /* RCTUIActivityIndicatorViewManager.h */, + 13B080191A69489C00A75B9A /* RCTUIActivityIndicatorViewManager.m */, + 13E0674F1A70F44B002CDEE1 /* RCTView.h */, + 13E067501A70F44B002CDEE1 /* RCTView.m */, + 13442BF41AA90E0B0037E5B0 /* RCTViewControllerProtocol.h */, + 13E0674D1A70F44B002CDEE1 /* RCTViewManager.h */, + 13E0674E1A70F44B002CDEE1 /* RCTViewManager.m */, + 13C325281AA63B6A0048765F /* RCTViewNodeProtocol.h */, + 13B080231A694A8400A75B9A /* RCTWrapperViewController.h */, + 13B080241A694A8400A75B9A /* RCTWrapperViewController.m */, 13E067531A70F44B002CDEE1 /* UIView+ReactKit.h */, 13E067541A70F44B002CDEE1 /* UIView+ReactKit.m */, ); @@ -435,12 +462,17 @@ 134FCB361A6D42D900051CC8 /* RCTSparseArray.m in Sources */, 13A1F71E1A75392D00D3D453 /* RCTKeyCommands.m in Sources */, 83CBBA531A601E3B00E9B192 /* RCTUtils.m in Sources */, + 14435CE61AAC4AE100FC20F4 /* RCTMapManager.m in Sources */, + 83C911101AAE6521001323A3 /* RCTAnimationManager.m in Sources */, 83CBBA601A601EAA00E9B192 /* RCTBridge.m in Sources */, + 58114A161AAE854800E7D092 /* RCTPicker.m in Sources */, 137327E81AA5CF210034F82E /* RCTTabBarItem.m in Sources */, 13E067551A70F44B002CDEE1 /* RCTShadowView.m in Sources */, + 58114A171AAE854800E7D092 /* RCTPickerManager.m in Sources */, 13B0801A1A69489C00A75B9A /* RCTNavigator.m in Sources */, 830BA4551A8E3BDA00D53203 /* RCTCache.m in Sources */, 137327E71AA5CF210034F82E /* RCTTabBar.m in Sources */, + 14435CE51AAC4AE100FC20F4 /* RCTMap.m in Sources */, 134FCB3E1A6E7F0800051CC8 /* RCTWebViewExecutor.m in Sources */, 13B0801C1A69489C00A75B9A /* RCTNavItem.m in Sources */, 83CBBA691A601EF300E9B192 /* RCTEventDispatcher.m in Sources */, diff --git a/ReactKit/Views/RCTMap.h b/ReactKit/Views/RCTMap.h new file mode 100644 index 000000000..5ab56079b --- /dev/null +++ b/ReactKit/Views/RCTMap.h @@ -0,0 +1,24 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#import +#import + +extern const CLLocationDegrees RCTMapDefaultSpan; +extern const NSTimeInterval RCTMapRegionChangeObserveInterval; +extern const CGFloat RCTMapZoomBoundBuffer; + +@class RCTEventDispatcher; + +@interface RCTMap: MKMapView + +@property (nonatomic, assign) BOOL followUserLocation; +@property (nonatomic, copy) NSDictionary *JSONRegion; +@property (nonatomic, assign) CGFloat minDelta; +@property (nonatomic, assign) CGFloat maxDelta; +@property (nonatomic, assign) UIEdgeInsets legalLabelInsets; +@property (nonatomic, strong) NSTimer *regionChangeObserveTimer; + +@end + +#define FLUSH_NAN(value) \ + (isnan(value) ? 0 : value) diff --git a/ReactKit/Views/RCTMap.m b/ReactKit/Views/RCTMap.m new file mode 100644 index 000000000..09dac2a5b --- /dev/null +++ b/ReactKit/Views/RCTMap.m @@ -0,0 +1,130 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#import "RCTMap.h" + +#import "RCTEventDispatcher.h" +#import "RCTLog.h" +#import "RCTUtils.h" + +const CLLocationDegrees RCTMapDefaultSpan = 0.005; +const NSTimeInterval RCTMapRegionChangeObserveInterval = 0.1; +const CGFloat RCTMapZoomBoundBuffer = 0.01; + +@interface RCTMap() + +@property (nonatomic, strong) UIView *legalLabel; +@property (nonatomic, strong) CLLocationManager *locationManager; + +@end + +@implementation RCTMap + +- (instancetype)init +{ + self = [super init]; + if (self) { + // Find Apple link label + for (UIView *subview in self.subviews) { + if ([NSStringFromClass(subview.class) isEqualToString:@"MKAttributionLabel"]) { + // This check is super hacky, but the whole premise of moving around Apple's internal subviews is super hacky + _legalLabel = subview; + break; + } + } + } + return self; +} + +- (void)dealloc +{ + [self.regionChangeObserveTimer invalidate]; +} + +- (void)layoutSubviews +{ + [super layoutSubviews]; + + // Force resize subviews - only the layer is resized by default + CGRect mapFrame = self.frame; + self.frame = CGRectZero; + self.frame = mapFrame; + + if (_legalLabel) { + dispatch_async(dispatch_get_main_queue(), ^{ + CGRect frame = _legalLabel.frame; + if (_legalLabelInsets.left) { + frame.origin.x = _legalLabelInsets.left; + } else if (_legalLabelInsets.right) { + frame.origin.x = mapFrame.size.width - _legalLabelInsets.right - frame.size.width; + } + if (_legalLabelInsets.top) { + frame.origin.y = _legalLabelInsets.top; + } else if (_legalLabelInsets.bottom) { + frame.origin.y = mapFrame.size.height - _legalLabelInsets.bottom - frame.size.height; + } + _legalLabel.frame = frame; + }); + } +} + +#pragma mark Accessors + +- (void)setShowsUserLocation:(BOOL)showsUserLocation +{ + if (self.showsUserLocation != showsUserLocation) { + if (showsUserLocation && !_locationManager) { + _locationManager = [[CLLocationManager alloc] init]; + if ([_locationManager respondsToSelector:@selector(requestWhenInUseAuthorization)]) { + [_locationManager requestWhenInUseAuthorization]; + } + } + [super setShowsUserLocation:showsUserLocation]; + + // If it needs to show user location, force map view centered + // on user's current location on user location updates + self.followUserLocation = showsUserLocation; + } +} + +- (void)setJSONRegion:(NSDictionary *)region +{ + if (region) { + MKCoordinateRegion coordinateRegion = self.region; + if ([region[@"latitude"] isKindOfClass:[NSNumber class]]) { + coordinateRegion.center.latitude = [region[@"latitude"] doubleValue]; + } else { + RCTLogError(@"region must include numeric latitude, got: %@", region); + return; + } + if ([region[@"longitude"] isKindOfClass:[NSNumber class]]) { + coordinateRegion.center.longitude = [region[@"longitude"] doubleValue]; + } else { + RCTLogError(@"region must include numeric longitude, got: %@", region); + return; + } + if ([region[@"latitudeDelta"] isKindOfClass:[NSNumber class]]) { + coordinateRegion.span.latitudeDelta = [region[@"latitudeDelta"] doubleValue]; + } + if ([region[@"longitudeDelta"] isKindOfClass:[NSNumber class]]) { + coordinateRegion.span.longitudeDelta = [region[@"longitudeDelta"] doubleValue]; + } + + [self setRegion:coordinateRegion animated:YES]; + } +} + +- (NSDictionary *)JSONRegion +{ + MKCoordinateRegion region = self.region; + if (!CLLocationCoordinate2DIsValid(region.center)) { + return nil; + } + return @{ + @"latitude": @(FLUSH_NAN(region.center.latitude)), + @"longitude": @(FLUSH_NAN(region.center.longitude)), + @"latitudeDelta": @(FLUSH_NAN(region.span.latitudeDelta)), + @"longitudeDelta": @(FLUSH_NAN(region.span.longitudeDelta)), + }; +} + +@end diff --git a/ReactKit/Views/RCTMapManager.h b/ReactKit/Views/RCTMapManager.h new file mode 100644 index 000000000..93b7049ca --- /dev/null +++ b/ReactKit/Views/RCTMapManager.h @@ -0,0 +1,7 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#import "RCTViewManager.h" + +@interface RCTMapManager : RCTViewManager + +@end diff --git a/ReactKit/Views/RCTMapManager.m b/ReactKit/Views/RCTMapManager.m new file mode 100644 index 000000000..421396a1e --- /dev/null +++ b/ReactKit/Views/RCTMapManager.m @@ -0,0 +1,119 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#import "RCTMapManager.h" + +#import "RCTBridge.h" +#import "RCTEventDispatcher.h" +#import "RCTMap.h" +#import "UIView+ReactKit.h" + +@interface RCTMapManager() + +@end + +@implementation RCTMapManager + +- (UIView *)view +{ + RCTMap *map = [[RCTMap alloc] init]; + map.delegate = self; + return map; +} + +RCT_EXPORT_VIEW_PROPERTY(showsUserLocation); +RCT_EXPORT_VIEW_PROPERTY(zoomEnabled); +RCT_EXPORT_VIEW_PROPERTY(rotateEnabled); +RCT_EXPORT_VIEW_PROPERTY(pitchEnabled); +RCT_EXPORT_VIEW_PROPERTY(scrollEnabled); +RCT_EXPORT_VIEW_PROPERTY(maxDelta); +RCT_EXPORT_VIEW_PROPERTY(minDelta); +RCT_EXPORT_VIEW_PROPERTY(legalLabelInsets); +RCT_REMAP_VIEW_PROPERTY(region, JSONRegion) + +#pragma mark MKMapViewDelegate + +- (void)mapView:(RCTMap *)mapView didUpdateUserLocation:(MKUserLocation *)location +{ + if (mapView.followUserLocation) { + MKCoordinateRegion region; + region.span.latitudeDelta = RCTMapDefaultSpan; + region.span.longitudeDelta = RCTMapDefaultSpan; + region.center = location.coordinate; + [mapView setRegion:region animated:YES]; + + // Move to user location only for the first time it loads up. + mapView.followUserLocation = NO; + } +} + +- (void)mapView:(RCTMap *)mapView regionWillChangeAnimated:(BOOL)animated +{ + [self _regionChanged:mapView]; + + mapView.regionChangeObserveTimer = [NSTimer timerWithTimeInterval:RCTMapRegionChangeObserveInterval + target:self + selector:@selector(_onTick:) + userInfo:@{ @"mapView": mapView } + repeats:YES]; + [[NSRunLoop mainRunLoop] addTimer:mapView.regionChangeObserveTimer forMode:NSRunLoopCommonModes]; +} + +- (void)mapView:(RCTMap *)mapView regionDidChangeAnimated:(BOOL)animated +{ + [self _regionChanged:mapView]; + [self _emitRegionChangeEvent:mapView continuous:NO]; + + [mapView.regionChangeObserveTimer invalidate]; + mapView.regionChangeObserveTimer = nil; +} + +#pragma mark Private + +- (void)_onTick:(NSTimer *)timer +{ + [self _regionChanged:timer.userInfo[@"mapView"]]; +} + +- (void)_regionChanged:(RCTMap *)mapView +{ + BOOL needZoom = NO; + CGFloat newLongitudeDelta = 0.0f; + MKCoordinateRegion region = mapView.region; + // On iOS 7, it's possible that we observe invalid locations during initialization of the map. + // Filter those out. + if (!CLLocationCoordinate2DIsValid(region.center)) { + return; + } + // Calculation on float is not 100% accurate. If user zoom to max/min and then move, it's likely the map will auto zoom to max/min from time to time. + // So let's try to make map zoom back to 99% max or 101% min so that there are some buffer that moving the map won't constantly hitting the max/min bound. + if (mapView.maxDelta > FLT_EPSILON && region.span.longitudeDelta > mapView.maxDelta) { + needZoom = YES; + newLongitudeDelta = mapView.maxDelta * (1 - RCTMapZoomBoundBuffer); + } else if (mapView.minDelta > FLT_EPSILON && region.span.longitudeDelta < mapView.minDelta) { + needZoom = YES; + newLongitudeDelta = mapView.minDelta * (1 + RCTMapZoomBoundBuffer); + } + if (needZoom) { + region.span.latitudeDelta = region.span.latitudeDelta / region.span.longitudeDelta * newLongitudeDelta; + region.span.longitudeDelta = newLongitudeDelta; + mapView.region = region; + } + + // Continously observe region changes + [self _emitRegionChangeEvent:mapView continuous:YES]; +} + +- (void)_emitRegionChangeEvent:(RCTMap *)mapView continuous:(BOOL)continuous +{ + NSDictionary *region = mapView.JSONRegion; + if (region) { + NSDictionary *event = @{ + @"target": [mapView reactTag], + @"continuous": @(continuous), + @"region": mapView.JSONRegion, + }; + [self.bridge.eventDispatcher sendInputEventWithName:@"topChange" body:event]; + } +} + +@end diff --git a/ReactKit/Views/RCTPicker.h b/ReactKit/Views/RCTPicker.h new file mode 100644 index 000000000..cbc55c7f3 --- /dev/null +++ b/ReactKit/Views/RCTPicker.h @@ -0,0 +1,11 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#import + +@class RCTEventDispatcher; + +@interface RCTPicker : UIPickerView + +- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER; + +@end diff --git a/ReactKit/Views/RCTPicker.m b/ReactKit/Views/RCTPicker.m new file mode 100644 index 000000000..1fcb33c1c --- /dev/null +++ b/ReactKit/Views/RCTPicker.m @@ -0,0 +1,91 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#import "RCTPicker.h" + +#import "RCTConvert.h" +#import "RCTEventDispatcher.h" +#import "RCTUtils.h" +#import "UIView+ReactKit.h" + +const NSInteger UNINITIALIZED_INDEX = -1; + +@interface RCTPicker() +{ + RCTEventDispatcher *_eventDispatcher; + NSArray *_items; + NSInteger _selectedIndex; +} +@end + +@implementation RCTPicker + +- (id)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher +{ + if (self = [super initWithFrame:CGRectZero]) { + _eventDispatcher = eventDispatcher; + _selectedIndex = UNINITIALIZED_INDEX; + self.delegate = self; + } + return self; +} + +- (void)setItems:(NSArray *)items +{ + if (_items != items) { + _items = [items copy]; + [self setNeedsLayout]; + } +} + +- (void)setSelectedIndex:(NSInteger)selectedIndex +{ + if (_selectedIndex != selectedIndex) { + BOOL animated = _selectedIndex != UNINITIALIZED_INDEX; // Don't animate the initial value + _selectedIndex = selectedIndex; + dispatch_async(dispatch_get_main_queue(), ^{ + [self selectRow:selectedIndex inComponent:0 animated:animated]; + }); + } +} + +#pragma mark - UIPickerViewDataSource protocol + +- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView +{ + return 1; +} + +- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component +{ + return [_items count]; +} + +#pragma mark - UIPickerViewDelegate methods + +- (NSDictionary *)itemForRow:(NSInteger)row +{ + return (NSDictionary*)[_items objectAtIndex:row]; +} + +- (id)valueForRow:(NSInteger)row +{ + return [self itemForRow:row][@"value"]; +} + +- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component +{ + return [self itemForRow:row][@"label"]; +} + +- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component +{ + _selectedIndex = row; + NSDictionary *event = @{ + @"target": self.reactTag, + @"newIndex": @(row), + @"newValue": [self valueForRow:row] + }; + + [_eventDispatcher sendInputEventWithName:@"topChange" body:event]; +} +@end diff --git a/ReactKit/Views/RCTPickerManager.h b/ReactKit/Views/RCTPickerManager.h new file mode 100644 index 000000000..49cd74cb7 --- /dev/null +++ b/ReactKit/Views/RCTPickerManager.h @@ -0,0 +1,7 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#import "RCTViewManager.h" + +@interface RCTPickerManager : RCTViewManager + +@end diff --git a/ReactKit/Views/RCTPickerManager.m b/ReactKit/Views/RCTPickerManager.m new file mode 100644 index 000000000..320aae0d6 --- /dev/null +++ b/ReactKit/Views/RCTPickerManager.m @@ -0,0 +1,28 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#import "RCTPickerManager.h" + +#import "RCTBridge.h" +#import "RCTConvert.h" +#import "RCTPicker.h" + +@implementation RCTPickerManager + +- (UIView *)view +{ + return [[RCTPicker alloc] initWithEventDispatcher:self.bridge.eventDispatcher]; +} + +RCT_EXPORT_VIEW_PROPERTY(items) +RCT_EXPORT_VIEW_PROPERTY(selectedIndex) + +- (NSDictionary *)constantsToExport +{ + RCTPicker *pv = [[RCTPicker alloc] init]; + return @{ + @"ComponentHeight": @(CGRectGetHeight(pv.frame)), + @"ComponentWidth": @(CGRectGetWidth(pv.frame)) + }; +} + +@end diff --git a/ReactKit/Views/RCTTextField.m b/ReactKit/Views/RCTTextField.m index b684517b3..0dca73daa 100644 --- a/ReactKit/Views/RCTTextField.m +++ b/ReactKit/Views/RCTTextField.m @@ -17,7 +17,7 @@ - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher { if ((self = [super initWithFrame:CGRectZero])) { - + _eventDispatcher = eventDispatcher; [self addTarget:self action:@selector(_textFieldDidChange) forControlEvents:UIControlEventEditingChanged]; [self addTarget:self action:@selector(_textFieldBeginEditing) forControlEvents:UIControlEventEditingDidBegin]; @@ -40,7 +40,7 @@ - (void)removeReactSubview:(UIView *)subview { // TODO: this is a bit broken - if the TextField inserts any of - // it's own views below or between React's, the indices won't match + // its own views below or between React's, the indices won't match [_reactSubviews removeObject:subview]; [subview removeFromSuperview]; } @@ -48,7 +48,7 @@ - (void)insertReactSubview:(UIView *)view atIndex:(NSInteger)atIndex { // TODO: this is a bit broken - if the TextField inserts any of - // it's own views below or between React's, the indices won't match + // its own views below or between React's, the indices won't match [_reactSubviews insertObject:view atIndex:atIndex]; [super insertSubview:view atIndex:atIndex]; } @@ -74,7 +74,7 @@ - (void)setAutoCorrect:(BOOL)autoCorrect { - [super setAutocorrectionType:(autoCorrect ? UITextAutocorrectionTypeYes : UITextAutocorrectionTypeNo)]; + self.autocorrectionType = (autoCorrect ? UITextAutocorrectionTypeYes : UITextAutocorrectionTypeNo); } - (BOOL)autoCorrect @@ -117,7 +117,6 @@ RCT_TEXT_EVENT_HANDLER(_textFieldSubmitEditing, RCTTextEventTypeSubmit) return result; } -// Prevent native from becoming first responder (TODO: why?) - (BOOL)canBecomeFirstResponder { return _jsRequestingFirstResponder; diff --git a/ReactKit/Views/RCTTextFieldManager.m b/ReactKit/Views/RCTTextFieldManager.m index 4f46c0dff..52b29dc36 100644 --- a/ReactKit/Views/RCTTextFieldManager.m +++ b/ReactKit/Views/RCTTextFieldManager.m @@ -20,7 +20,6 @@ RCT_REMAP_VIEW_PROPERTY(autoCapitalize, autocapitalizationType) RCT_EXPORT_VIEW_PROPERTY(enabled) RCT_EXPORT_VIEW_PROPERTY(placeholder) RCT_EXPORT_VIEW_PROPERTY(text) -RCT_EXPORT_VIEW_PROPERTY(font) RCT_EXPORT_VIEW_PROPERTY(clearButtonMode) RCT_EXPORT_VIEW_PROPERTY(keyboardType) RCT_REMAP_VIEW_PROPERTY(color, textColor) diff --git a/package.json b/package.json index f0af6823e..13a3467e8 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,8 @@ "scriptPreprocessor": "jestSupport/scriptPreprocess.js", "setupEnvScriptFile": "jestSupport/env.js", "testPathIgnorePatterns": [ - "/node_modules/" + "/node_modules/", + "packager/react-packager/src/Activity/" ], "testFileExtensions": [ "js"