├── .eslintrc ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── ReactNativeKeyboardInput.podspec ├── Supplementals ├── example-android.gif ├── example.gif └── example2.gif ├── android ├── app │ ├── BUCK │ ├── build.gradle │ ├── build_defs.bzl │ ├── debug.keystore │ ├── proguard-rules.pro │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ └── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ └── com │ │ │ └── keyboardinput │ │ │ ├── MainActivity.java │ │ │ └── MainApplication.java │ │ └── res │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── strings.xml │ │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle ├── demo ├── demoKeyboards.js ├── demoRoot.android.js ├── demoScreen.js └── res │ ├── settings.png │ └── star.png ├── e2e ├── init.js ├── mocha.opts └── sanity.spec.js ├── index.android.js ├── index.ios.js ├── ios ├── KeyboardInput-tvOS │ └── Info.plist ├── KeyboardInput-tvOSTests │ └── Info.plist ├── KeyboardInput.xcodeproj │ ├── project.pbxproj │ └── xcshareddata │ │ └── xcschemes │ │ ├── KeyboardInput-tvOS.xcscheme │ │ └── KeyboardInput.xcscheme ├── KeyboardInput.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── KeyboardInput │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Base.lproj │ │ └── LaunchScreen.xib │ ├── Images.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ ├── Info.plist │ └── main.m ├── KeyboardInputTests │ ├── Info.plist │ └── KeyboardInputTests.m ├── Podfile └── Podfile.lock ├── lib ├── android │ ├── build.gradle │ └── src │ │ └── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── com │ │ └── wix │ │ └── reactnativekeyboardinput │ │ ├── AppContextHolder.java │ │ ├── CustomKeyboardLayout.java │ │ ├── CustomKeyboardRootView.java │ │ ├── CustomKeyboardRootViewManager.java │ │ ├── CustomKeyboardRootViewShadow.java │ │ ├── GlobalDefs.java │ │ ├── KeyboardInputModule.java │ │ ├── KeyboardInputPackage.java │ │ ├── ReactContextHolder.java │ │ ├── ReactScreenMonitor.java │ │ ├── ReactSoftKeyboardMonitor.java │ │ └── utils │ │ ├── Logger.java │ │ ├── PredicateFunc.java │ │ ├── RuntimeUtils.java │ │ └── ViewUtils.java └── ios │ ├── LNInterpolation │ ├── Color+Interpolation.h │ ├── Color+Interpolation.m │ ├── LNAnimator.h │ ├── LNAnimator.m │ ├── LNInterpolable.h │ ├── LNInterpolable.m │ ├── LNInterpolation.h │ ├── NSValue+Interpolation.h │ └── NSValue+Interpolation.mm │ ├── RCTCustomInputController.xcodeproj │ └── project.pbxproj │ └── RCTCustomInputController │ ├── RCTCustomInputController.h │ ├── RCTCustomInputController.m │ ├── RCTCustomKeyboardViewController.h │ └── RCTCustomKeyboardViewController.m ├── package-lock.json ├── package.json ├── src ├── CustomKeyboardView.js ├── KeyboardAccessoryView.js ├── KeyboardRegistry.spec.js ├── KeyboardsRegistry.js ├── TextInputKeyboardManagerAndroid.js ├── TextInputKeyboardMangerIOS.js ├── index.js └── utils │ ├── EventEmitterManager.js │ ├── EventEmitterManager.spec.js │ └── KeyboardUtils.js └── wallaby.js /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "plugins": ["react-native"], 4 | "extends": [ 5 | "airbnb" 6 | ], 7 | "rules": { 8 | "object-curly-spacing": "off", 9 | "react/prefer-stateless-function": "off", 10 | "react/jsx-filename-extension": "off", 11 | "react/require-default-props": "off", 12 | "no-use-before-define": "off", 13 | "react/forbid-prop-types": "off", 14 | "react/jsx-space-before-closing": "off", 15 | "react/jsx-tag-spacing": "off", 16 | "max-len": [2, 135, 4, {"ignoreUrls": true}], 17 | "class-methods-use-this": "off", 18 | "arrow-body-style": "off", 19 | "no-plusplus": "off", 20 | "no-array-index-key": "off", 21 | "no-multi-comp": "off" 22 | }, 23 | "env": { 24 | "browser": true, 25 | "node": true, 26 | "jest": true 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | ModuleCache/ 9 | 10 | ## Various settings 11 | *.pbxuser 12 | !default.pbxuser 13 | *.mode1v3 14 | !default.mode1v3 15 | *.mode2v3 16 | !default.mode2v3 17 | *.perspectivev3 18 | !default.perspectivev3 19 | xcuserdata/ 20 | ios/info.plist 21 | 22 | ## Other 23 | *.moved-aside 24 | *.xcuserstate 25 | 26 | ## Obj-C/Swift specific 27 | *.hmap 28 | *.ipa 29 | *.dSYM.zip 30 | *.dSYM 31 | 32 | # CocoaPods 33 | # 34 | # We recommend against adding the Pods directory to your .gitignore. However 35 | # you should judge for yourself, the pros and cons are mentioned at: 36 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 37 | # 38 | # Pods/ 39 | 40 | # Carthage 41 | # 42 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 43 | # Carthage/Checkouts 44 | 45 | Carthage/Build 46 | /ios/Pods/ 47 | 48 | # fastlane 49 | # 50 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 51 | # screenshots whenever they are needed. 52 | # For more information about the recommended setup visit: 53 | # https://docs.fastlane.tools/best-practices/source-control/ 54 | 55 | fastlane/report.xml 56 | fastlane/screenshots 57 | 58 | #Code Injection 59 | # 60 | # After new code Injection tools there's a generated folder /iOSInjectionProject 61 | # https://github.com/johnno1962/injectionforxcode 62 | 63 | iOSInjectionProject/ 64 | 65 | # OSX 66 | # 67 | .DS_Store 68 | 69 | # Xcode 70 | # 71 | build/ 72 | *.pbxuser 73 | !default.pbxuser 74 | *.mode1v3 75 | !default.mode1v3 76 | *.mode2v3 77 | !default.mode2v3 78 | *.perspectivev3 79 | !default.perspectivev3 80 | xcuserdata 81 | *.xccheckout 82 | *.moved-aside 83 | DerivedData 84 | *.hmap 85 | *.ipa 86 | *.xcuserstate 87 | project.xcworkspace 88 | 89 | # Android/IJ 90 | # 91 | *.iml 92 | .idea 93 | .gradle 94 | local.properties 95 | android/.project 96 | android/.settings/* 97 | android/app/.project 98 | android/app/.classpath 99 | android/app/.settings/* 100 | lib/android/.project 101 | lib/android/.classpath 102 | lib/android/.settings/* 103 | 104 | # node.js 105 | # 106 | node_modules/ 107 | npm-debug.log 108 | 109 | # BUCK 110 | buck-out/ 111 | \.buckd/ 112 | android/app/libs 113 | *.keystore 114 | !debug.keystore 115 | yarn.lock 116 | 117 | #vscoe 118 | .vscode/* 119 | 120 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | demo/ 2 | ios/ 3 | android/ 4 | Supplementals/ 5 | index.android.js 6 | index.ios.js 7 | .eslintrc 8 | *.spec.js 9 | 10 | # Xcode 11 | # 12 | 13 | 14 | ## Build generated 15 | build/ 16 | DerivedData/ 17 | ModuleCache/ 18 | 19 | ## Various settings 20 | *.pbxuser 21 | !default.pbxuser 22 | *.mode1v3 23 | !default.mode1v3 24 | *.mode2v3 25 | !default.mode2v3 26 | *.perspectivev3 27 | !default.perspectivev3 28 | xcuserdata/ 29 | ios/info.plist 30 | 31 | ## Other 32 | *.moved-aside 33 | *.xcuserstate 34 | 35 | ## Obj-C/Swift specific 36 | *.hmap 37 | *.ipa 38 | *.dSYM.zip 39 | *.dSYM 40 | 41 | # Android/IJ 42 | # 43 | *.iml 44 | .idea 45 | .gradle 46 | local.properties 47 | 48 | # OSX 49 | # 50 | .DS_Store 51 | 52 | # node.js 53 | # 54 | npm-debug.log 55 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Wix.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Important: deprecation alert 2 | This library is being deprecated and the repository will not be maintaned, the components have moved to our UI library - please start migrating to [RN-UILib](https://github.com/wix/react-native-ui-lib/). 3 | If you want to try out our excelent (and constantly improving) UI compoenent library, please use: 4 | ``` 5 | import {Keyboard} from 'react-native-ui-lib'; 6 | const KeyboardAccessoryView = Keyboard.KeyboardAccessoryView; 7 | ``` 8 | If you don't want to import the whole library, you can use only the `keyboard` package: 9 | ``` 10 | import {KeyboardAccessoryView} from 'react-native-ui-lib/keyboard'; 11 | ``` 12 | 13 | # React Native Keyboard Input 14 | 15 | Presents a React component as an input view which replaces the system keyboard. Can be used for creating custom input views such as an image gallery, stickers, etc. 16 | 17 | Supports both iOS and Android. 18 | 19 | 20 | 21 | # Installation 22 | Install the package from npm: 23 | 24 | `yarn add react-native-keyboard-input` or `npm i --save react-native-keyboard-input` 25 | 26 | ## Android 27 | 28 | Update your dependencies in `android/app/build.gradle`: 29 | 30 | ```gradle 31 | dependencies { 32 | // Add this dependency: 33 | compile project(":reactnativekeyboardinput") 34 | } 35 | ``` 36 | 37 | Update your `android/settings.gradle`: 38 | 39 | ```gradle 40 | include ':reactnativekeyboardinput' 41 | project(':reactnativekeyboardinput').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-keyboard-input/lib/android') 42 | ``` 43 | 44 | In your `MainApplication.java`, add to the `getPackages()` list: 45 | 46 | ```java 47 | import com.wix.reactnativekeyboardinput.KeyboardInputPackage; 48 | 49 | @Override 50 | protected List getPackages() { 51 | return Arrays.asList( 52 | // Add this package: 53 | new KeyboardInputPackage(this) // (this = Android application object) 54 | ); 55 | } 56 | ``` 57 | 58 | ### ProGuard 59 | 60 | If you have pro-guard enabled and are having trouble with your build, apply this to your project's main `proguard-rules.pro`: 61 | 62 | ``` 63 | -dontwarn com.wix.reactnativekeyboardinput.** 64 | ``` 65 | 66 | ## iOS 67 | In Xcode, drag both `RCTCustomInputController.xcodeproj` and `KeyboardTrackingView.xcodeproj` from your `node_modules` to the Libraries folder in the Project Navigator, then add `libRCTCustomInputController.a` and `libKeyboardTrackingView.a` to your app target "Linked Frameworks and Libraries". 68 | 69 | #### Covering the whold keyboard in predictive mode 70 | To utilize this feature you'll need to add `KeyboardTrackingView` to your projects scheme build action. 71 | 72 | From Xcode menu: 73 | 74 | 1. `product -> scheme -> manage schemes -> double-click your project` 75 | 2. Slect build at the right menu, click the + icon at the bottom of the targets list and select `KeyboardTrackingView`. 76 | 3. Drag and position `KeyboardTrackingView` to be first, above your project, and unmark "Parallelize Build" option at the top. 77 | 78 | If necessary, you can take a look at how it is set-up in the demo project. 79 | 80 | 81 | # Usage 82 | 83 | There are 2 main parts necessary for the implementation: 84 | 85 | ## 1. A keyboard component 86 | Create a component that you wish to use as a keyboard input. For example: 87 | 88 | ```js 89 | class KeyboardView extends Component { 90 | static propTypes = { 91 | title: PropTypes.string, 92 | }; 93 | render() { 94 | return ( 95 | 96 | HELOOOO!!! 97 | {this.props.title} 98 | 99 | ); 100 | } 101 | } 102 | ``` 103 | 104 | Now register with the keyboard registry so it can be used later as a keyboard: 105 | 106 | ```js 107 | import {KeyboardRegistry} from 'react-native-keyboard-input'; 108 | 109 | KeyboardRegistry.registerKeyboard('MyKeyboardView', () => KeyboardView); 110 | ``` 111 | 112 | When you need to notify about selecting an item in the keyboard, use: 113 | 114 | ```js 115 | KeyboardRegistry.onItemSelected(`MyKeyboardView`, params); 116 | ``` 117 | 118 | ## 2. Using the keyboard component as an input view 119 | While this package provides several component and classes for low-level control over custom keyboard inputs, the easiets way would be to use `KeyboardAccessoryView`. It's the only thing you'll need to show your Keyboard component as a custom input. For example: 120 | 121 | ```js 122 | 128 | ``` 129 | 130 | | Prop | Type | Description | 131 | | ---- | ---- | ----------- | 132 | | renderContent | Function | a fucntion for rendering the content of the keyboard toolbar | 133 | | kbInputRef | Object | A ref to the input component which triggers the showing of the keyboard | 134 | | kbComponent | String | The registered component name | 135 | | kbInitialProps | Object | Initial props to pass to the registered keyboard component | 136 | | onItemSelected | Function | a callback function for a selection of an item in the keyboard component | 137 | 138 | This component takes care of making your toolbar (which is rendered via `renderContent `) "float" above the keyboard (necessary for iOS), and for setting your component as the keyboard input when the `kbComponent` changes. 139 | 140 | # Demo 141 | 142 | See [demoScreen.js](https://github.com/wix/react-native-keyboard-input/blob/master/demo/demoScreen.js) for a full working example. 143 | -------------------------------------------------------------------------------- /ReactNativeKeyboardInput.podspec: -------------------------------------------------------------------------------- 1 | require 'json' 2 | 3 | package = JSON.parse(File.read(File.join(__dir__, 'package.json'))) 4 | 5 | Pod::Spec.new do |s| 6 | s.name = "ReactNativeKeyboardInput" 7 | s.version = package['version'] 8 | s.summary = package['description'] 9 | 10 | s.authors = package['author'] 11 | s.homepage = package['homepage'] 12 | s.license = package['license'] 13 | s.platforms = { :ios => "9.0", :tvos => "9.2" } 14 | 15 | s.module_name = 'ReactNativeKeyboardInput' 16 | 17 | s.source = { :git => "https://github.com/wix/react-native-autogrow-textinput", :tag => "#{s.version}" } 18 | s.source_files = "lib/ios/**/*.{h,m}" 19 | 20 | s.dependency 'React' 21 | s.frameworks = 'UIKit' 22 | end 23 | -------------------------------------------------------------------------------- /Supplementals/example-android.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wix-incubator/react-native-keyboard-input/acb3a58e96988026f449b48e8b49f49164684d9f/Supplementals/example-android.gif -------------------------------------------------------------------------------- /Supplementals/example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wix-incubator/react-native-keyboard-input/acb3a58e96988026f449b48e8b49f49164684d9f/Supplementals/example.gif -------------------------------------------------------------------------------- /Supplementals/example2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wix-incubator/react-native-keyboard-input/acb3a58e96988026f449b48e8b49f49164684d9f/Supplementals/example2.gif -------------------------------------------------------------------------------- /android/app/BUCK: -------------------------------------------------------------------------------- 1 | # To learn about Buck see [Docs](https://buckbuild.com/). 2 | # To run your application with Buck: 3 | # - install Buck 4 | # - `npm start` - to start the packager 5 | # - `cd android` 6 | # - `keytool -genkey -v -keystore keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US"` 7 | # - `./gradlew :app:copyDownloadableDepsToLibs` - make all Gradle compile dependencies available to Buck 8 | # - `buck install -r android/app` - compile, install and run application 9 | # 10 | 11 | load(":build_defs.bzl", "create_aar_targets", "create_jar_targets") 12 | 13 | lib_deps = [] 14 | 15 | create_aar_targets(glob(["libs/*.aar"])) 16 | 17 | create_jar_targets(glob(["libs/*.jar"])) 18 | 19 | android_library( 20 | name = "all-libs", 21 | exported_deps = lib_deps, 22 | ) 23 | 24 | android_library( 25 | name = "app-code", 26 | srcs = glob([ 27 | "src/main/java/**/*.java", 28 | ]), 29 | deps = [ 30 | ":all-libs", 31 | ":build_config", 32 | ":res", 33 | ], 34 | ) 35 | 36 | android_build_config( 37 | name = "build_config", 38 | package = "com.keyboardinput", 39 | ) 40 | 41 | android_resource( 42 | name = "res", 43 | package = "com.keyboardinput", 44 | res = "src/main/res", 45 | ) 46 | 47 | android_binary( 48 | name = "app", 49 | keystore = "//android/keystores:debug", 50 | manifest = "src/main/AndroidManifest.xml", 51 | package_type = "debug", 52 | deps = [ 53 | ":app-code", 54 | ], 55 | ) 56 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: "com.android.application" 2 | 3 | import com.android.build.OutputFile 4 | 5 | /** 6 | * The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets 7 | * and bundleReleaseJsAndAssets). 8 | * These basically call `react-native bundle` with the correct arguments during the Android build 9 | * cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the 10 | * bundle directly from the development server. Below you can see all the possible configurations 11 | * and their defaults. If you decide to add a configuration block, make sure to add it before the 12 | * `apply from: "../../node_modules/react-native/react.gradle"` line. 13 | * 14 | * project.ext.react = [ 15 | * // the name of the generated asset file containing your JS bundle 16 | * bundleAssetName: "index.android.bundle", 17 | * 18 | * // the entry file for bundle generation 19 | * entryFile: "index.android.js", 20 | * 21 | * // https://facebook.github.io/react-native/docs/performance#enable-the-ram-format 22 | * bundleCommand: "ram-bundle", 23 | * 24 | * // whether to bundle JS and assets in debug mode 25 | * bundleInDebug: false, 26 | * 27 | * // whether to bundle JS and assets in release mode 28 | * bundleInRelease: true, 29 | * 30 | * // whether to bundle JS and assets in another build variant (if configured). 31 | * // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants 32 | * // The configuration property can be in the following formats 33 | * // 'bundleIn${productFlavor}${buildType}' 34 | * // 'bundleIn${buildType}' 35 | * // bundleInFreeDebug: true, 36 | * // bundleInPaidRelease: true, 37 | * // bundleInBeta: true, 38 | * 39 | * // whether to disable dev mode in custom build variants (by default only disabled in release) 40 | * // for example: to disable dev mode in the staging build type (if configured) 41 | * devDisabledInStaging: true, 42 | * // The configuration property can be in the following formats 43 | * // 'devDisabledIn${productFlavor}${buildType}' 44 | * // 'devDisabledIn${buildType}' 45 | * 46 | * // the root of your project, i.e. where "package.json" lives 47 | * root: "../../", 48 | * 49 | * // where to put the JS bundle asset in debug mode 50 | * jsBundleDirDebug: "$buildDir/intermediates/assets/debug", 51 | * 52 | * // where to put the JS bundle asset in release mode 53 | * jsBundleDirRelease: "$buildDir/intermediates/assets/release", 54 | * 55 | * // where to put drawable resources / React Native assets, e.g. the ones you use via 56 | * // require('./image.png')), in debug mode 57 | * resourcesDirDebug: "$buildDir/intermediates/res/merged/debug", 58 | * 59 | * // where to put drawable resources / React Native assets, e.g. the ones you use via 60 | * // require('./image.png')), in release mode 61 | * resourcesDirRelease: "$buildDir/intermediates/res/merged/release", 62 | * 63 | * // by default the gradle tasks are skipped if none of the JS files or assets change; this means 64 | * // that we don't look at files in android/ or ios/ to determine whether the tasks are up to 65 | * // date; if you have any other folders that you want to ignore for performance reasons (gradle 66 | * // indexes the entire tree), add them here. Alternatively, if you have JS files in android/ 67 | * // for example, you might want to remove it from here. 68 | * inputExcludes: ["android/**", "ios/**"], 69 | * 70 | * // override which node gets called and with what additional arguments 71 | * nodeExecutableAndArgs: ["node"], 72 | * 73 | * // supply additional arguments to the packager 74 | * extraPackagerArgs: [] 75 | * ] 76 | */ 77 | 78 | project.ext.react = [ 79 | entryFile: "index.js", 80 | enableHermes: false, // clean and rebuild if changing 81 | ] 82 | 83 | apply from: "../../node_modules/react-native/react.gradle" 84 | 85 | /** 86 | * Set this to true to create two separate APKs instead of one: 87 | * - An APK that only works on ARM devices 88 | * - An APK that only works on x86 devices 89 | * The advantage is the size of the APK is reduced by about 4MB. 90 | * Upload all the APKs to the Play Store and people will download 91 | * the correct one based on the CPU architecture of their device. 92 | */ 93 | def enableSeparateBuildPerCPUArchitecture = false 94 | 95 | /** 96 | * Run Proguard to shrink the Java bytecode in release builds. 97 | */ 98 | def enableProguardInReleaseBuilds = false 99 | 100 | /** 101 | * The preferred build flavor of JavaScriptCore. 102 | * 103 | * For example, to use the international variant, you can use: 104 | * `def jscFlavor = 'org.webkit:android-jsc-intl:+'` 105 | * 106 | * The international variant includes ICU i18n library and necessary data 107 | * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that 108 | * give correct results when using with locales other than en-US. Note that 109 | * this variant is about 6MiB larger per architecture than default. 110 | */ 111 | def jscFlavor = 'org.webkit:android-jsc:+' 112 | 113 | /** 114 | * Whether to enable the Hermes VM. 115 | * 116 | * This should be set on project.ext.react and mirrored here. If it is not set 117 | * on project.ext.react, JavaScript will not be compiled to Hermes Bytecode 118 | * and the benefits of using Hermes will therefore be sharply reduced. 119 | */ 120 | def enableHermes = project.ext.react.get("enableHermes", false); 121 | 122 | android { 123 | compileSdkVersion rootProject.ext.compileSdkVersion 124 | 125 | compileOptions { 126 | sourceCompatibility JavaVersion.VERSION_1_8 127 | targetCompatibility JavaVersion.VERSION_1_8 128 | } 129 | 130 | defaultConfig { 131 | applicationId "com.keyboardinput" 132 | minSdkVersion rootProject.ext.minSdkVersion 133 | targetSdkVersion rootProject.ext.targetSdkVersion 134 | missingDimensionStrategy "RNN.reactNativeVersion", "reactNative60" 135 | versionCode 1 136 | versionName "1.0" 137 | } 138 | splits { 139 | abi { 140 | reset() 141 | enable enableSeparateBuildPerCPUArchitecture 142 | universalApk false // If true, also generate a universal APK 143 | include "armeabi-v7a", "x86", "arm64-v8a", "x86_64" 144 | } 145 | } 146 | signingConfigs { 147 | debug { 148 | storeFile file('debug.keystore') 149 | storePassword 'android' 150 | keyAlias 'androiddebugkey' 151 | keyPassword 'android' 152 | } 153 | } 154 | buildTypes { 155 | debug { 156 | signingConfig signingConfigs.debug 157 | } 158 | release { 159 | // Caution! In production, you need to generate your own keystore file. 160 | // see https://facebook.github.io/react-native/docs/signed-apk-android. 161 | signingConfig signingConfigs.debug 162 | minifyEnabled enableProguardInReleaseBuilds 163 | proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" 164 | } 165 | } 166 | // applicationVariants are e.g. debug, release 167 | applicationVariants.all { variant -> 168 | variant.outputs.each { output -> 169 | // For each separate APK per architecture, set a unique version code as described here: 170 | // https://developer.android.com/studio/build/configure-apk-splits.html 171 | def versionCodes = ["armeabi-v7a": 1, "x86": 2, "arm64-v8a": 3, "x86_64": 4] 172 | def abi = output.getFilter(OutputFile.ABI) 173 | if (abi != null) { // null for the universal-debug, universal-release variants 174 | output.versionCodeOverride = 175 | versionCodes.get(abi) * 1048576 + defaultConfig.versionCode 176 | } 177 | 178 | } 179 | } 180 | } 181 | 182 | dependencies { 183 | implementation fileTree(dir: "libs", include: ["*.jar"]) 184 | implementation "com.facebook.react:react-native:+" // From node_modules 185 | implementation project(":reactnativekeyboardinput") 186 | implementation project(':react-native-navigation') 187 | 188 | if (enableHermes) { 189 | def hermesPath = "../../node_modules/hermes-engine/android/"; 190 | debugImplementation files(hermesPath + "hermes-debug.aar") 191 | releaseImplementation files(hermesPath + "hermes-release.aar") 192 | } else { 193 | implementation jscFlavor 194 | } 195 | } 196 | 197 | // Run this once to be able to run the application with BUCK 198 | // puts all compile dependencies into folder libs for BUCK to use 199 | task copyDownloadableDepsToLibs(type: Copy) { 200 | from configurations.compile 201 | into 'libs' 202 | } 203 | 204 | apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project) 205 | -------------------------------------------------------------------------------- /android/app/build_defs.bzl: -------------------------------------------------------------------------------- 1 | """Helper definitions to glob .aar and .jar targets""" 2 | 3 | def create_aar_targets(aarfiles): 4 | for aarfile in aarfiles: 5 | name = "aars__" + aarfile[aarfile.rindex("/") + 1:aarfile.rindex(".aar")] 6 | lib_deps.append(":" + name) 7 | android_prebuilt_aar( 8 | name = name, 9 | aar = aarfile, 10 | ) 11 | 12 | def create_jar_targets(jarfiles): 13 | for jarfile in jarfiles: 14 | name = "jars__" + jarfile[jarfile.rindex("/") + 1:jarfile.rindex(".jar")] 15 | lib_deps.append(":" + name) 16 | prebuilt_jar( 17 | name = name, 18 | binary_jar = jarfile, 19 | ) 20 | -------------------------------------------------------------------------------- /android/app/debug.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wix-incubator/react-native-keyboard-input/acb3a58e96988026f449b48e8b49f49164684d9f/android/app/debug.keystore -------------------------------------------------------------------------------- /android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 13 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/keyboardinput/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.keyboardinput; 2 | 3 | import com.reactnativenavigation.NavigationActivity; 4 | 5 | public class MainActivity extends NavigationActivity {} 6 | 7 | //public class MainActivity extends ReactActivity { 8 | // 9 | // /** 10 | // * Returns the name of the main component registered from JavaScript. This is used to schedule 11 | // * rendering of the component. 12 | // */ 13 | // @Override 14 | // protected String getMainComponentName() { 15 | // return "KeyboardInput"; 16 | // } 17 | //} 18 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/keyboardinput/MainApplication.java: -------------------------------------------------------------------------------- 1 | package com.keyboardinput; 2 | 3 | import com.facebook.react.ReactNativeHost; 4 | import com.facebook.react.ReactPackage; 5 | import com.reactnativenavigation.NavigationApplication; 6 | import com.reactnativenavigation.react.NavigationReactNativeHost; 7 | import com.reactnativenavigation.react.ReactGateway; 8 | import com.wix.reactnativekeyboardinput.KeyboardInputPackage; 9 | 10 | import java.util.Arrays; 11 | import java.util.List; 12 | 13 | public class MainApplication extends NavigationApplication { 14 | 15 | @Override 16 | protected ReactGateway createReactGateway() { 17 | ReactNativeHost host = new NavigationReactNativeHost(this, isDebug(), createAdditionalReactPackages()) { 18 | @Override 19 | protected String getJSMainModuleName() { 20 | return "index"; 21 | } 22 | }; 23 | return new ReactGateway(this, isDebug(), host); 24 | } 25 | 26 | @Override 27 | public boolean isDebug() { 28 | return BuildConfig.DEBUG; 29 | } 30 | 31 | protected List getPackages() { 32 | // Add additional packages you require here 33 | // No need to add RnnPackage and MainReactPackage 34 | return Arrays.asList( 35 | new KeyboardInputPackage(this) 36 | ); 37 | } 38 | 39 | @Override 40 | public List createAdditionalReactPackages() { 41 | return getPackages(); 42 | } 43 | } 44 | 45 | //public class MainApplication extends Application implements ReactApplication { 46 | // 47 | // private final ReactNativeHost mReactNativeHost = 48 | // new ReactNativeHost(this) { 49 | // @Override 50 | // public boolean getUseDeveloperSupport() { 51 | // return BuildConfig.DEBUG; 52 | // } 53 | // 54 | // @Override 55 | // protected List getPackages() { 56 | // @SuppressWarnings("UnnecessaryLocalVariable") 57 | // List packages = new PackageList(this).getPackages(); 58 | // // Packages that cannot be autolinked yet can be added manually here, for example: 59 | // // packages.add(new MyReactNativePackage()); 60 | // return packages; 61 | // } 62 | // 63 | // @Override 64 | // protected String getJSMainModuleName() { 65 | // return "index"; 66 | // } 67 | // }; 68 | // 69 | // @Override 70 | // public ReactNativeHost getReactNativeHost() { 71 | // return mReactNativeHost; 72 | // } 73 | // 74 | // @Override 75 | // public void onCreate() { 76 | // super.onCreate(); 77 | // SoLoader.init(this, /* native exopackage */ false); 78 | // initializeFlipper(this); // Remove this line if you don't want Flipper enabled 79 | // } 80 | // 81 | // /** 82 | // * Loads Flipper in React Native templates. 83 | // * 84 | // * @param context 85 | // */ 86 | // private static void initializeFlipper(Context context) { 87 | // if (BuildConfig.DEBUG) { 88 | // try { 89 | // /* 90 | // We use reflection here to pick up the class that initializes Flipper, 91 | // since Flipper library is not available in release mode 92 | // */ 93 | // Class aClass = Class.forName("com.facebook.flipper.ReactNativeFlipper"); 94 | // aClass.getMethod("initializeFlipper", Context.class).invoke(null, context); 95 | // } catch (ClassNotFoundException e) { 96 | // e.printStackTrace(); 97 | // } catch (NoSuchMethodException e) { 98 | // e.printStackTrace(); 99 | // } catch (IllegalAccessException e) { 100 | // e.printStackTrace(); 101 | // } catch (InvocationTargetException e) { 102 | // e.printStackTrace(); 103 | // } 104 | // } 105 | // } 106 | //} 107 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wix-incubator/react-native-keyboard-input/acb3a58e96988026f449b48e8b49f49164684d9f/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wix-incubator/react-native-keyboard-input/acb3a58e96988026f449b48e8b49f49164684d9f/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wix-incubator/react-native-keyboard-input/acb3a58e96988026f449b48e8b49f49164684d9f/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wix-incubator/react-native-keyboard-input/acb3a58e96988026f449b48e8b49f49164684d9f/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wix-incubator/react-native-keyboard-input/acb3a58e96988026f449b48e8b49f49164684d9f/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wix-incubator/react-native-keyboard-input/acb3a58e96988026f449b48e8b49f49164684d9f/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wix-incubator/react-native-keyboard-input/acb3a58e96988026f449b48e8b49f49164684d9f/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wix-incubator/react-native-keyboard-input/acb3a58e96988026f449b48e8b49f49164684d9f/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wix-incubator/react-native-keyboard-input/acb3a58e96988026f449b48e8b49f49164684d9f/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wix-incubator/react-native-keyboard-input/acb3a58e96988026f449b48e8b49f49164684d9f/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | KeyboardInput 3 | 4 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | ext { 5 | buildToolsVersion = "28.0.3" 6 | minSdkVersion = 18 7 | compileSdkVersion = 28 8 | targetSdkVersion = 28 9 | } 10 | repositories { 11 | google() 12 | jcenter() 13 | } 14 | dependencies { 15 | classpath("com.android.tools.build:gradle:3.4.2") 16 | 17 | // NOTE: Do not place your application dependencies here; they belong 18 | // in the individual module build.gradle files 19 | } 20 | } 21 | 22 | allprojects { 23 | repositories { 24 | mavenLocal() 25 | maven { 26 | // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm 27 | url("$rootDir/../node_modules/react-native/android") 28 | } 29 | maven { 30 | // Android JSC is installed from npm 31 | url("$rootDir/../node_modules/jsc-android/dist") 32 | } 33 | 34 | google() 35 | jcenter() 36 | maven { url 'https://jitpack.io' } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true 19 | 20 | android.useAndroidX=true 21 | android.enableJetifier=true 22 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wix-incubator/react-native-keyboard-input/acb3a58e96988026f449b48e8b49f49164684d9f/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.5-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /android/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | # Determine the Java command to use to start the JVM. 86 | if [ -n "$JAVA_HOME" ] ; then 87 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 88 | # IBM's JDK on AIX uses strange locations for the executables 89 | JAVACMD="$JAVA_HOME/jre/sh/java" 90 | else 91 | JAVACMD="$JAVA_HOME/bin/java" 92 | fi 93 | if [ ! -x "$JAVACMD" ] ; then 94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 95 | 96 | Please set the JAVA_HOME variable in your environment to match the 97 | location of your Java installation." 98 | fi 99 | else 100 | JAVACMD="java" 101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 102 | 103 | Please set the JAVA_HOME variable in your environment to match the 104 | location of your Java installation." 105 | fi 106 | 107 | # Increase the maximum file descriptors if we can. 108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 109 | MAX_FD_LIMIT=`ulimit -H -n` 110 | if [ $? -eq 0 ] ; then 111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 112 | MAX_FD="$MAX_FD_LIMIT" 113 | fi 114 | ulimit -n $MAX_FD 115 | if [ $? -ne 0 ] ; then 116 | warn "Could not set maximum file descriptor limit: $MAX_FD" 117 | fi 118 | else 119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 120 | fi 121 | fi 122 | 123 | # For Darwin, add options to specify how the application appears in the dock 124 | if $darwin; then 125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 126 | fi 127 | 128 | # For Cygwin, switch paths to Windows format before running java 129 | if $cygwin ; then 130 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 131 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 132 | JAVACMD=`cygpath --unix "$JAVACMD"` 133 | 134 | # We build the pattern for arguments to be converted via cygpath 135 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 136 | SEP="" 137 | for dir in $ROOTDIRSRAW ; do 138 | ROOTDIRS="$ROOTDIRS$SEP$dir" 139 | SEP="|" 140 | done 141 | OURCYGPATTERN="(^($ROOTDIRS))" 142 | # Add a user-defined pattern to the cygpath arguments 143 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 144 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 145 | fi 146 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 147 | i=0 148 | for arg in "$@" ; do 149 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 150 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 151 | 152 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 153 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 154 | else 155 | eval `echo args$i`="\"$arg\"" 156 | fi 157 | i=$((i+1)) 158 | done 159 | case $i in 160 | (0) set -- ;; 161 | (1) set -- "$args0" ;; 162 | (2) set -- "$args0" "$args1" ;; 163 | (3) set -- "$args0" "$args1" "$args2" ;; 164 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 165 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 166 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 167 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 168 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 169 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 170 | esac 171 | fi 172 | 173 | # Escape application args 174 | save () { 175 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 176 | echo " " 177 | } 178 | APP_ARGS=$(save "$@") 179 | 180 | # Collect all arguments for the java command, following the shell quoting and substitution rules 181 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 182 | 183 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 184 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 185 | cd "$(dirname "$0")" 186 | fi 187 | 188 | exec "$JAVACMD" "$@" 189 | -------------------------------------------------------------------------------- /android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem http://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 33 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 34 | 35 | @rem Find java.exe 36 | if defined JAVA_HOME goto findJavaFromJavaHome 37 | 38 | set JAVA_EXE=java.exe 39 | %JAVA_EXE% -version >NUL 2>&1 40 | if "%ERRORLEVEL%" == "0" goto init 41 | 42 | echo. 43 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 44 | echo. 45 | echo Please set the JAVA_HOME variable in your environment to match the 46 | echo location of your Java installation. 47 | 48 | goto fail 49 | 50 | :findJavaFromJavaHome 51 | set JAVA_HOME=%JAVA_HOME:"=% 52 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 53 | 54 | if exist "%JAVA_EXE%" goto init 55 | 56 | echo. 57 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 58 | echo. 59 | echo Please set the JAVA_HOME variable in your environment to match the 60 | echo location of your Java installation. 61 | 62 | goto fail 63 | 64 | :init 65 | @rem Get command-line arguments, handling Windows variants 66 | 67 | if not "%OS%" == "Windows_NT" goto win9xME_args 68 | 69 | :win9xME_args 70 | @rem Slurp the command line arguments. 71 | set CMD_LINE_ARGS= 72 | set _SKIP=2 73 | 74 | :win9xME_args_slurp 75 | if "x%~1" == "x" goto execute 76 | 77 | set CMD_LINE_ARGS=%* 78 | 79 | :execute 80 | @rem Setup the command line 81 | 82 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 83 | 84 | @rem Execute Gradle 85 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 86 | 87 | :end 88 | @rem End local scope for the variables with windows NT shell 89 | if "%ERRORLEVEL%"=="0" goto mainEnd 90 | 91 | :fail 92 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 93 | rem the _cmd.exe /c_ return code! 94 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 95 | exit /b 1 96 | 97 | :mainEnd 98 | if "%OS%"=="Windows_NT" endlocal 99 | 100 | :omega 101 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'KeyboardInput' 2 | apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings) 3 | include ':app' 4 | 5 | include ':reactnativekeyboardinput' 6 | project(':reactnativekeyboardinput').projectDir = new File(rootProject.projectDir, '../lib/android') 7 | 8 | include ':react-native-navigation' 9 | project(':react-native-navigation').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-navigation/lib/android/app/') 10 | -------------------------------------------------------------------------------- /demo/demoKeyboards.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import {Text, TouchableOpacity, ScrollView, StyleSheet} from 'react-native'; 4 | import {KeyboardRegistry} from 'react-native-keyboard-input'; 5 | 6 | class KeyboardView extends Component { 7 | static propTypes = { 8 | title: PropTypes.string, 9 | }; 10 | 11 | onButtonPress() { 12 | KeyboardRegistry.onItemSelected('KeyboardView', { 13 | message: 'item selected from KeyboardView', 14 | }); 15 | } 16 | 17 | render() { 18 | return ( 19 | 20 | HELOOOO!!! 21 | {this.props.title} 22 | this.onButtonPress()} 26 | > 27 | 28 | Click Me! 29 | 30 | 31 | 32 | ); 33 | } 34 | } 35 | 36 | class AnotherKeyboardView extends Component { 37 | static propTypes = { 38 | title: PropTypes.string, 39 | }; 40 | 41 | onButtonPress() { 42 | KeyboardRegistry.toggleExpandedKeyboard('AnotherKeyboardView'); 43 | } 44 | 45 | render() { 46 | return ( 47 | 48 | *** ANOTHER ONE *** 49 | {this.props.title} 50 | this.onButtonPress()} 54 | > 55 | 56 | Toggle Full-Screen! 57 | 58 | 59 | 60 | ); 61 | } 62 | } 63 | 64 | const styles = StyleSheet.create({ 65 | keyboardContainer: { 66 | flex: 1, 67 | flexDirection: 'column', 68 | alignItems: 'center', 69 | justifyContent: 'center', 70 | }, 71 | }); 72 | 73 | KeyboardRegistry.registerKeyboard('KeyboardView', () => KeyboardView); 74 | KeyboardRegistry.registerKeyboard('AnotherKeyboardView', () => AnotherKeyboardView); 75 | -------------------------------------------------------------------------------- /demo/demoRoot.android.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { 3 | Text, 4 | View, 5 | TouchableOpacity, 6 | } from 'react-native'; 7 | import PropTypes from 'prop-types'; 8 | import {Navigation} from 'react-native-navigation'; 9 | 10 | export default class KeyboardInput extends Component { 11 | static propTypes = { 12 | componentId: PropTypes.string, 13 | }; 14 | 15 | constructor(props) { 16 | super(props); 17 | this.onPressBodyMessage = this.onPressBodyMessage.bind(this); 18 | } 19 | 20 | onPressBodyMessage() { 21 | Navigation.push(this.props.componentId, { 22 | component: { 23 | name: 'screens.innerScreen', 24 | passProps: { 25 | message: 'In the secondary tab, the keyboard input is inside a pushed screen, yet it works nonetheless! :-)', 26 | }, 27 | options: { 28 | topBar: { 29 | title: { 30 | text: 'Second screen', 31 | }, 32 | }, 33 | }, 34 | }, 35 | }); 36 | } 37 | 38 | render() { 39 | return ( 40 | 41 | 42 | Tap for more keyboard fun... 43 | 44 | 45 | ); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /demo/demoScreen.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { 3 | StyleSheet, 4 | Text, 5 | View, 6 | ScrollView, 7 | TouchableOpacity, 8 | PixelRatio, 9 | Platform, 10 | Switch, 11 | } from 'react-native'; 12 | import PropTypes from 'prop-types'; 13 | import {AutoGrowingTextInput} from 'react-native-autogrow-textinput'; 14 | import {KeyboardAccessoryView, KeyboardUtils} from 'react-native-keyboard-input'; 15 | import {KeyboardRegistry} from 'react-native-keyboard-input'; 16 | import {_} from 'lodash'; 17 | 18 | import './demoKeyboards'; 19 | import CustomKeyboardView from '../src/CustomKeyboardView'; 20 | 21 | const IsIOS = Platform.OS === 'ios'; 22 | const TrackInteractive = true; 23 | 24 | export default class KeyboardInput extends Component { 25 | static propTypes = { 26 | message: PropTypes.string, 27 | }; 28 | 29 | constructor(props) { 30 | super(props); 31 | this.keyboardAccessoryViewContent = this.keyboardAccessoryViewContent.bind(this); 32 | this.onKeyboardItemSelected = this.onKeyboardItemSelected.bind(this); 33 | this.resetKeyboardView = this.resetKeyboardView.bind(this); 34 | this.onKeyboardResigned = this.onKeyboardResigned.bind(this); 35 | this.showLastKeyboard = this.showLastKeyboard.bind(this); 36 | this.isCustomKeyboardOpen = this.isCustomKeyboardOpen.bind(this); 37 | 38 | this.state = { 39 | customKeyboard: { 40 | component: undefined, 41 | initialProps: undefined, 42 | }, 43 | receivedKeyboardData: undefined, 44 | useSafeArea: true, 45 | keyboardOpenState: false, 46 | }; 47 | } 48 | 49 | onKeyboardItemSelected(keyboardId, params) { 50 | const receivedKeyboardData = `onItemSelected from "${keyboardId}"\nreceived params: ${JSON.stringify(params)}`; 51 | this.setState({receivedKeyboardData}); 52 | } 53 | 54 | onKeyboardResigned() { 55 | this.setState({keyboardOpenState: false}); 56 | this.resetKeyboardView(); 57 | } 58 | 59 | getToolbarButtons() { 60 | return [ 61 | { 62 | text: 'show1', 63 | testID: 'show1', 64 | onPress: () => this.showKeyboardView('KeyboardView', 'FIRST - 1 (passed prop)'), 65 | }, 66 | { 67 | text: 'show2', 68 | testID: 'show2', 69 | onPress: () => this.showKeyboardView('AnotherKeyboardView', 'SECOND - 2 (passed prop)'), 70 | }, 71 | { 72 | text: 'reset', 73 | testID: 'reset', 74 | onPress: () => this.resetKeyboardView(), 75 | }, 76 | ]; 77 | } 78 | 79 | resetKeyboardView() { 80 | this.setState({customKeyboard: {}}); 81 | } 82 | 83 | showKeyboardView(component, title) { 84 | this.setState({ 85 | keyboardOpenState: true, 86 | customKeyboard: { 87 | component, 88 | initialProps: {title}, 89 | }, 90 | }); 91 | } 92 | 93 | dismissKeyboard() { 94 | KeyboardUtils.dismiss(); 95 | } 96 | 97 | showLastKeyboard() { 98 | const {customKeyboard} = this.state; 99 | this.setState({customKeyboard: {}}); 100 | 101 | setTimeout(() => { 102 | this.setState({ 103 | keyboardOpenState: true, 104 | customKeyboard, 105 | }); 106 | }, 500); 107 | } 108 | 109 | isCustomKeyboardOpen = () => { 110 | const {keyboardOpenState, customKeyboard} = this.state; 111 | return keyboardOpenState && !_.isEmpty(customKeyboard); 112 | } 113 | 114 | toggleUseSafeArea = () => { 115 | const {useSafeArea} = this.state; 116 | this.setState({useSafeArea: !useSafeArea}); 117 | 118 | if (this.isCustomKeyboardOpen()) { 119 | this.dismissKeyboard(); 120 | this.showLastKeyboard(); 121 | } 122 | } 123 | 124 | safeAreaSwitchToggle = () => { 125 | if (!IsIOS) { 126 | return (); 127 | } 128 | const {useSafeArea} = this.state; 129 | return ( 130 | 131 | Safe Area Enabled: 132 | 133 | 134 | ); 135 | } 136 | 137 | keyboardAccessoryViewContent() { 138 | return ( 139 | 140 | 141 | 142 | { 146 | this.textInputRef = r; 147 | }} 148 | placeholder={'Message'} 149 | underlineColorAndroid="transparent" 150 | onFocus={() => this.resetKeyboardView()} 151 | testID={'input'} 152 | /> 153 | KeyboardUtils.dismiss()}> 154 | Action 155 | 156 | 157 | 158 | { 159 | this.getToolbarButtons().map((button, index) => 160 | 166 | {button.text} 167 | ) 168 | } 169 | 170 | 171 | ); 172 | } 173 | 174 | render() { 175 | return ( 176 | 177 | 178 | 182 | {this.props.message ? this.props.message : 'Keyboards example'} 183 | {this.state.receivedKeyboardData} 184 | { this.safeAreaSwitchToggle() } 185 | 186 | 187 | this.setState({keyboardAccessoryViewHeight: IsIOS ? height : undefined})} 190 | trackInteractive={TrackInteractive} 191 | kbInputRef={this.textInputRef} 192 | kbComponent={this.state.customKeyboard.component} 193 | kbInitialProps={this.state.customKeyboard.initialProps} 194 | onItemSelected={this.onKeyboardItemSelected} 195 | onKeyboardResigned={this.onKeyboardResigned} 196 | revealKeyboardInteractive 197 | useSafeArea={this.state.useSafeArea} 198 | /> 199 | 200 | ); 201 | } 202 | } 203 | 204 | const COLOR = '#F5FCFF'; 205 | 206 | const styles = StyleSheet.create({ 207 | container: { 208 | flex: 1, 209 | backgroundColor: COLOR, 210 | }, 211 | scrollContainer: { 212 | justifyContent: 'center', 213 | padding: 15, 214 | flex: 1, 215 | }, 216 | welcome: { 217 | fontSize: 20, 218 | textAlign: 'center', 219 | margin: 10, 220 | paddingTop: 50, 221 | paddingBottom: 50, 222 | }, 223 | inputContainer: { 224 | flexDirection: 'row', 225 | alignItems: 'center', 226 | justifyContent: 'space-between', 227 | marginBottom: 25, 228 | }, 229 | keyboardContainer: { 230 | ...Platform.select({ 231 | ios: { 232 | flex: 1, 233 | backgroundColor: COLOR, 234 | }, 235 | }), 236 | }, 237 | textInput: { 238 | flex: 1, 239 | marginLeft: 10, 240 | marginTop: 10, 241 | marginBottom: 10, 242 | paddingLeft: 10, 243 | paddingTop: 2, 244 | paddingBottom: 5, 245 | fontSize: 16, 246 | backgroundColor: 'white', 247 | borderWidth: 0.5 / PixelRatio.get(), 248 | borderRadius: 18, 249 | }, 250 | sendButton: { 251 | paddingRight: 15, 252 | paddingLeft: 15, 253 | alignSelf: 'center', 254 | }, 255 | switch: { 256 | marginLeft: 15, 257 | }, 258 | safeAreaSwitchContainer: { 259 | flexDirection: 'row', 260 | alignItems: 'center', 261 | justifyContent: 'center', 262 | }, 263 | }); 264 | -------------------------------------------------------------------------------- /demo/res/settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wix-incubator/react-native-keyboard-input/acb3a58e96988026f449b48e8b49f49164684d9f/demo/res/settings.png -------------------------------------------------------------------------------- /demo/res/star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wix-incubator/react-native-keyboard-input/acb3a58e96988026f449b48e8b49f49164684d9f/demo/res/star.png -------------------------------------------------------------------------------- /e2e/init.js: -------------------------------------------------------------------------------- 1 | require('babel-polyfill'); 2 | const detox = require('detox'); 3 | const config = require('../package.json').detox; 4 | 5 | before(async () => { 6 | await detox.init(config); 7 | }); 8 | 9 | after(async () => { 10 | await detox.cleanup(); 11 | }); -------------------------------------------------------------------------------- /e2e/mocha.opts: -------------------------------------------------------------------------------- 1 | --recursive --timeout 120000 --bail -------------------------------------------------------------------------------- /e2e/sanity.spec.js: -------------------------------------------------------------------------------- 1 | describe('Sanity', () => { 2 | beforeEach(async () => { 3 | await device.reloadReactNative(); 4 | }); 5 | 6 | it('should have example screen', async () => { 7 | await expect(element(by.label('Keyboards example'))).toBeVisible(); 8 | }); 9 | 10 | it('should switch between keyboards', async () => { 11 | await element(by.id('input')).tap(); 12 | await element(by.id('show1')).tap(); 13 | await expect(element(by.label('HELOOOO!!!'))).toBeVisible(); 14 | await element(by.id('show2')).tap(); 15 | await expect(element(by.label('*** ANOTHER ONE ***'))).toBeVisible(); 16 | await element(by.id('reset')).tap(); 17 | await expect(element(by.label('Keyboards example'))).toBeVisible(); 18 | }); 19 | 20 | it('should select item on the first demo keyboard', async () => { 21 | await element(by.id('input')).tap(); 22 | await element(by.id('show1')).tap(); 23 | await expect(element(by.label('HELOOOO!!!'))).toBeVisible(); 24 | await element(by.id('click-me')).tap(); 25 | await expect(element(by.id('demo-message'))).toBeVisible(); 26 | }); 27 | 28 | it('should expand the keyboard', async () => { 29 | await element(by.id('input')).tap(); 30 | await expect(element(by.id('show2'))).toBeVisible(); 31 | await element(by.id('show2')).tap(); 32 | await element(by.id('toggle-fs')).tap(); 33 | await expect(element(by.id('show2'))).toBeNotVisible(); 34 | await element(by.id('toggle-fs')).tap(); 35 | await expect(element(by.id('show2'))).toBeVisible(); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /index.android.js: -------------------------------------------------------------------------------- 1 | import KeyboardAppScreen from './demo/demoScreen'; 2 | import DemoRootScreen from './demo/demoRoot'; 3 | 4 | import {Navigation} from 'react-native-navigation'; 5 | 6 | Navigation.registerComponent('screens.star', () => KeyboardAppScreen); 7 | Navigation.registerComponent('screens.settings', () => DemoRootScreen); 8 | Navigation.registerComponent('screens.innerScreen', () => KeyboardAppScreen); 9 | 10 | Navigation.setRoot({ 11 | root: { 12 | bottomTabs: { 13 | children: [{ 14 | stack: { 15 | children: [{ 16 | component: { 17 | name: 'screens.star', 18 | passProps: { 19 | message: 'On the main tab, the keyboard input is in the root screen!', 20 | }, 21 | }, 22 | }], 23 | options: { 24 | bottomTab: { 25 | text: 'Main Tab', 26 | icon: require('./demo/res/star.png'), 27 | }, 28 | topBar: { 29 | title: { 30 | text: 'Main Tab', 31 | }, 32 | }, 33 | }, 34 | }, 35 | }, 36 | { 37 | stack: { 38 | children: [{ 39 | component: { 40 | name: 'screens.settings', 41 | passProps: { 42 | message: 'Secondary Tab', 43 | }, 44 | }, 45 | }], 46 | options: { 47 | bottomTab: { 48 | text: 'Second Tab', 49 | icon: require('./demo/res/settings.png'), 50 | }, 51 | topBar: { 52 | title: { 53 | text: 'Second Tab', 54 | }, 55 | }, 56 | }, 57 | }, 58 | }], 59 | }, 60 | }, 61 | }); 62 | -------------------------------------------------------------------------------- /index.ios.js: -------------------------------------------------------------------------------- 1 | import {AppRegistry} from 'react-native'; 2 | import KeyboardInput from './demo/demoScreen'; 3 | 4 | AppRegistry.registerComponent('KeyboardInput', () => KeyboardInput); 5 | -------------------------------------------------------------------------------- /ios/KeyboardInput-tvOS/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | NSAppTransportSecurity 26 | 27 | NSExceptionDomains 28 | 29 | localhost 30 | 31 | NSExceptionAllowsInsecureHTTPLoads 32 | 33 | 34 | 35 | 36 | NSLocationWhenInUseUsageDescription 37 | 38 | UILaunchStoryboardName 39 | LaunchScreen 40 | UIRequiredDeviceCapabilities 41 | 42 | armv7 43 | 44 | UISupportedInterfaceOrientations 45 | 46 | UIInterfaceOrientationPortrait 47 | UIInterfaceOrientationLandscapeLeft 48 | UIInterfaceOrientationLandscapeRight 49 | 50 | UIViewControllerBasedStatusBarAppearance 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /ios/KeyboardInput-tvOSTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /ios/KeyboardInput.xcodeproj/xcshareddata/xcschemes/KeyboardInput-tvOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 43 | 49 | 50 | 51 | 52 | 53 | 58 | 59 | 61 | 67 | 68 | 69 | 70 | 71 | 77 | 78 | 79 | 80 | 81 | 82 | 92 | 94 | 100 | 101 | 102 | 103 | 104 | 105 | 111 | 113 | 119 | 120 | 121 | 122 | 124 | 125 | 128 | 129 | 130 | -------------------------------------------------------------------------------- /ios/KeyboardInput.xcodeproj/xcshareddata/xcschemes/KeyboardInput.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 43 | 49 | 50 | 51 | 52 | 53 | 58 | 59 | 61 | 67 | 68 | 69 | 70 | 71 | 77 | 78 | 79 | 80 | 81 | 82 | 92 | 94 | 100 | 101 | 102 | 103 | 104 | 105 | 111 | 113 | 119 | 120 | 121 | 122 | 124 | 125 | 128 | 129 | 130 | -------------------------------------------------------------------------------- /ios/KeyboardInput.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/KeyboardInput.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/KeyboardInput/AppDelegate.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | #import 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (nonatomic, strong) UIWindow *window; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /ios/KeyboardInput/AppDelegate.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | #import "AppDelegate.h" 9 | 10 | #import 11 | #import 12 | #import 13 | 14 | @implementation AppDelegate 15 | 16 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 17 | { 18 | RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions]; 19 | RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge 20 | moduleName:@"KeyboardInput" 21 | initialProperties:nil]; 22 | 23 | rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1]; 24 | 25 | self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; 26 | UIViewController *rootViewController = [UIViewController new]; 27 | rootViewController.view = rootView; 28 | self.window.rootViewController = rootViewController; 29 | [self.window makeKeyAndVisible]; 30 | return YES; 31 | } 32 | 33 | - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge 34 | { 35 | #if DEBUG 36 | return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil]; 37 | #else 38 | return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; 39 | #endif 40 | } 41 | 42 | @end 43 | -------------------------------------------------------------------------------- /ios/KeyboardInput/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 21 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /ios/KeyboardInput/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /ios/KeyboardInput/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /ios/KeyboardInput/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | KeyboardInput 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | LSRequiresIPhoneOS 26 | 27 | NSAppTransportSecurity 28 | 29 | NSAllowsArbitraryLoads 30 | 31 | NSExceptionDomains 32 | 33 | localhost 34 | 35 | NSExceptionAllowsInsecureHTTPLoads 36 | 37 | 38 | 39 | 40 | NSLocationWhenInUseUsageDescription 41 | 42 | UILaunchStoryboardName 43 | LaunchScreen 44 | UIRequiredDeviceCapabilities 45 | 46 | armv7 47 | 48 | UISupportedInterfaceOrientations 49 | 50 | UIInterfaceOrientationPortrait 51 | UIInterfaceOrientationLandscapeLeft 52 | UIInterfaceOrientationLandscapeRight 53 | 54 | UIViewControllerBasedStatusBarAppearance 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /ios/KeyboardInput/main.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | #import 9 | 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /ios/KeyboardInputTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /ios/KeyboardInputTests/KeyboardInputTests.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | #import 9 | #import 10 | 11 | #import 12 | #import 13 | 14 | #define TIMEOUT_SECONDS 600 15 | #define TEXT_TO_LOOK_FOR @"Welcome to React" 16 | 17 | @interface KeyboardInputTests : XCTestCase 18 | 19 | @end 20 | 21 | @implementation KeyboardInputTests 22 | 23 | - (BOOL)findSubviewInView:(UIView *)view matching:(BOOL(^)(UIView *view))test 24 | { 25 | if (test(view)) { 26 | return YES; 27 | } 28 | for (UIView *subview in [view subviews]) { 29 | if ([self findSubviewInView:subview matching:test]) { 30 | return YES; 31 | } 32 | } 33 | return NO; 34 | } 35 | 36 | - (void)testRendersWelcomeScreen 37 | { 38 | UIViewController *vc = [[[RCTSharedApplication() delegate] window] rootViewController]; 39 | NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS]; 40 | BOOL foundElement = NO; 41 | 42 | __block NSString *redboxError = nil; 43 | #ifdef DEBUG 44 | RCTSetLogFunction(^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) { 45 | if (level >= RCTLogLevelError) { 46 | redboxError = message; 47 | } 48 | }); 49 | #endif 50 | 51 | while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) { 52 | [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 53 | [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 54 | 55 | foundElement = [self findSubviewInView:vc.view matching:^BOOL(UIView *view) { 56 | if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) { 57 | return YES; 58 | } 59 | return NO; 60 | }]; 61 | } 62 | 63 | #ifdef DEBUG 64 | RCTSetLogFunction(RCTDefaultLogFunction); 65 | #endif 66 | 67 | XCTAssertNil(redboxError, @"RedBox error: %@", redboxError); 68 | XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS); 69 | } 70 | 71 | 72 | @end 73 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | platform :ios, '9.0' 2 | require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules' 3 | 4 | target 'KeyboardInput' do 5 | 6 | pod 'ReactNativeKeyboardInput', :path => '../ReactNativeKeyboardInput.podspec' 7 | 8 | # Pods for KeyboardInput 9 | pod 'FBLazyVector', :path => "../node_modules/react-native/Libraries/FBLazyVector" 10 | pod 'FBReactNativeSpec', :path => "../node_modules/react-native/Libraries/FBReactNativeSpec" 11 | pod 'RCTRequired', :path => "../node_modules/react-native/Libraries/RCTRequired" 12 | pod 'RCTTypeSafety', :path => "../node_modules/react-native/Libraries/TypeSafety" 13 | pod 'React', :path => '../node_modules/react-native/' 14 | pod 'React-Core', :path => '../node_modules/react-native/' 15 | pod 'React-CoreModules', :path => '../node_modules/react-native/React/CoreModules' 16 | pod 'React-Core/DevSupport', :path => '../node_modules/react-native/' 17 | pod 'React-RCTActionSheet', :path => '../node_modules/react-native/Libraries/ActionSheetIOS' 18 | pod 'React-RCTAnimation', :path => '../node_modules/react-native/Libraries/NativeAnimation' 19 | pod 'React-RCTBlob', :path => '../node_modules/react-native/Libraries/Blob' 20 | pod 'React-RCTImage', :path => '../node_modules/react-native/Libraries/Image' 21 | pod 'React-RCTLinking', :path => '../node_modules/react-native/Libraries/LinkingIOS' 22 | pod 'React-RCTNetwork', :path => '../node_modules/react-native/Libraries/Network' 23 | pod 'React-RCTSettings', :path => '../node_modules/react-native/Libraries/Settings' 24 | pod 'React-RCTText', :path => '../node_modules/react-native/Libraries/Text' 25 | pod 'React-RCTVibration', :path => '../node_modules/react-native/Libraries/Vibration' 26 | pod 'React-Core/RCTWebSocket', :path => '../node_modules/react-native/' 27 | 28 | pod 'React-cxxreact', :path => '../node_modules/react-native/ReactCommon/cxxreact' 29 | pod 'React-jsi', :path => '../node_modules/react-native/ReactCommon/jsi' 30 | pod 'React-jsiexecutor', :path => '../node_modules/react-native/ReactCommon/jsiexecutor' 31 | pod 'React-jsinspector', :path => '../node_modules/react-native/ReactCommon/jsinspector' 32 | pod 'ReactCommon/jscallinvoker', :path => "../node_modules/react-native/ReactCommon" 33 | pod 'ReactCommon/turbomodule/core', :path => "../node_modules/react-native/ReactCommon" 34 | pod 'Yoga', :path => '../node_modules/react-native/ReactCommon/yoga' 35 | 36 | pod 'DoubleConversion', :podspec => '../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec' 37 | pod 'glog', :podspec => '../node_modules/react-native/third-party-podspecs/glog.podspec' 38 | pod 'Folly', :podspec => '../node_modules/react-native/third-party-podspecs/Folly.podspec' 39 | 40 | use_native_modules! 41 | end 42 | -------------------------------------------------------------------------------- /ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - boost-for-react-native (1.63.0) 3 | - DoubleConversion (1.1.6) 4 | - FBLazyVector (0.61.4) 5 | - FBReactNativeSpec (0.61.4): 6 | - Folly (= 2018.10.22.00) 7 | - RCTRequired (= 0.61.4) 8 | - RCTTypeSafety (= 0.61.4) 9 | - React-Core (= 0.61.4) 10 | - React-jsi (= 0.61.4) 11 | - ReactCommon/turbomodule/core (= 0.61.4) 12 | - Folly (2018.10.22.00): 13 | - boost-for-react-native 14 | - DoubleConversion 15 | - Folly/Default (= 2018.10.22.00) 16 | - glog 17 | - Folly/Default (2018.10.22.00): 18 | - boost-for-react-native 19 | - DoubleConversion 20 | - glog 21 | - glog (0.3.5) 22 | - RCTRequired (0.61.4) 23 | - RCTTypeSafety (0.61.4): 24 | - FBLazyVector (= 0.61.4) 25 | - Folly (= 2018.10.22.00) 26 | - RCTRequired (= 0.61.4) 27 | - React-Core (= 0.61.4) 28 | - React (0.61.4): 29 | - React-Core (= 0.61.4) 30 | - React-Core/DevSupport (= 0.61.4) 31 | - React-Core/RCTWebSocket (= 0.61.4) 32 | - React-RCTActionSheet (= 0.61.4) 33 | - React-RCTAnimation (= 0.61.4) 34 | - React-RCTBlob (= 0.61.4) 35 | - React-RCTImage (= 0.61.4) 36 | - React-RCTLinking (= 0.61.4) 37 | - React-RCTNetwork (= 0.61.4) 38 | - React-RCTSettings (= 0.61.4) 39 | - React-RCTText (= 0.61.4) 40 | - React-RCTVibration (= 0.61.4) 41 | - React-Core (0.61.4): 42 | - Folly (= 2018.10.22.00) 43 | - glog 44 | - React-Core/Default (= 0.61.4) 45 | - React-cxxreact (= 0.61.4) 46 | - React-jsi (= 0.61.4) 47 | - React-jsiexecutor (= 0.61.4) 48 | - Yoga 49 | - React-Core/CoreModulesHeaders (0.61.4): 50 | - Folly (= 2018.10.22.00) 51 | - glog 52 | - React-Core/Default 53 | - React-cxxreact (= 0.61.4) 54 | - React-jsi (= 0.61.4) 55 | - React-jsiexecutor (= 0.61.4) 56 | - Yoga 57 | - React-Core/Default (0.61.4): 58 | - Folly (= 2018.10.22.00) 59 | - glog 60 | - React-cxxreact (= 0.61.4) 61 | - React-jsi (= 0.61.4) 62 | - React-jsiexecutor (= 0.61.4) 63 | - Yoga 64 | - React-Core/DevSupport (0.61.4): 65 | - Folly (= 2018.10.22.00) 66 | - glog 67 | - React-Core/Default (= 0.61.4) 68 | - React-Core/RCTWebSocket (= 0.61.4) 69 | - React-cxxreact (= 0.61.4) 70 | - React-jsi (= 0.61.4) 71 | - React-jsiexecutor (= 0.61.4) 72 | - React-jsinspector (= 0.61.4) 73 | - Yoga 74 | - React-Core/RCTActionSheetHeaders (0.61.4): 75 | - Folly (= 2018.10.22.00) 76 | - glog 77 | - React-Core/Default 78 | - React-cxxreact (= 0.61.4) 79 | - React-jsi (= 0.61.4) 80 | - React-jsiexecutor (= 0.61.4) 81 | - Yoga 82 | - React-Core/RCTAnimationHeaders (0.61.4): 83 | - Folly (= 2018.10.22.00) 84 | - glog 85 | - React-Core/Default 86 | - React-cxxreact (= 0.61.4) 87 | - React-jsi (= 0.61.4) 88 | - React-jsiexecutor (= 0.61.4) 89 | - Yoga 90 | - React-Core/RCTBlobHeaders (0.61.4): 91 | - Folly (= 2018.10.22.00) 92 | - glog 93 | - React-Core/Default 94 | - React-cxxreact (= 0.61.4) 95 | - React-jsi (= 0.61.4) 96 | - React-jsiexecutor (= 0.61.4) 97 | - Yoga 98 | - React-Core/RCTImageHeaders (0.61.4): 99 | - Folly (= 2018.10.22.00) 100 | - glog 101 | - React-Core/Default 102 | - React-cxxreact (= 0.61.4) 103 | - React-jsi (= 0.61.4) 104 | - React-jsiexecutor (= 0.61.4) 105 | - Yoga 106 | - React-Core/RCTLinkingHeaders (0.61.4): 107 | - Folly (= 2018.10.22.00) 108 | - glog 109 | - React-Core/Default 110 | - React-cxxreact (= 0.61.4) 111 | - React-jsi (= 0.61.4) 112 | - React-jsiexecutor (= 0.61.4) 113 | - Yoga 114 | - React-Core/RCTNetworkHeaders (0.61.4): 115 | - Folly (= 2018.10.22.00) 116 | - glog 117 | - React-Core/Default 118 | - React-cxxreact (= 0.61.4) 119 | - React-jsi (= 0.61.4) 120 | - React-jsiexecutor (= 0.61.4) 121 | - Yoga 122 | - React-Core/RCTSettingsHeaders (0.61.4): 123 | - Folly (= 2018.10.22.00) 124 | - glog 125 | - React-Core/Default 126 | - React-cxxreact (= 0.61.4) 127 | - React-jsi (= 0.61.4) 128 | - React-jsiexecutor (= 0.61.4) 129 | - Yoga 130 | - React-Core/RCTTextHeaders (0.61.4): 131 | - Folly (= 2018.10.22.00) 132 | - glog 133 | - React-Core/Default 134 | - React-cxxreact (= 0.61.4) 135 | - React-jsi (= 0.61.4) 136 | - React-jsiexecutor (= 0.61.4) 137 | - Yoga 138 | - React-Core/RCTVibrationHeaders (0.61.4): 139 | - Folly (= 2018.10.22.00) 140 | - glog 141 | - React-Core/Default 142 | - React-cxxreact (= 0.61.4) 143 | - React-jsi (= 0.61.4) 144 | - React-jsiexecutor (= 0.61.4) 145 | - Yoga 146 | - React-Core/RCTWebSocket (0.61.4): 147 | - Folly (= 2018.10.22.00) 148 | - glog 149 | - React-Core/Default (= 0.61.4) 150 | - React-cxxreact (= 0.61.4) 151 | - React-jsi (= 0.61.4) 152 | - React-jsiexecutor (= 0.61.4) 153 | - Yoga 154 | - React-CoreModules (0.61.4): 155 | - FBReactNativeSpec (= 0.61.4) 156 | - Folly (= 2018.10.22.00) 157 | - RCTTypeSafety (= 0.61.4) 158 | - React-Core/CoreModulesHeaders (= 0.61.4) 159 | - React-RCTImage (= 0.61.4) 160 | - ReactCommon/turbomodule/core (= 0.61.4) 161 | - React-cxxreact (0.61.4): 162 | - boost-for-react-native (= 1.63.0) 163 | - DoubleConversion 164 | - Folly (= 2018.10.22.00) 165 | - glog 166 | - React-jsinspector (= 0.61.4) 167 | - React-jsi (0.61.4): 168 | - boost-for-react-native (= 1.63.0) 169 | - DoubleConversion 170 | - Folly (= 2018.10.22.00) 171 | - glog 172 | - React-jsi/Default (= 0.61.4) 173 | - React-jsi/Default (0.61.4): 174 | - boost-for-react-native (= 1.63.0) 175 | - DoubleConversion 176 | - Folly (= 2018.10.22.00) 177 | - glog 178 | - React-jsiexecutor (0.61.4): 179 | - DoubleConversion 180 | - Folly (= 2018.10.22.00) 181 | - glog 182 | - React-cxxreact (= 0.61.4) 183 | - React-jsi (= 0.61.4) 184 | - React-jsinspector (0.61.4) 185 | - React-RCTActionSheet (0.61.4): 186 | - React-Core/RCTActionSheetHeaders (= 0.61.4) 187 | - React-RCTAnimation (0.61.4): 188 | - React-Core/RCTAnimationHeaders (= 0.61.4) 189 | - React-RCTBlob (0.61.4): 190 | - React-Core/RCTBlobHeaders (= 0.61.4) 191 | - React-Core/RCTWebSocket (= 0.61.4) 192 | - React-jsi (= 0.61.4) 193 | - React-RCTNetwork (= 0.61.4) 194 | - React-RCTImage (0.61.4): 195 | - React-Core/RCTImageHeaders (= 0.61.4) 196 | - React-RCTNetwork (= 0.61.4) 197 | - React-RCTLinking (0.61.4): 198 | - React-Core/RCTLinkingHeaders (= 0.61.4) 199 | - React-RCTNetwork (0.61.4): 200 | - React-Core/RCTNetworkHeaders (= 0.61.4) 201 | - React-RCTSettings (0.61.4): 202 | - React-Core/RCTSettingsHeaders (= 0.61.4) 203 | - React-RCTText (0.61.4): 204 | - React-Core/RCTTextHeaders (= 0.61.4) 205 | - React-RCTVibration (0.61.4): 206 | - React-Core/RCTVibrationHeaders (= 0.61.4) 207 | - ReactCommon/jscallinvoker (0.61.4): 208 | - DoubleConversion 209 | - Folly (= 2018.10.22.00) 210 | - glog 211 | - React-cxxreact (= 0.61.4) 212 | - ReactCommon/turbomodule/core (0.61.4): 213 | - DoubleConversion 214 | - Folly (= 2018.10.22.00) 215 | - glog 216 | - React-Core (= 0.61.4) 217 | - React-cxxreact (= 0.61.4) 218 | - React-jsi (= 0.61.4) 219 | - ReactCommon/jscallinvoker (= 0.61.4) 220 | - ReactNativeKeyboardInput (6.0.1): 221 | - React 222 | - ReactNativeKeyboardTrackingView (5.6.1): 223 | - React 224 | - ReactNativeNavigation (3.5.1): 225 | - React 226 | - Yoga (1.14.0) 227 | 228 | DEPENDENCIES: 229 | - DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`) 230 | - FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`) 231 | - FBReactNativeSpec (from `../node_modules/react-native/Libraries/FBReactNativeSpec`) 232 | - Folly (from `../node_modules/react-native/third-party-podspecs/Folly.podspec`) 233 | - glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`) 234 | - RCTRequired (from `../node_modules/react-native/Libraries/RCTRequired`) 235 | - RCTTypeSafety (from `../node_modules/react-native/Libraries/TypeSafety`) 236 | - React (from `../node_modules/react-native/`) 237 | - React-Core (from `../node_modules/react-native/`) 238 | - React-Core/DevSupport (from `../node_modules/react-native/`) 239 | - React-Core/RCTWebSocket (from `../node_modules/react-native/`) 240 | - React-CoreModules (from `../node_modules/react-native/React/CoreModules`) 241 | - React-cxxreact (from `../node_modules/react-native/ReactCommon/cxxreact`) 242 | - React-jsi (from `../node_modules/react-native/ReactCommon/jsi`) 243 | - React-jsiexecutor (from `../node_modules/react-native/ReactCommon/jsiexecutor`) 244 | - React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector`) 245 | - React-RCTActionSheet (from `../node_modules/react-native/Libraries/ActionSheetIOS`) 246 | - React-RCTAnimation (from `../node_modules/react-native/Libraries/NativeAnimation`) 247 | - React-RCTBlob (from `../node_modules/react-native/Libraries/Blob`) 248 | - React-RCTImage (from `../node_modules/react-native/Libraries/Image`) 249 | - React-RCTLinking (from `../node_modules/react-native/Libraries/LinkingIOS`) 250 | - React-RCTNetwork (from `../node_modules/react-native/Libraries/Network`) 251 | - React-RCTSettings (from `../node_modules/react-native/Libraries/Settings`) 252 | - React-RCTText (from `../node_modules/react-native/Libraries/Text`) 253 | - React-RCTVibration (from `../node_modules/react-native/Libraries/Vibration`) 254 | - ReactCommon/jscallinvoker (from `../node_modules/react-native/ReactCommon`) 255 | - ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`) 256 | - ReactNativeKeyboardInput (from `../ReactNativeKeyboardInput.podspec`) 257 | - ReactNativeKeyboardTrackingView (from `../node_modules/react-native-keyboard-tracking-view`) 258 | - ReactNativeNavigation (from `../node_modules/react-native-navigation`) 259 | - Yoga (from `../node_modules/react-native/ReactCommon/yoga`) 260 | 261 | SPEC REPOS: 262 | trunk: 263 | - boost-for-react-native 264 | 265 | EXTERNAL SOURCES: 266 | DoubleConversion: 267 | :podspec: "../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec" 268 | FBLazyVector: 269 | :path: "../node_modules/react-native/Libraries/FBLazyVector" 270 | FBReactNativeSpec: 271 | :path: "../node_modules/react-native/Libraries/FBReactNativeSpec" 272 | Folly: 273 | :podspec: "../node_modules/react-native/third-party-podspecs/Folly.podspec" 274 | glog: 275 | :podspec: "../node_modules/react-native/third-party-podspecs/glog.podspec" 276 | RCTRequired: 277 | :path: "../node_modules/react-native/Libraries/RCTRequired" 278 | RCTTypeSafety: 279 | :path: "../node_modules/react-native/Libraries/TypeSafety" 280 | React: 281 | :path: "../node_modules/react-native/" 282 | React-Core: 283 | :path: "../node_modules/react-native/" 284 | React-CoreModules: 285 | :path: "../node_modules/react-native/React/CoreModules" 286 | React-cxxreact: 287 | :path: "../node_modules/react-native/ReactCommon/cxxreact" 288 | React-jsi: 289 | :path: "../node_modules/react-native/ReactCommon/jsi" 290 | React-jsiexecutor: 291 | :path: "../node_modules/react-native/ReactCommon/jsiexecutor" 292 | React-jsinspector: 293 | :path: "../node_modules/react-native/ReactCommon/jsinspector" 294 | React-RCTActionSheet: 295 | :path: "../node_modules/react-native/Libraries/ActionSheetIOS" 296 | React-RCTAnimation: 297 | :path: "../node_modules/react-native/Libraries/NativeAnimation" 298 | React-RCTBlob: 299 | :path: "../node_modules/react-native/Libraries/Blob" 300 | React-RCTImage: 301 | :path: "../node_modules/react-native/Libraries/Image" 302 | React-RCTLinking: 303 | :path: "../node_modules/react-native/Libraries/LinkingIOS" 304 | React-RCTNetwork: 305 | :path: "../node_modules/react-native/Libraries/Network" 306 | React-RCTSettings: 307 | :path: "../node_modules/react-native/Libraries/Settings" 308 | React-RCTText: 309 | :path: "../node_modules/react-native/Libraries/Text" 310 | React-RCTVibration: 311 | :path: "../node_modules/react-native/Libraries/Vibration" 312 | ReactCommon: 313 | :path: "../node_modules/react-native/ReactCommon" 314 | ReactNativeKeyboardInput: 315 | :path: "../ReactNativeKeyboardInput.podspec" 316 | ReactNativeKeyboardTrackingView: 317 | :path: "../node_modules/react-native-keyboard-tracking-view" 318 | ReactNativeNavigation: 319 | :path: "../node_modules/react-native-navigation" 320 | Yoga: 321 | :path: "../node_modules/react-native/ReactCommon/yoga" 322 | 323 | SPEC CHECKSUMS: 324 | boost-for-react-native: 39c7adb57c4e60d6c5479dd8623128eb5b3f0f2c 325 | DoubleConversion: 5805e889d232975c086db112ece9ed034df7a0b2 326 | FBLazyVector: feb35a6b7f7b50f367be07f34012f34a79282fa3 327 | FBReactNativeSpec: 51477b84b1bf7ab6f9ef307c24e3dd675391be44 328 | Folly: 30e7936e1c45c08d884aa59369ed951a8e68cf51 329 | glog: 1f3da668190260b06b429bb211bfbee5cd790c28 330 | RCTRequired: f3b3fb6f4723e8e52facb229d0c75fdc76773849 331 | RCTTypeSafety: 2ec60de6abb1db050b56ecc4b60188026078fd10 332 | React: 10e0130b57e55a7cd8c3dee37c1261102ce295f4 333 | React-Core: 636212410772d05f3a1eb79d965df2962ca1c70b 334 | React-CoreModules: 6f70d5e41919289c582f88c9ad9923fe5c87400a 335 | React-cxxreact: ddecbe9157ec1743f52ea17bf8d95debc0d6e846 336 | React-jsi: ca921f4041505f9d5197139b2d09eeb020bb12e8 337 | React-jsiexecutor: 8dfb73b987afa9324e4009bdce62a18ce23d983c 338 | React-jsinspector: d15478d0a8ada19864aa4d1cc1c697b41b3fa92f 339 | React-RCTActionSheet: 7369b7c85f99b6299491333affd9f01f5a130c22 340 | React-RCTAnimation: d07be15b2bd1d06d89417eb0343f98ffd2b099a7 341 | React-RCTBlob: 8e0b23d95c9baa98f6b0e127e07666aaafd96c34 342 | React-RCTImage: 443050d14a66e8c2332e9c055f45689d23e15cc7 343 | React-RCTLinking: ce9a90ba155aec41be49e75ec721bbae2d48a47e 344 | React-RCTNetwork: 41fe54bacc67dd00e6e4c4d30dd98a13e4beabc8 345 | React-RCTSettings: 45e3e0a6470310b2dab2ccc6d1d73121ba3ea936 346 | React-RCTText: 21934e0a51d522abcd0a275407e80af45d6fd9ec 347 | React-RCTVibration: 0f76400ee3cec6edb9c125da49fed279340d145a 348 | ReactCommon: a6a294e7028ed67b926d29551aa9394fd989c24c 349 | ReactNativeKeyboardInput: 266ba27a2e9921f5bdc0b4cc30289b2a2f46b157 350 | ReactNativeKeyboardTrackingView: a240a6a0dba852bb107109a7ec7e98b884055977 351 | ReactNativeNavigation: 3fad99b3843e8840affd70577aaa30bf14b272d5 352 | Yoga: ba3d99dbee6c15ea6bbe3783d1f0cb1ffb79af0f 353 | 354 | PODFILE CHECKSUM: db72c512534d095b2d49d5dbe372d7ea041b7645 355 | 356 | COCOAPODS: 1.9.3 357 | -------------------------------------------------------------------------------- /lib/android/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 28 5 | buildToolsVersion "28.0.3" 6 | 7 | defaultConfig { 8 | minSdkVersion 16 9 | targetSdkVersion 28 10 | versionCode 1 11 | versionName "1.0" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | } 20 | 21 | dependencies { 22 | implementation 'com.facebook.react:react-native:+' 23 | } 24 | 25 | -------------------------------------------------------------------------------- /lib/android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /lib/android/src/main/java/com/wix/reactnativekeyboardinput/AppContextHolder.java: -------------------------------------------------------------------------------- 1 | package com.wix.reactnativekeyboardinput; 2 | 3 | import android.app.Activity; 4 | import android.app.Application; 5 | import android.os.Bundle; 6 | 7 | public class AppContextHolder { 8 | 9 | private static Activity sCurrentActivity; 10 | 11 | public static void setApplication(Application application) { 12 | application.registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() { 13 | @Override 14 | public void onActivityCreated(Activity activity, Bundle savedInstanceState) { 15 | sCurrentActivity = activity; 16 | } 17 | 18 | @Override 19 | public void onActivityStarted(Activity activity) { 20 | sCurrentActivity = activity; 21 | } 22 | 23 | @Override 24 | public void onActivityResumed(Activity activity) { 25 | sCurrentActivity = activity; 26 | } 27 | 28 | @Override 29 | public void onActivityPaused(Activity activity) { 30 | } 31 | 32 | @Override 33 | public void onActivityStopped(Activity activity) { 34 | } 35 | 36 | @Override 37 | public void onActivitySaveInstanceState(Activity activity, Bundle outState) { 38 | } 39 | 40 | @Override 41 | public void onActivityDestroyed(Activity activity) { 42 | if (sCurrentActivity == activity) { 43 | sCurrentActivity = null; 44 | } 45 | } 46 | }); 47 | } 48 | 49 | public static Activity getCurrentActivity() { 50 | return sCurrentActivity; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /lib/android/src/main/java/com/wix/reactnativekeyboardinput/CustomKeyboardLayout.java: -------------------------------------------------------------------------------- 1 | package com.wix.reactnativekeyboardinput; 2 | 3 | import android.content.Context; 4 | import android.view.View; 5 | import android.view.WindowManager; 6 | import android.view.inputmethod.InputMethodManager; 7 | import android.widget.EditText; 8 | 9 | import com.facebook.react.ReactRootView; 10 | import com.facebook.react.bridge.Promise; 11 | import com.facebook.react.bridge.ReactContext; 12 | import com.facebook.react.modules.core.DeviceEventManagerModule; 13 | import com.wix.reactnativekeyboardinput.utils.Logger; 14 | 15 | import java.lang.ref.WeakReference; 16 | 17 | import static com.wix.reactnativekeyboardinput.AppContextHolder.getCurrentActivity; 18 | import static com.wix.reactnativekeyboardinput.GlobalDefs.TAG; 19 | import static com.wix.reactnativekeyboardinput.utils.RuntimeUtils.dispatchUIUpdates; 20 | import static com.wix.reactnativekeyboardinput.utils.RuntimeUtils.runOnUIThread; 21 | import static com.wix.reactnativekeyboardinput.utils.ViewUtils.getWindow; 22 | 23 | public class CustomKeyboardLayout implements ReactSoftKeyboardMonitor.Listener, ReactScreenMonitor.Listener { 24 | private boolean mFirstKeyboardShow = true; 25 | private final InputMethodManager mInputMethodManager; 26 | private final ReactSoftKeyboardMonitor mKeyboardMonitor; 27 | private WeakReference mShadowNode = new WeakReference<>(null); 28 | 29 | public CustomKeyboardLayout(ReactContext reactContext, ReactSoftKeyboardMonitor keyboardMonitor, ReactScreenMonitor screenMonitor) { 30 | mKeyboardMonitor = keyboardMonitor; 31 | mInputMethodManager = (InputMethodManager) reactContext.getSystemService(Context.INPUT_METHOD_SERVICE); 32 | 33 | mKeyboardMonitor.setListener(this); 34 | screenMonitor.addListener(this); 35 | } 36 | 37 | @Override 38 | public void onSoftKeyboardVisible(boolean distinct) { 39 | if (distinct) { 40 | clearKeyboardOverlayMode(); 41 | } 42 | hideCustomKeyboardContent(); 43 | } 44 | 45 | @Override 46 | public void onSoftKeyboardHidden() { 47 | if (getShadowNodeHeight() == 0) { 48 | mFirstKeyboardShow = true; 49 | } 50 | } 51 | 52 | @Override 53 | public void onNewReactScreen(ReactRootView reactRootView) { 54 | clearKeyboardOverlayMode(); 55 | if (reactRootView != null) { 56 | sendCustomKeyboardResignedEvent(); 57 | } 58 | } 59 | 60 | public void setShadowNode(CustomKeyboardRootViewShadow node) { 61 | Logger.v(TAG, "New shadow node: " + node); 62 | mShadowNode = new WeakReference<>(node); 63 | } 64 | 65 | public void onKeyboardHasCustomContent() { 66 | runOnUIThread(new Runnable() { 67 | @Override 68 | public void run() { 69 | showCustomKeyboardContent(); 70 | setKeyboardOverlayMode(); 71 | hideSoftKeyboardIfNeeded(); 72 | } 73 | }); 74 | } 75 | 76 | public void forceReset(final Promise promise) { 77 | runOnUIThread(new Runnable() { 78 | @Override 79 | public void run() { 80 | final View focusedView = getCurrentActivity().getCurrentFocus(); 81 | if (focusedView instanceof EditText) { 82 | showSoftKeyboard(); 83 | } else { 84 | hideCustomKeyboardContent(); 85 | clearKeyboardOverlayMode(); 86 | } 87 | promise.resolve(null); 88 | } 89 | }); 90 | } 91 | 92 | public void clearFocusedView() { 93 | runOnUIThread(new Runnable() { 94 | @Override 95 | public void run() { 96 | final View focusedView = getCurrentActivity().getCurrentFocus(); 97 | if (focusedView != null) { 98 | focusedView.clearFocus(); 99 | } 100 | } 101 | }); 102 | } 103 | 104 | private void showCustomKeyboardContent() { 105 | setCustomKeyboardHeight(getHeightForCustomContent()); 106 | } 107 | 108 | private void hideCustomKeyboardContent() { 109 | setCustomKeyboardHeight(0); 110 | runOnUIThread(new Runnable() { 111 | @Override 112 | public void run() { 113 | sendCustomKeyboardResignedEvent(); 114 | } 115 | }); 116 | } 117 | 118 | private void syncCustomKeyboardHeightAfterUIUpdate(final int height) { 119 | dispatchUIUpdates(new Runnable() { 120 | @Override 121 | public void run() { 122 | setShadowNodeHeight(height); 123 | } 124 | }); 125 | } 126 | 127 | private void setCustomKeyboardHeight(int height) { 128 | try { 129 | if (mFirstKeyboardShow) { 130 | mFirstKeyboardShow = false; 131 | syncCustomKeyboardHeightAfterUIUpdate(height); 132 | } else { 133 | setShadowNodeHeight(height); 134 | } 135 | } catch (Exception e) { 136 | e.printStackTrace(); 137 | } 138 | } 139 | 140 | private void setShadowNodeHeight(int height) { 141 | final CustomKeyboardRootViewShadow shadowNode = mShadowNode.get(); 142 | if (shadowNode != null) { 143 | shadowNode.setHeight(height); 144 | } 145 | } 146 | 147 | private float getShadowNodeHeight() { 148 | float height = 0; 149 | final CustomKeyboardRootViewShadow shadowNode = mShadowNode.get(); 150 | if (shadowNode != null) { 151 | height = shadowNode.getHeight(); 152 | } 153 | return height; 154 | } 155 | 156 | private void showSoftKeyboard() { 157 | mInputMethodManager.showSoftInput(getCurrentActivity().getCurrentFocus(), 0); 158 | } 159 | 160 | private void hideSoftKeyboardIfNeeded() { 161 | final View focusedView = getCurrentActivity().getCurrentFocus(); 162 | if (focusedView != null) { 163 | mInputMethodManager.hideSoftInputFromWindow(focusedView.getWindowToken(), 0); 164 | } 165 | } 166 | 167 | private int getHeightForCustomContent() { 168 | return mKeyboardMonitor.getKeyboardHeight(); 169 | } 170 | 171 | private void setKeyboardOverlayMode() { 172 | getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN); 173 | } 174 | 175 | private void clearKeyboardOverlayMode() { 176 | getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); 177 | } 178 | 179 | private void sendCustomKeyboardResignedEvent() { 180 | Logger.v(TAG, "Notifying the custom-keyboard-resigned event to JS"); 181 | if (ReactContextHolder.getContext().hasActiveCatalystInstance()) { 182 | ReactContextHolder.getContext().getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit("kbdResigned", null); 183 | } 184 | } 185 | 186 | } 187 | -------------------------------------------------------------------------------- /lib/android/src/main/java/com/wix/reactnativekeyboardinput/CustomKeyboardRootView.java: -------------------------------------------------------------------------------- 1 | package com.wix.reactnativekeyboardinput; 2 | 3 | import android.content.Context; 4 | import android.view.View; 5 | import android.widget.FrameLayout; 6 | 7 | import androidx.annotation.NonNull; 8 | 9 | import com.wix.reactnativekeyboardinput.utils.Logger; 10 | 11 | import static com.wix.reactnativekeyboardinput.GlobalDefs.TAG; 12 | 13 | public class CustomKeyboardRootView extends FrameLayout { 14 | 15 | private final CustomKeyboardLayout mLayout; 16 | 17 | public CustomKeyboardRootView(@NonNull Context context, CustomKeyboardLayout layout) { 18 | super(context); 19 | mLayout = layout; 20 | 21 | setWillNotDraw(false); 22 | } 23 | 24 | @Override 25 | public void onViewAdded(View child) { 26 | if (getChildCount() == 1) { 27 | Logger.d(TAG, "New custom keyboard content"); 28 | mLayout.onKeyboardHasCustomContent(); 29 | } 30 | super.onViewAdded(child); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/android/src/main/java/com/wix/reactnativekeyboardinput/CustomKeyboardRootViewManager.java: -------------------------------------------------------------------------------- 1 | package com.wix.reactnativekeyboardinput; 2 | 3 | import com.facebook.react.uimanager.LayoutShadowNode; 4 | import com.facebook.react.uimanager.ThemedReactContext; 5 | import com.facebook.react.uimanager.ViewGroupManager; 6 | 7 | public class CustomKeyboardRootViewManager extends ViewGroupManager { 8 | 9 | private final CustomKeyboardLayout mLayout; 10 | 11 | public CustomKeyboardRootViewManager(CustomKeyboardLayout layout) { 12 | mLayout = layout; 13 | } 14 | 15 | @Override 16 | public String getName() { 17 | return "CustomKeyboardViewNative"; 18 | } 19 | 20 | @Override 21 | public CustomKeyboardRootView createViewInstance(ThemedReactContext reactContext) { 22 | return new CustomKeyboardRootView(reactContext, mLayout); 23 | } 24 | 25 | @Override 26 | public LayoutShadowNode createShadowNodeInstance() { 27 | return new CustomKeyboardRootViewShadow(mLayout); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/android/src/main/java/com/wix/reactnativekeyboardinput/CustomKeyboardRootViewShadow.java: -------------------------------------------------------------------------------- 1 | package com.wix.reactnativekeyboardinput; 2 | 3 | import com.facebook.react.uimanager.LayoutShadowNode; 4 | import com.facebook.react.uimanager.NativeViewHierarchyOptimizer; 5 | 6 | public class CustomKeyboardRootViewShadow extends LayoutShadowNode { 7 | 8 | private final CustomKeyboardLayout mLayout; 9 | 10 | CustomKeyboardRootViewShadow(CustomKeyboardLayout layout) { 11 | setStyleHeight(0); 12 | 13 | mLayout = layout; 14 | mLayout.setShadowNode(this); 15 | } 16 | 17 | @Override 18 | public void onBeforeLayout(NativeViewHierarchyOptimizer nativeViewHierarchyOptimizer) { 19 | mLayout.setShadowNode(this); 20 | } 21 | 22 | public void setHeight(int heightPx) { 23 | setStyleHeight(heightPx); 24 | } 25 | 26 | public float getHeight() { 27 | return getStyleHeight().value; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/android/src/main/java/com/wix/reactnativekeyboardinput/GlobalDefs.java: -------------------------------------------------------------------------------- 1 | package com.wix.reactnativekeyboardinput; 2 | 3 | public interface GlobalDefs { 4 | String TAG = "RCTKeyboardInput"; 5 | } 6 | -------------------------------------------------------------------------------- /lib/android/src/main/java/com/wix/reactnativekeyboardinput/KeyboardInputModule.java: -------------------------------------------------------------------------------- 1 | package com.wix.reactnativekeyboardinput; 2 | 3 | import com.facebook.react.bridge.Promise; 4 | import com.facebook.react.bridge.ReactApplicationContext; 5 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 6 | import com.facebook.react.bridge.ReactMethod; 7 | 8 | public class KeyboardInputModule extends ReactContextBaseJavaModule { 9 | 10 | private static final String REACT_CLASS = "CustomKeyboardInput"; 11 | 12 | private final CustomKeyboardLayout mLayout; 13 | 14 | public KeyboardInputModule(ReactApplicationContext reactContext, CustomKeyboardLayout layout) { 15 | super(reactContext); 16 | 17 | mLayout = layout; 18 | } 19 | 20 | @Override 21 | public String getName() { 22 | return REACT_CLASS; 23 | } 24 | 25 | @ReactMethod 26 | public void reset(Promise promise) { 27 | mLayout.forceReset(promise); 28 | } 29 | 30 | @ReactMethod 31 | public void clearFocusedView() { 32 | mLayout.clearFocusedView(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/android/src/main/java/com/wix/reactnativekeyboardinput/KeyboardInputPackage.java: -------------------------------------------------------------------------------- 1 | package com.wix.reactnativekeyboardinput; 2 | 3 | import android.app.Application; 4 | 5 | import com.facebook.react.ReactPackage; 6 | import com.facebook.react.bridge.JavaScriptModule; 7 | import com.facebook.react.bridge.NativeModule; 8 | import com.facebook.react.bridge.ReactApplicationContext; 9 | import com.facebook.react.uimanager.ViewManager; 10 | import com.wix.reactnativekeyboardinput.utils.Logger; 11 | 12 | import java.util.Arrays; 13 | import java.util.Collections; 14 | import java.util.List; 15 | 16 | public class KeyboardInputPackage implements ReactPackage { 17 | 18 | private CustomKeyboardLayout mLayout; 19 | 20 | public KeyboardInputPackage(Application application) { 21 | AppContextHolder.setApplication(application); 22 | } 23 | 24 | public KeyboardInputPackage(Application application, boolean enableLogging) { 25 | this(application); 26 | if (enableLogging) { 27 | Logger.enable(); 28 | } 29 | } 30 | 31 | @Override 32 | public List createNativeModules(ReactApplicationContext reactContext) { 33 | init(reactContext); 34 | return Arrays.asList(new KeyboardInputModule(reactContext, mLayout)); 35 | } 36 | 37 | @Override 38 | public List createViewManagers(ReactApplicationContext reactContext) { 39 | init(reactContext); 40 | return Arrays.asList(new CustomKeyboardRootViewManager(mLayout)); 41 | } 42 | 43 | // Deprecated in RN 0.47 44 | public List> createJSModules() { 45 | return Collections.emptyList(); 46 | } 47 | 48 | private synchronized void init(ReactApplicationContext reactContext) { 49 | if (ReactContextHolder.getContext() != reactContext) { 50 | ReactContextHolder.setContext(reactContext); 51 | 52 | final ReactScreenMonitor screenMonitor = new ReactScreenMonitor(reactContext); 53 | final ReactSoftKeyboardMonitor keyboardMonitor = new ReactSoftKeyboardMonitor(screenMonitor); 54 | mLayout = new CustomKeyboardLayout(reactContext, keyboardMonitor, screenMonitor); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /lib/android/src/main/java/com/wix/reactnativekeyboardinput/ReactContextHolder.java: -------------------------------------------------------------------------------- 1 | package com.wix.reactnativekeyboardinput; 2 | 3 | import com.facebook.react.bridge.ReactApplicationContext; 4 | 5 | public class ReactContextHolder { 6 | 7 | private static ReactApplicationContext sContext; 8 | 9 | public static void setContext(ReactApplicationContext context) { 10 | sContext = context; 11 | } 12 | 13 | public static ReactApplicationContext getContext() { 14 | return sContext; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/android/src/main/java/com/wix/reactnativekeyboardinput/ReactScreenMonitor.java: -------------------------------------------------------------------------------- 1 | package com.wix.reactnativekeyboardinput; 2 | 3 | import android.view.ViewTreeObserver; 4 | 5 | import com.facebook.react.ReactRootView; 6 | import com.facebook.react.bridge.LifecycleEventListener; 7 | import com.facebook.react.bridge.ReactContext; 8 | import com.wix.reactnativekeyboardinput.utils.ViewUtils; 9 | 10 | import java.util.HashSet; 11 | import java.util.Set; 12 | 13 | import static com.wix.reactnativekeyboardinput.utils.ViewUtils.getWindow; 14 | 15 | public class ReactScreenMonitor implements LifecycleEventListener { 16 | 17 | public interface Listener { 18 | void onNewReactScreen(ReactRootView reactRootView); 19 | } 20 | 21 | private final ViewTreeObserver.OnGlobalLayoutListener mWindowLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() { 22 | @Override 23 | public void onGlobalLayout() { 24 | final ReactRootView reactRootView = ViewUtils.getReactRootView(); 25 | if (mLastReactRootView != reactRootView) { 26 | mLastReactRootView = reactRootView; 27 | notifyNewScreen(); 28 | } 29 | } 30 | }; 31 | 32 | private ReactRootView mLastReactRootView; 33 | private Set mExternalListeners = new HashSet<>(); 34 | 35 | private boolean mHasWindowLayoutListener; 36 | 37 | public ReactScreenMonitor(ReactContext reactContext) { 38 | reactContext.addLifecycleEventListener(this); 39 | } 40 | 41 | public void addListener(Listener listener) { 42 | mExternalListeners.add(listener); 43 | } 44 | 45 | @Override 46 | public void onHostResume() { 47 | if (mHasWindowLayoutListener) { 48 | removeWindowLayoutListener(); 49 | } 50 | mHasWindowLayoutListener = true; 51 | registerWindowLayoutListener(); 52 | } 53 | 54 | @Override 55 | public void onHostDestroy() { 56 | removeWindowLayoutListener(); 57 | mHasWindowLayoutListener = false; 58 | } 59 | 60 | @Override 61 | public void onHostPause() { 62 | } 63 | 64 | private void registerWindowLayoutListener() { 65 | getWindow().getDecorView().getViewTreeObserver().addOnGlobalLayoutListener(mWindowLayoutListener); 66 | } 67 | 68 | private void removeWindowLayoutListener() { 69 | if (getWindow() == null) { 70 | // No window => no activity => nothing to clear. 71 | return; 72 | } 73 | getWindow().getDecorView().getViewTreeObserver().removeOnGlobalLayoutListener(mWindowLayoutListener); 74 | } 75 | 76 | private void notifyNewScreen() { 77 | for (Listener listener : mExternalListeners) { 78 | listener.onNewReactScreen(mLastReactRootView); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /lib/android/src/main/java/com/wix/reactnativekeyboardinput/ReactSoftKeyboardMonitor.java: -------------------------------------------------------------------------------- 1 | package com.wix.reactnativekeyboardinput; 2 | 3 | import android.graphics.Rect; 4 | import android.view.ViewTreeObserver; 5 | import android.view.Window; 6 | 7 | import androidx.annotation.Nullable; 8 | 9 | import com.facebook.react.ReactRootView; 10 | import com.wix.reactnativekeyboardinput.utils.Logger; 11 | import com.wix.reactnativekeyboardinput.utils.RuntimeUtils; 12 | 13 | import static com.wix.reactnativekeyboardinput.GlobalDefs.TAG; 14 | import static com.wix.reactnativekeyboardinput.utils.ViewUtils.getWindow; 15 | 16 | public class ReactSoftKeyboardMonitor implements ReactScreenMonitor.Listener { 17 | 18 | public interface Listener { 19 | void onSoftKeyboardVisible(boolean distinct); 20 | void onSoftKeyboardHidden(); 21 | } 22 | 23 | private final ViewTreeObserver.OnGlobalLayoutListener mInnerLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() { 24 | @Override 25 | public void onGlobalLayout() { 26 | Integer viewportVisibleHeight = getViewportVisibleHeight(); 27 | if (viewportVisibleHeight == null || viewportVisibleHeight.equals(mLastViewportVisibleHeight)) { 28 | return; 29 | } 30 | 31 | mLastViewportVisibleHeight = viewportVisibleHeight; 32 | if (mMaxViewportVisibleHeight == null) { 33 | mMaxViewportVisibleHeight = viewportVisibleHeight; 34 | Logger.d(TAG, "mMaxViewportVisibleHeight WAS NULL, now is: " + mMaxViewportVisibleHeight); 35 | } else if (viewportVisibleHeight < mMaxViewportVisibleHeight) { 36 | mExternalListener.onSoftKeyboardVisible(!mSoftKeyboardUp); 37 | refreshKeyboardHeight(); 38 | mSoftKeyboardUp = true; 39 | Logger.d(TAG, "Keyboard SHOWING!"); 40 | } else { 41 | mSoftKeyboardUp = false; 42 | mExternalListener.onSoftKeyboardHidden(); 43 | Logger.d(TAG, "Keyboard GONE!"); 44 | } 45 | } 46 | }; 47 | 48 | /** 49 | * Soft-keyboard appearance (yes or no) is deduced according to view-port (window-level display-frame), as 50 | * root-view height normally remains unaffected during immediate layout. We therefore keep the maximal view-port size so we could 51 | * concurrently compare heights in each layout. 52 | */ 53 | private Integer mMaxViewportVisibleHeight; 54 | 55 | private Integer mLastViewportVisibleHeight; 56 | 57 | /** 58 | * Soft-keyboard *height* (when visible) is deduced by the effect on the root react-view height. This is ineffective in trying to 59 | * monitor keyboard appearance -- only for height measuring. 60 | */ 61 | private Integer mLocallyVisibleHeight; 62 | 63 | private boolean mSoftKeyboardUp; 64 | private Integer mKeyboardHeight; 65 | private Listener mExternalListener; 66 | private ReactRootView mLastReactRootView; 67 | 68 | public ReactSoftKeyboardMonitor(ReactScreenMonitor screenMonitor) { 69 | screenMonitor.addListener(this); 70 | } 71 | 72 | @Override 73 | public void onNewReactScreen(ReactRootView reactRootView) { 74 | removeReactRootViewLayoutListener(); 75 | mLastReactRootView = reactRootView; 76 | 77 | if (mLastReactRootView != null) { // 'Null' is applicable when activity is going down (e.g. bundle reload in RN dev mode) 78 | registerReactRootViewLayoutListener(); 79 | 80 | initViewportVisibleHeight(); // TODO: running this each time might be redundant 81 | initLocallyVisibleHeight(); 82 | } 83 | } 84 | 85 | public void setListener(Listener listener) { 86 | mExternalListener = listener; 87 | } 88 | 89 | @Nullable 90 | public Integer getKeyboardHeight() { 91 | if (mKeyboardHeight != null) { 92 | return mKeyboardHeight; 93 | } 94 | 95 | if (mLocallyVisibleHeight != null) { 96 | return (int) (.5f * mLocallyVisibleHeight); 97 | } 98 | 99 | Logger.d(TAG, "getKeyboardHeight, no keyboard height"); 100 | return null; 101 | } 102 | 103 | private void registerReactRootViewLayoutListener() { 104 | final ViewTreeObserver viewTreeObserver = mLastReactRootView.getViewTreeObserver(); 105 | viewTreeObserver.addOnGlobalLayoutListener(mInnerLayoutListener); 106 | } 107 | 108 | private void removeReactRootViewLayoutListener() { 109 | if (mLastReactRootView != null) { 110 | final ViewTreeObserver viewTreeObserver = mLastReactRootView.getViewTreeObserver(); 111 | viewTreeObserver.removeOnGlobalLayoutListener(mInnerLayoutListener); 112 | } 113 | } 114 | 115 | private void initViewportVisibleHeight() { 116 | mMaxViewportVisibleHeight = getViewportVisibleHeight(); 117 | mLastViewportVisibleHeight = null; 118 | Logger.d(TAG, "Measured new max view-port height: "+mMaxViewportVisibleHeight); 119 | } 120 | 121 | private void initLocallyVisibleHeight() { 122 | mLocallyVisibleHeight = getLocallyVisibleHeight(); 123 | Logger.d(TAG, "Measured locally visible height: "+mLocallyVisibleHeight); 124 | mKeyboardHeight = null; // Reset so the keyboard would be measured in the next opportunity. 125 | } 126 | 127 | private void refreshKeyboardHeight() { 128 | if (mKeyboardHeight != null) { 129 | return; 130 | } 131 | 132 | RuntimeUtils.runOnUIThread(new Runnable() { 133 | @Override 134 | public void run() { 135 | final Integer locallyVisibleHeight = getLocallyVisibleHeight(); 136 | if (locallyVisibleHeight == null) { 137 | // Too late to join the party - react-view seems to be gone... 138 | return; 139 | } 140 | 141 | if (mLocallyVisibleHeight == null) { 142 | mLocallyVisibleHeight = locallyVisibleHeight; 143 | mKeyboardHeight = mLocallyVisibleHeight; 144 | Logger.d(TAG, "mLocallyVisibleHeight WAS NULL, now is: " + mLocallyVisibleHeight); 145 | } else if (mLocallyVisibleHeight > locallyVisibleHeight) { 146 | mKeyboardHeight = mLocallyVisibleHeight - locallyVisibleHeight; 147 | } else { 148 | mKeyboardHeight = locallyVisibleHeight; 149 | Logger.d(TAG, "mKeyboardHeight = " + mKeyboardHeight + " mLocallyVisibleHeight = " + mLocallyVisibleHeight + " locallyVisibleHeight = " + locallyVisibleHeight); 150 | } 151 | } 152 | }); 153 | } 154 | 155 | private Integer getViewportVisibleHeight() { 156 | Integer visibleHeight = null; 157 | final Rect visibleArea = new Rect(); 158 | Window window = getWindow(); 159 | if (window != null) { 160 | window.getDecorView().getWindowVisibleDisplayFrame(visibleArea); 161 | visibleHeight = visibleArea.height(); 162 | } 163 | 164 | return visibleHeight; 165 | } 166 | 167 | private Integer getLocallyVisibleHeight() { 168 | if (mLastReactRootView != null) { 169 | return mLastReactRootView.getHeight(); 170 | } 171 | return null; 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /lib/android/src/main/java/com/wix/reactnativekeyboardinput/utils/Logger.java: -------------------------------------------------------------------------------- 1 | package com.wix.reactnativekeyboardinput.utils; 2 | 3 | import android.util.Log; 4 | 5 | public class Logger { 6 | 7 | private static boolean sIsEnabled; 8 | 9 | public static void enable() { 10 | sIsEnabled = true; 11 | } 12 | 13 | public static void disable() { 14 | sIsEnabled = false; 15 | } 16 | 17 | public static void v(String tag, String message) { 18 | if (sIsEnabled) { 19 | Log.v(tag, message); 20 | } 21 | } 22 | 23 | public static void d(String tag, String message) { 24 | if (sIsEnabled) { 25 | Log.d(tag, message); 26 | } 27 | } 28 | 29 | public static void i(String tag, String message) { 30 | if (sIsEnabled) { 31 | Log.i(tag, message); 32 | } 33 | } 34 | 35 | public static void e(String tag, String message) { 36 | if (sIsEnabled) { 37 | Log.e(tag, message); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lib/android/src/main/java/com/wix/reactnativekeyboardinput/utils/PredicateFunc.java: -------------------------------------------------------------------------------- 1 | package com.wix.reactnativekeyboardinput.utils; 2 | 3 | public interface PredicateFunc { 4 | boolean invoke(T element); 5 | } 6 | -------------------------------------------------------------------------------- /lib/android/src/main/java/com/wix/reactnativekeyboardinput/utils/RuntimeUtils.java: -------------------------------------------------------------------------------- 1 | package com.wix.reactnativekeyboardinput.utils; 2 | 3 | import com.facebook.react.uimanager.UIManagerModule; 4 | import com.wix.reactnativekeyboardinput.ReactContextHolder; 5 | 6 | public class RuntimeUtils { 7 | 8 | // TODO Switch to GuardedRunnable when upgrading RN's minimal ver 9 | private static final Runnable sUIUpdateClosure = new Runnable() { 10 | @Override 11 | public void run() { 12 | ReactContextHolder.getContext().getNativeModule(UIManagerModule.class).onBatchComplete(); 13 | } 14 | }; 15 | 16 | public static void runOnUIThread(Runnable runnable) { 17 | ReactContextHolder.getContext().runOnUiQueueThread(runnable); 18 | } 19 | 20 | public static void dispatchUIUpdates(final Runnable userRunnable) { 21 | runOnUIThread(new Runnable() { 22 | @Override 23 | public void run() { 24 | userRunnable.run(); 25 | ReactContextHolder.getContext().runOnNativeModulesQueueThread(sUIUpdateClosure); 26 | } 27 | }); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/android/src/main/java/com/wix/reactnativekeyboardinput/utils/ViewUtils.java: -------------------------------------------------------------------------------- 1 | package com.wix.reactnativekeyboardinput.utils; 2 | 3 | import android.app.Activity; 4 | import android.view.View; 5 | import android.view.ViewGroup; 6 | import android.view.Window; 7 | 8 | import androidx.annotation.Nullable; 9 | 10 | import com.facebook.react.ReactRootView; 11 | 12 | import static com.wix.reactnativekeyboardinput.AppContextHolder.getCurrentActivity; 13 | import static com.wix.reactnativekeyboardinput.GlobalDefs.TAG; 14 | 15 | public class ViewUtils { 16 | 17 | private static class VisibleViewClassMatchPredicate implements PredicateFunc { 18 | private final Class mClazz; 19 | 20 | private VisibleViewClassMatchPredicate(Class clazz) { 21 | mClazz = clazz; 22 | } 23 | 24 | @Override 25 | public boolean invoke(View view) { 26 | return mClazz.isAssignableFrom(view.getClass()) && view.isShown(); 27 | } 28 | } 29 | private static final VisibleViewClassMatchPredicate sVisibleReactRootViewMatcher = new VisibleViewClassMatchPredicate(ReactRootView.class); 30 | 31 | public static Window getWindow() { 32 | final Activity activity = getCurrentActivity(); 33 | return (activity == null ? null : activity.getWindow()); 34 | } 35 | 36 | public static ReactRootView getReactRootView() { 37 | final Window window = getWindow(); 38 | if (window == null) { 39 | return null; 40 | } 41 | 42 | final ReactRootView view = findChildByClass((ViewGroup) window.getDecorView(), sVisibleReactRootViewMatcher); 43 | Logger.v(TAG, "Visible RCT view: " + (view != null ? view.hashCode() : null)); 44 | return view; 45 | } 46 | 47 | /** 48 | * Returns the first instance of clazz in root for which predicate is evaluated as true. 49 | */ 50 | @Nullable 51 | public static T findChildByClass(ViewGroup root, PredicateFunc predicate) { 52 | for (int i = 0; i < root.getChildCount(); i++) { 53 | View view = root.getChildAt(i); 54 | if (predicate.invoke(view)) { 55 | return ((T) view); 56 | } 57 | 58 | if (view instanceof ViewGroup) { 59 | view = findChildByClass((ViewGroup) view, predicate); 60 | if (view != null) { 61 | return (T) view; 62 | } 63 | } 64 | } 65 | return null; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /lib/ios/LNInterpolation/Color+Interpolation.h: -------------------------------------------------------------------------------- 1 | // 2 | // Color+Interpolation.h 3 | // 4 | // Created by Leo Natan on 01/10/2016. 5 | // Copyright © 2016 Leo Natan. All rights reserved. 6 | // 7 | 8 | #if __has_include() || __has_include() 9 | 10 | #import "LNInterpolable.h" 11 | 12 | #if __has_include() 13 | #import 14 | #else 15 | #import 16 | #endif 17 | 18 | /** 19 | Interpolate using the LAB color space for optimal quality. This constant is equal to @c LNUseDefaultInterpolationBehavior. 20 | */ 21 | extern LNInterpolationBehavior const LNInterpolationBehaviorUseLABColorSpace; 22 | 23 | /** 24 | Interpolate using the RGB color space. 25 | */ 26 | extern LNInterpolationBehavior const LNInterpolationBehaviorUseRGBColorSpace; 27 | 28 | /** 29 | Interpolates between colors. 30 | 31 | By default, colors are interpolated in the Lab color space for optimal quality at the expense of some performance. Use @c LNUseRGBInterpolationBehavior for better performance but suboptimal quality. 32 | */ 33 | #if __has_include() 34 | @interface UIColor (LNInterpolation) @end 35 | #else 36 | @interface NSColor (LNInterpolation) @end 37 | #endif 38 | 39 | #endif 40 | -------------------------------------------------------------------------------- /lib/ios/LNInterpolation/Color+Interpolation.m: -------------------------------------------------------------------------------- 1 | // 2 | // Color+Interpolation.m 3 | // 4 | // Created by Leo Natan on 01/10/2016. 5 | // Copyright © 2016 Leo Natan. All rights reserved. 6 | // 7 | 8 | #if __has_include() || __has_include() 9 | 10 | #import "Color+Interpolation.h" 11 | 12 | #if __has_include() 13 | #define Color UIColor 14 | #else 15 | #define Color NSColor 16 | #endif 17 | 18 | #define SWAP(x, y) do { __typeof(x) __ZZZZ__SWAP = x; x = y; y = __ZZZZ__SWAP; } while(0) 19 | 20 | //Same value as LNInterpolationBehaviorUseDefault 21 | LNInterpolationBehavior const LNInterpolationBehaviorUseLABColorSpace = @"LNInterpolationBehaviorUseDefault"; 22 | LNInterpolationBehavior const LNInterpolationBehaviorUseRGBColorSpace = @"LNInterpolationBehaviorUseRGB"; 23 | 24 | extern double LNLinearInterpolate(double from, double to, double p); 25 | 26 | static NSArray* LNRGBComponentsFromColor(Color* color) 27 | { 28 | size_t numberOfComponents = CGColorGetNumberOfComponents(color.CGColor); 29 | const CGFloat* components = CGColorGetComponents(color.CGColor); 30 | 31 | return numberOfComponents == 2 ? @[@(components[0]), @(components[0]), @(components[0]), @(components[1])] : @[@(components[0]), @(components[1]), @(components[2]), @(components[3])]; 32 | } 33 | 34 | static Color* LNColorFromRGBComponents(NSArray* components) 35 | { 36 | return [Color colorWithRed:components[0].doubleValue green:components[1].doubleValue blue:components[2].doubleValue alpha:components[3].doubleValue]; 37 | } 38 | 39 | static NSArray* LNLabComponentsFromColor(Color* color) 40 | { 41 | NSArray* rgbComponents = LNRGBComponentsFromColor(color); 42 | CGFloat r = rgbComponents[0].doubleValue; 43 | CGFloat g = rgbComponents[1].doubleValue; 44 | CGFloat b = rgbComponents[2].doubleValue; 45 | 46 | //RGB -> XYZ 47 | //http://www.brucelindbloom.com/index.html?Eqn_RGB_to_XYZ.html 48 | 49 | r = (r > 0.04045) ? pow((r + 0.055) / 1.055, 2.4) : (r / 12.92); 50 | g = (g > 0.04045) ? pow((g + 0.055) / 1.055, 2.4) : (g / 12.92); 51 | b = (b > 0.04045) ? pow((b + 0.055) / 1.055, 2.4) : (b / 12.92); 52 | 53 | //http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html (sRGB -> XYZ) 54 | CGFloat x = r * 41.24564 + g * 35.75761 + b * 18.04375; 55 | CGFloat y = r * 21.26729 + g * 71.51522 + b * 07.21750; 56 | CGFloat z = r * 01.93339 + g * 11.91920 + b * 95.03041; 57 | 58 | //XYZ -> Lab 59 | //http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_Lab.html 60 | 61 | static const CGFloat eps = 216.0 / 24389.0; 62 | static const CGFloat k = 24389.0 / 27.0; 63 | 64 | x = x / 100.0;//95.047; 65 | y = y / 100.0;//100.000; 66 | z = z / 100.0;//108.883; 67 | 68 | x = x > eps ? pow(x, 1.0 / 3.0) : (k * x + 16.0) / 116.0; 69 | y = y > eps ? pow(y, 1.0 / 3.0) : (k * y + 16.0) / 116.0; 70 | z = z > eps ? pow(z, 1.0 / 3.0) : (k * z + 16.0) / 116.0; 71 | 72 | CGFloat l = 116 * y - 16; 73 | CGFloat a = 500 * (x - y); 74 | b = 200 * (y - z); 75 | 76 | return @[@(l), @(a), @(b), rgbComponents[3]]; 77 | } 78 | 79 | static Color* LNColorFromLabComponents(NSArray* components) 80 | { 81 | CGFloat l = components[0].doubleValue; 82 | CGFloat a = components[1].doubleValue; 83 | CGFloat b = components[2].doubleValue; 84 | 85 | //Lab -> XYZ 86 | //http://www.brucelindbloom.com/index.html?Eqn_Lab_to_XYZ.html 87 | 88 | static const CGFloat eps = 216.0 / 24389.0; 89 | static const CGFloat k = 24389.0 / 27.0; 90 | 91 | CGFloat y = (l + 16.0) / 116.0; 92 | CGFloat x = a / 500 + y; 93 | CGFloat z = y - b / 200; 94 | 95 | x = pow(x, 3.0) > eps ? pow(x, 3.0) : (116 * x - 16) / k; 96 | y = l > k * eps ? pow((l + 16) / 116, 3.0) : l / k; 97 | z = pow(z, 3.0) > eps ? pow(z, 3.0) : (116 * z - 16) / k; 98 | 99 | x = x * 1.0;//.95047; 100 | y = y * 1.0;//1.00000; 101 | z = z * 1.0;//1.08883; 102 | 103 | //XYZ -> RGB 104 | 105 | //http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html (XYZ -> sRGB) 106 | CGFloat r = x * 3.2404542 + y * -1.5371385 + z * -0.4985314; 107 | CGFloat g = x * -0.9692660 + y * 1.8760108 + z * 0.0415560; 108 | b = x * 0.0556434 + y * -0.2040259 + z * 1.0572252; 109 | 110 | r = r <= 0.0031308 ? 12.92 * r : 1.055 * pow(r, 1.0 / 2.4) - 0.055; 111 | g = g <= 0.0031308 ? 12.92 * g : 1.055 * pow(g, 1.0 / 2.4) - 0.055; 112 | b = b <= 0.0031308 ? 12.92 * b : 1.055 * pow(b, 1.0 / 2.4) - 0.055; 113 | 114 | // return Color 115 | return LNColorFromRGBComponents(@[@(r), @(g), @(b), components[3]]); 116 | } 117 | 118 | static inline Color* LNInterpolateColor(Color* fromValue, Color* toValue, CGFloat p, NSArray* (*compConverter)(Color*), Color* (*colorConverter)(NSArray*)) 119 | { 120 | NSArray* arrayC1 = compConverter(fromValue); 121 | NSArray* arrayC2 = compConverter(toValue); 122 | 123 | NSMutableArray* arrayOutput = [NSMutableArray new]; 124 | [arrayC1 enumerateObjectsUsingBlock:^(NSNumber * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 125 | arrayOutput[idx] = @(LNLinearInterpolate(obj.doubleValue, arrayC2[idx].doubleValue, p)); 126 | }]; 127 | 128 | return colorConverter(arrayOutput); 129 | } 130 | 131 | @implementation Color (Interpolation) 132 | 133 | - (instancetype)interpolateToValue:(Color*)toValue progress:(double)p 134 | { 135 | return [self interpolateToValue:toValue progress:p behavior:LNInterpolationBehaviorUseDefault]; 136 | } 137 | 138 | - (instancetype)interpolateToValue:(id)toValue progress:(double)p behavior:(LNInterpolationBehavior)behavior 139 | { 140 | if([toValue isKindOfClass:[Color class]] == NO) 141 | { 142 | return nil; 143 | } 144 | 145 | if(p <= 0) 146 | { 147 | return self; 148 | } 149 | 150 | if(p >= 1) 151 | { 152 | return toValue; 153 | } 154 | 155 | return LNInterpolateColor(self, toValue, p, behavior == LNInterpolationBehaviorUseRGBColorSpace ? LNRGBComponentsFromColor : LNLabComponentsFromColor, behavior == LNInterpolationBehaviorUseRGBColorSpace ? LNColorFromRGBComponents : LNColorFromLabComponents); 156 | } 157 | 158 | @end 159 | 160 | #endif 161 | -------------------------------------------------------------------------------- /lib/ios/LNInterpolation/LNAnimator.h: -------------------------------------------------------------------------------- 1 | // 2 | // LNFrameAnimator.h 3 | // KeyboardTransitionDemo 4 | // 5 | // Created by Leo Natan (Wix) on 11/05/2017. 6 | // Copyright © 2017 Wix. All rights reserved. 7 | // 8 | 9 | @import UIKit; 10 | 11 | @protocol LNAnimation 12 | 13 | @property (nonatomic) CGFloat progress; 14 | 15 | @end 16 | 17 | @interface LNViewAnimation : NSObject 18 | 19 | @property (nonatomic, strong, readonly) UIView* view; 20 | @property (nonatomic, strong, readonly) NSString* keyPath; 21 | @property (nonatomic, strong, readonly) id toValue; 22 | 23 | + (instancetype)animationWithView:(UIView*)view keyPath:(NSString*)keyPath toValue:(id)toValue; 24 | 25 | @end 26 | 27 | @interface LNAnimator : NSObject 28 | 29 | @property (nonatomic, readonly) NSTimeInterval duration; 30 | @property (nonatomic, strong, readonly) NSArray>* animations; 31 | 32 | + (instancetype)animatorWithDuration:(NSTimeInterval)duration animations:(NSArray>*)animations completionHandler:(void(^)(BOOL completed))completionHandler; 33 | 34 | - (void)start; 35 | 36 | @end 37 | -------------------------------------------------------------------------------- /lib/ios/LNInterpolation/LNAnimator.m: -------------------------------------------------------------------------------- 1 | // 2 | // LNFrameAnimator.m 3 | // KeyboardTransitionDemo 4 | // 5 | // Created by Leo Natan (Wix) on 11/05/2017. 6 | // Copyright © 2017 Wix. All rights reserved. 7 | // 8 | 9 | #import "LNAnimator.h" 10 | 11 | #import "LNInterpolation.h" 12 | 13 | @implementation LNViewAnimation 14 | { 15 | id _fromValue; 16 | } 17 | 18 | @synthesize progress = _progress; 19 | 20 | - (instancetype)init 21 | { 22 | [NSException raise:NSInvalidArgumentException format:@"Use animationWithView:keyPath:toValue: to create LNViewAnimation objects."]; 23 | return nil; 24 | } 25 | 26 | - (instancetype)_init 27 | { 28 | return [super init]; 29 | } 30 | 31 | + (instancetype)animationWithView:(UIView*)view keyPath:(NSString*)keyPath toValue:(id)toValue 32 | { 33 | LNViewAnimation* rv = [[LNViewAnimation alloc] _init]; 34 | 35 | if(rv) 36 | { 37 | rv->_view = view; 38 | rv->_keyPath = keyPath; 39 | rv->_toValue = toValue; 40 | rv->_fromValue = [view valueForKeyPath:keyPath]; 41 | } 42 | 43 | return rv; 44 | } 45 | 46 | - (void)setProgress:(CGFloat)progress 47 | { 48 | _progress = progress; 49 | [_view setValue:[_fromValue interpolateToValue:_toValue progress:progress] forKeyPath:_keyPath]; 50 | [_view layoutIfNeeded]; 51 | } 52 | 53 | @end 54 | 55 | @implementation LNAnimator 56 | { 57 | void (^_completionHandler)(BOOL); 58 | CADisplayLink* _displayLink; 59 | CFTimeInterval _previousFrameTimestamp; 60 | CFTimeInterval _elapsedTime; 61 | } 62 | 63 | - (instancetype)init 64 | { 65 | [NSException raise:NSInvalidArgumentException format:@"Use animationWithView:keyPath:toValue: to create LNViewAnimation objects."]; 66 | return nil; 67 | } 68 | 69 | - (instancetype)_init 70 | { 71 | return [super init]; 72 | } 73 | 74 | + (instancetype)animatorWithDuration:(NSTimeInterval)duration animations:(NSArray>*)animations completionHandler:(void(^)(BOOL completed))completionHandler 75 | { 76 | LNAnimator* rv = [[LNAnimator alloc] _init]; 77 | if(rv) 78 | { 79 | rv->_duration = duration; 80 | 81 | NSAssert(animations.count > 0, @"At least one animation must be provided."); 82 | 83 | rv->_animations = animations; 84 | rv->_completionHandler = completionHandler; 85 | } 86 | 87 | return rv; 88 | } 89 | 90 | - (void)start 91 | { 92 | _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(_displayLinkDidTick)]; 93 | // _displayLink.preferredFramesPerSecond = 30; 94 | [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; 95 | } 96 | 97 | - (void)_displayLinkDidTick 98 | { 99 | if(_previousFrameTimestamp != 0) 100 | { 101 | _elapsedTime += _displayLink.timestamp - _previousFrameTimestamp; 102 | } 103 | _previousFrameTimestamp = _displayLink.timestamp; 104 | 105 | [_animations enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 106 | obj.progress = MIN(_elapsedTime / _duration, 1.0); 107 | }]; 108 | 109 | if(_elapsedTime / _duration >= 1.0) 110 | { 111 | [_displayLink invalidate]; 112 | _displayLink = nil; 113 | 114 | if(_completionHandler) 115 | { 116 | _completionHandler(YES); 117 | } 118 | } 119 | } 120 | 121 | @end 122 | -------------------------------------------------------------------------------- /lib/ios/LNInterpolation/LNInterpolable.h: -------------------------------------------------------------------------------- 1 | // 2 | // LNInterpolable.h 3 | // 4 | // Created by Leo Natan on 01/10/2016. 5 | // Copyright © 2016 Leo Natan. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | NS_SWIFT_NAME(InterpolationBehavior) 13 | typedef const NSString* LNInterpolationBehavior CF_STRING_ENUM; 14 | 15 | /** 16 | Interpolate using the default behavor of each implementation. 17 | */ 18 | extern LNInterpolationBehavior const LNInterpolationBehaviorUseDefault; 19 | 20 | 21 | /** 22 | Classes implementing this protocol support interpolation. 23 | */ 24 | NS_SWIFT_NAME(Interpolable) 25 | @protocol LNInterpolable 26 | 27 | /** 28 | Interpolates between @c self and @c toValue accodring to @c progress using the default behavior. 29 | 30 | @param toValue The value to interpolate to 31 | @param progress The progress of the interpolation 32 | @return An object representing the interpolated value at the requested progress 33 | */ 34 | - (instancetype)interpolateToValue:(id)toValue progress:(double)progress NS_SWIFT_NAME(interpolate(to:progress:)); 35 | 36 | /** 37 | Interpolates between @c self and @c toValue according to @c progress using @c behavior. 38 | 39 | @param toValue The value to interpolate to 40 | @param behavior The bahvior to use for interpolation 41 | @param progress The progress of the interpolation 42 | @return An object representing the interpolated value at the requested progress 43 | */ 44 | - (instancetype)interpolateToValue:(id)toValue progress:(double)progress behavior:(LNInterpolationBehavior)behavior NS_SWIFT_NAME(interpolate(to:progress:behavior:)); 45 | 46 | NS_ASSUME_NONNULL_END 47 | 48 | @end 49 | -------------------------------------------------------------------------------- /lib/ios/LNInterpolation/LNInterpolable.m: -------------------------------------------------------------------------------- 1 | // 2 | // LNInterpolable.c 3 | // 4 | // Created by Leo Natan on 04/10/2016. 5 | // Copyright © 2016 Leo Natan. All rights reserved. 6 | // 7 | 8 | #import "LNInterpolable.h" 9 | 10 | LNInterpolationBehavior const LNInterpolationBehaviorUseDefault = @"LNInterpolationBehaviorUseDefault"; 11 | 12 | double BackEaseOut(double p) 13 | { 14 | double f = (1 - p); 15 | return 1 - (f * f * f - 0.4*f * sin(f * M_PI)); 16 | } 17 | 18 | double QuarticEaseOut(double p) 19 | { 20 | double f = (p - 1); 21 | return f * f * f * (1 - p) + 1; 22 | } 23 | 24 | double LNLinearInterpolate(double from, double to, double p) 25 | { 26 | return from + QuarticEaseOut(p) * (to - from); 27 | } 28 | -------------------------------------------------------------------------------- /lib/ios/LNInterpolation/LNInterpolation.h: -------------------------------------------------------------------------------- 1 | // 2 | // LNInterpolation.h 3 | // 4 | // Created by Leo Natan on 05/10/2016. 5 | // Copyright © 2016 Leo Natan. All rights reserved. 6 | // 7 | 8 | #import "LNInterpolable.h" 9 | #import "NSValue+Interpolation.h" 10 | #import "Color+Interpolation.h" 11 | 12 | //! Project version number for LNInterpolationFramework. 13 | FOUNDATION_EXPORT double LNInterpolationFrameworkVersionNumber; 14 | 15 | //! Project version string for LNInterpolationFramework. 16 | FOUNDATION_EXPORT const unsigned char LNInterpolationFrameworkVersionString[]; 17 | -------------------------------------------------------------------------------- /lib/ios/LNInterpolation/NSValue+Interpolation.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSValue+Interpolation.h 3 | // 4 | // Created by Leo Natan on 01/10/2016. 5 | // Copyright © 2016 Leo Natan. All rights reserved. 6 | // 7 | 8 | #import 9 | #import "LNInterpolable.h" 10 | 11 | /** 12 | Interpolates between @c NSValue objects. 13 | Currently, the following value types are supported: 14 | Numbers (@c NSNumber), decimal numbers (@c NSDecimalNumber) 15 | Core Graphics: CGPoint, CGSize, CGVector, CGRect, CGAffineTransform (@c NSValue) 16 | UIKit: UIOffset, UIEdgeInsets (@c NSValue) 17 | AppKit: NSEdgeInsets (@c NSValue) 18 | */ 19 | @interface NSValue (Interpolation) @end 20 | -------------------------------------------------------------------------------- /lib/ios/LNInterpolation/NSValue+Interpolation.mm: -------------------------------------------------------------------------------- 1 | // 2 | // NSValue+Interpolation.mm 3 | // 4 | // Created by Leo Natan on 01/10/2016. 5 | // Copyright © 2016 Leo Natan. All rights reserved. 6 | // 7 | 8 | #import "NSValue+Interpolation.h" 9 | 10 | #if __has_include() 11 | #import 12 | #endif 13 | 14 | #if __has_include() 15 | #import 16 | #endif 17 | 18 | #if __has_include() 19 | #import 20 | 21 | #define CGPointValue pointValue 22 | #define CGRectValue rectValue 23 | #define valueWithCGPoint valueWithPoint 24 | #define UIEdgeInsets NSEdgeInsets 25 | #define valueWithCGRect valueWithRect 26 | 27 | #endif 28 | 29 | extern "C" double LNLinearInterpolate(double from, double to, double p); 30 | 31 | #if __has_include() 32 | static inline CGAffineTransform _LNInterpolateCGAffineTransform(const CGAffineTransform& fromTransform, const CGAffineTransform& toTransform, CGFloat p) 33 | { 34 | CGAffineTransform rv; 35 | rv.a = LNLinearInterpolate(fromTransform.a, toTransform.a, p); 36 | rv.b = LNLinearInterpolate(fromTransform.b, toTransform.b, p); 37 | rv.c = LNLinearInterpolate(fromTransform.c, toTransform.c, p); 38 | rv.d = LNLinearInterpolate(fromTransform.d, toTransform.d, p); 39 | rv.tx = LNLinearInterpolate(fromTransform.tx, toTransform.tx, p); 40 | rv.ty = LNLinearInterpolate(fromTransform.ty, toTransform.ty, p); 41 | 42 | return rv; 43 | } 44 | #endif 45 | 46 | @implementation NSValue (Interpolation) 47 | 48 | - (instancetype)interpolateToValue:(id)toValue progress:(double)p 49 | { 50 | return [self interpolateToValue:toValue progress:p behavior:LNInterpolationBehaviorUseDefault]; 51 | } 52 | 53 | - (instancetype)interpolateToValue:(id)toValue progress:(double)p behavior:(LNInterpolationBehavior)behavior 54 | { 55 | if(p <= 0) 56 | { 57 | return self; 58 | } 59 | 60 | if(p >= 1) 61 | { 62 | return toValue; 63 | } 64 | 65 | if([self isKindOfClass:[NSNumber class]]) 66 | { 67 | if([self isKindOfClass:[NSDecimalNumber class]]) 68 | { 69 | //Special case for decimal numbers. 70 | NSDecimalNumber* from = (id)self; 71 | NSDecimalNumber* to = (id)toValue; 72 | 73 | return [[[to decimalNumberBySubtracting:from] decimalNumberByMultiplyingBy:[[NSDecimalNumber alloc] initWithDouble:p]] decimalNumberByAdding:from]; 74 | } 75 | 76 | double f = [(NSNumber*)self doubleValue]; 77 | double f2 = [(NSNumber*)toValue doubleValue]; 78 | 79 | return [NSNumber numberWithDouble:LNLinearInterpolate(f, f2, p)]; 80 | } 81 | 82 | #if __has_include() || __has_include() 83 | if((strcmp(self.objCType, @encode(CGPoint)) == 0) || (strcmp(self.objCType, @encode(CGSize)) == 0) || (strcmp(self.objCType, @encode(CGVector)) == 0) 84 | #if __has_include() 85 | || (strcmp(self.objCType, @encode(UIOffset)) == 0) 86 | #endif 87 | ) 88 | { 89 | CGPoint v = [self CGPointValue]; 90 | CGPoint v2 = [self CGPointValue]; 91 | v.x = LNLinearInterpolate(v.x, v2.y, p); 92 | v.y = LNLinearInterpolate(v.x, v2.y, p); 93 | 94 | return [NSValue valueWithCGPoint:v]; 95 | } 96 | 97 | if((strcmp(self.objCType, @encode(CGRect)) == 0) 98 | || (strcmp(self.objCType, @encode(UIEdgeInsets)) == 0)) 99 | { 100 | CGRect v = [self CGRectValue]; 101 | CGRect v2 = [toValue CGRectValue]; 102 | v.origin.x = LNLinearInterpolate(v.origin.x, v2.origin.x, p); 103 | v.origin.y = LNLinearInterpolate(v.origin.y, v2.origin.y, p); 104 | v.size.width = LNLinearInterpolate(v.size.width, v2.size.width, p); 105 | v.size.height = LNLinearInterpolate(v.size.height, v2.size.height, p); 106 | 107 | return [NSValue valueWithCGRect:v]; 108 | } 109 | 110 | #if __has_include() 111 | if(strcmp(self.objCType, @encode(CGAffineTransform)) == 0) 112 | { 113 | return [NSValue valueWithCGAffineTransform:_LNInterpolateCGAffineTransform([self CGAffineTransformValue], [toValue CGAffineTransformValue], p)]; 114 | } 115 | #endif 116 | #endif 117 | 118 | //Unsupported value type. 119 | 120 | return self; 121 | } 122 | 123 | @end 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | -------------------------------------------------------------------------------- /lib/ios/RCTCustomInputController/RCTCustomInputController.h: -------------------------------------------------------------------------------- 1 | // 2 | // RCTCustomInputController.h 3 | // 4 | // Created by Leo Natan (Wix) on 13/12/2016. 5 | // Copyright © 2016 Leo Natan. All rights reserved. 6 | // 7 | 8 | #if __has_include() 9 | #import 10 | #else 11 | #import "RCTEventEmitter.h" 12 | #endif 13 | 14 | @interface RCTCustomInputController : RCTEventEmitter 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /lib/ios/RCTCustomInputController/RCTCustomInputController.m: -------------------------------------------------------------------------------- 1 | // 2 | // RCTCustomInputController.m 3 | // 4 | // Created by Leo Natan (Wix) on 13/12/2016. 5 | // Copyright © 2016 Leo Natan. All rights reserved. 6 | // 7 | 8 | #import "RCTCustomInputController.h" 9 | #import "RCTCustomKeyboardViewController.h" 10 | 11 | #import 12 | #import 13 | 14 | #import "LNAnimator.h" 15 | 16 | #define kHlperViewTag 0x1f1f1f 17 | 18 | NSString *const RCTCustomInputControllerKeyboardResigendEvent = @"kbdResigned"; 19 | 20 | @protocol _WXInputHelperViewDelegate 21 | -(void)_WXInputHelperViewResignFirstResponder:(UIView*)wxInputHelperView; 22 | @end 23 | 24 | @interface _WXInputHelperView : UIView 25 | 26 | @property (nullable, nonatomic, readwrite, strong) UIInputViewController *inputViewController; 27 | @property (nonatomic, weak) id<_WXInputHelperViewDelegate> delegate; 28 | @property (nullable, readwrite, strong) UIView *inputAccessoryView; 29 | @property (nonatomic) BOOL keepInSuperviewOnResign; 30 | 31 | @end 32 | 33 | @implementation _WXInputHelperView 34 | 35 | - (BOOL)canBecomeFirstResponder 36 | { 37 | return YES; 38 | } 39 | 40 | - (BOOL)resignFirstResponder 41 | { 42 | BOOL rv = [super resignFirstResponder]; 43 | 44 | if (!_keepInSuperviewOnResign) 45 | { 46 | [self removeFromSuperview]; 47 | 48 | if(self.delegate && [self.delegate respondsToSelector:@selector(_WXInputHelperViewResignFirstResponder:)]) 49 | { 50 | [self.delegate _WXInputHelperViewResignFirstResponder:self]; 51 | } 52 | 53 | } 54 | 55 | return rv; 56 | } 57 | 58 | @end 59 | 60 | 61 | @interface RCTCustomInputController () <_WXInputHelperViewDelegate> { 62 | UIWindow *_fullScreenWindow; 63 | BOOL _performingExpandTransition; 64 | } 65 | 66 | @property (nonatomic) BOOL customInputComponentPresented; 67 | @end 68 | 69 | @implementation RCTCustomInputController 70 | 71 | + (BOOL)requiresMainQueueSetup 72 | { 73 | return YES; 74 | } 75 | 76 | - (dispatch_queue_t)methodQueue 77 | { 78 | return dispatch_get_main_queue(); 79 | } 80 | 81 | - (NSArray *)supportedEvents 82 | { 83 | return @[RCTCustomInputControllerKeyboardResigendEvent]; 84 | } 85 | 86 | RCT_EXPORT_MODULE(CustomInputController) 87 | 88 | - (instancetype)init 89 | { 90 | self = [super init]; 91 | if (self) 92 | { 93 | self.customInputComponentPresented = NO; 94 | } 95 | return self; 96 | } 97 | 98 | -(UIView*)getFirstResponder:(UIView*)view 99 | { 100 | if (view == nil || [view isFirstResponder]) 101 | { 102 | return view; 103 | } 104 | 105 | for (UIView *subview in view.subviews) 106 | { 107 | UIView *firstResponder = [self getFirstResponder:subview]; 108 | if(firstResponder != nil) 109 | { 110 | return firstResponder; 111 | } 112 | } 113 | return nil; 114 | } 115 | 116 | - (BOOL)shouldUseSafeAreaFrom:(NSDictionary *)params { 117 | return [params[@"useSafeArea"] isEqual:@(1)]; 118 | } 119 | 120 | RCT_EXPORT_METHOD(presentCustomInputComponent:(nonnull NSNumber*)inputFieldTag params:(nonnull NSDictionary*)params) 121 | { 122 | RCTBridge* bridge = [self.bridge valueForKey:@"parentBridge"]; 123 | if(bridge == nil) 124 | { 125 | return; 126 | } 127 | 128 | UIView* inputField = [self.bridge.uiManager viewForReactTag:inputFieldTag]; 129 | NSDictionary *initialProps = params[@"initialProps"]; 130 | RCTRootView* rv = [[RCTRootView alloc] initWithBridge:bridge moduleName:params[@"component"] initialProperties:initialProps]; 131 | if(initialProps != nil && initialProps[@"backgroundColor"] != nil) 132 | { 133 | UIColor *backgroundColor = [RCTConvert UIColor:initialProps[@"backgroundColor"]]; 134 | if(backgroundColor != nil) 135 | { 136 | rv.backgroundColor = backgroundColor; 137 | } 138 | } 139 | 140 | self.customInputComponentPresented = NO; 141 | 142 | BOOL useSafeArea = [self shouldUseSafeAreaFrom:params]; 143 | RCTCustomKeyboardViewController* customKeyboardController = [[RCTCustomKeyboardViewController alloc] initWithUsingSafeArea:useSafeArea]; 144 | customKeyboardController.rootView = rv; 145 | 146 | _WXInputHelperView* helperView = [[_WXInputHelperView alloc] initWithFrame:CGRectZero]; 147 | helperView.tag = kHlperViewTag; 148 | helperView.delegate = self; 149 | 150 | if ([inputField isKindOfClass:NSClassFromString(@"RCTTextView")]) 151 | { 152 | UITextView *textView = nil; 153 | Ivar backedTextInputIvar = class_getInstanceVariable([inputField class], "_backedTextInput"); 154 | if (backedTextInputIvar != NULL) 155 | { 156 | textView = [inputField valueForKey:@"_backedTextInput"]; 157 | } 158 | else if([inputField isKindOfClass:[UITextView class]]) 159 | { 160 | textView = (UITextView*)inputField; 161 | } 162 | 163 | if (textView != nil) 164 | { 165 | helperView.inputAccessoryView = textView.inputAccessoryView; 166 | } 167 | } 168 | else if ([inputField isKindOfClass:NSClassFromString(@"RCTUITextView")] && [inputField isKindOfClass:[UITextView class]]) 169 | { 170 | UITextView *textView = (UITextView*)inputField; 171 | helperView.inputAccessoryView = textView.inputAccessoryView; 172 | } 173 | else if([inputField isKindOfClass:NSClassFromString(@"RCTMultilineTextInputView")]) 174 | { 175 | Ivar backedTextInputIvar = class_getInstanceVariable([inputField class], "_backedTextInputView"); 176 | if (backedTextInputIvar != NULL) 177 | { 178 | id textViewObj = [inputField valueForKey:@"_backedTextInputView"]; 179 | if ([textViewObj isKindOfClass:[UITextView class]]) 180 | { 181 | UITextView *textView = (UITextView*)textViewObj; 182 | helperView.inputAccessoryView = textView.inputAccessoryView; 183 | } 184 | } 185 | } 186 | else 187 | { 188 | UIView *firstResponder = [self getFirstResponder:inputField]; 189 | helperView.inputAccessoryView = firstResponder.inputAccessoryView; 190 | } 191 | 192 | [helperView reloadInputViews]; 193 | 194 | helperView.backgroundColor = [UIColor clearColor]; 195 | [inputField.superview addSubview:helperView]; 196 | [inputField.superview sendSubviewToBack:helperView]; 197 | 198 | helperView.inputViewController = customKeyboardController; 199 | [helperView reloadInputViews]; 200 | [helperView becomeFirstResponder]; 201 | 202 | self.customInputComponentPresented = YES; 203 | } 204 | 205 | RCT_EXPORT_METHOD(resetInput:(nonnull NSNumber*)inputFieldTag) 206 | { 207 | self.customInputComponentPresented = NO; 208 | 209 | UIView* inputField = [self.bridge.uiManager viewForReactTag:inputFieldTag]; 210 | if(inputField != nil) 211 | { 212 | _WXInputHelperView* helperView = [inputField.superview viewWithTag:kHlperViewTag]; 213 | if(helperView != nil && [helperView isFirstResponder]) 214 | {//restore the first responder only if it was already the first responder to prevent the keyboard from opening again if not necessary 215 | [inputField reactFocus]; 216 | } 217 | } 218 | } 219 | 220 | RCT_EXPORT_METHOD(dismissKeyboard) 221 | { 222 | UIView *firstResponder = [self getFirstResponder:[UIApplication sharedApplication].delegate.window]; 223 | if(firstResponder != nil) 224 | { 225 | [firstResponder resignFirstResponder]; 226 | } 227 | } 228 | 229 | -(void)changeKeyboardHeightForInput:(nonnull NSNumber*)inputFieldTag newHeight:(CGFloat)newHeight 230 | { 231 | UIView* inputField = [self.bridge.uiManager viewForReactTag:inputFieldTag]; 232 | if(inputField != nil) 233 | { 234 | _WXInputHelperView* helperView = [inputField.superview viewWithTag:kHlperViewTag]; 235 | if(helperView != nil) 236 | { 237 | [((RCTCustomKeyboardViewController*)helperView.inputViewController) setAllowsSelfSizing:YES]; 238 | ((RCTCustomKeyboardViewController*)helperView.inputViewController).heightConstraint.constant = newHeight; 239 | 240 | UIInputView *inputView = helperView.inputViewController.inputView; 241 | [inputView setNeedsUpdateConstraints]; 242 | [UIView animateWithDuration:0.55 243 | delay:0 244 | usingSpringWithDamping:500.0 245 | initialSpringVelocity:0 246 | options:UIViewAnimationOptionCurveEaseOut 247 | animations:^{ [inputView layoutIfNeeded]; } 248 | completion:nil]; 249 | } 250 | } 251 | } 252 | 253 | -(UIColor*)reactViewAvgColor:(RCTRootView*)rootView 254 | { 255 | if (rootView.frame.size.width == 0 || rootView.frame.size.height == 0) 256 | { 257 | return [UIColor clearColor]; 258 | } 259 | 260 | UIGraphicsBeginImageContextWithOptions(CGSizeMake(1, 1), YES, 0); 261 | CGContextTranslateCTM(UIGraphicsGetCurrentContext(), 0, -(rootView.frame.size.height - 1)); 262 | [rootView.layer renderInContext:UIGraphicsGetCurrentContext()]; 263 | UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); 264 | UIGraphicsEndImageContext(); 265 | 266 | CFDataRef pixelData = CGDataProviderCopyData(CGImageGetDataProvider(image.CGImage)); 267 | const UInt8* data = CFDataGetBytePtr(pixelData); 268 | CFRelease(pixelData); 269 | 270 | //after scale defaults to bgr 271 | CGFloat red = data[2] / 255.0f, 272 | green = data[1] / 255.0f, 273 | blue = data[0] / 255.0f, 274 | alpha = data[3] / 255.0f; 275 | 276 | UIColor *color = [UIColor colorWithRed:red green:green blue:blue alpha:alpha]; 277 | return color; 278 | } 279 | 280 | RCT_EXPORT_METHOD(expandFullScreenForInput:(nonnull NSNumber*)inputFieldTag) 281 | { 282 | if (_fullScreenWindow != nil || _performingExpandTransition) 283 | { 284 | return; 285 | } 286 | 287 | UIView* inputField = [self.bridge.uiManager viewForReactTag:inputFieldTag]; 288 | if(inputField != nil) 289 | { 290 | _WXInputHelperView* helperView = [inputField.superview viewWithTag:kHlperViewTag]; 291 | if(helperView != nil) 292 | { 293 | _performingExpandTransition = YES; 294 | 295 | helperView.keepInSuperviewOnResign = YES; 296 | 297 | RCTCustomKeyboardViewController *customKeyboardViewController = (RCTCustomKeyboardViewController*)helperView.inputViewController; 298 | RCTRootView *rv = customKeyboardViewController.rootView; 299 | UIInputView *inputView = helperView.inputViewController.inputView; 300 | 301 | _fullScreenWindow = [[UIWindow alloc] initWithFrame:[inputView.window convertRect:inputView.bounds fromView:inputView]]; 302 | UIColor *originalBackgroundColor = rv.backgroundColor; 303 | rv.backgroundColor = [self reactViewAvgColor:rv]; 304 | 305 | customKeyboardViewController.rootView = nil; 306 | 307 | UIViewController *vc = [UIViewController new]; 308 | vc.view = rv; 309 | 310 | inputView.window.hidden = YES; 311 | 312 | [UIView performWithoutAnimation:^{ 313 | _fullScreenWindow.hidden = NO; 314 | _fullScreenWindow.rootViewController = vc; 315 | 316 | [_fullScreenWindow layoutIfNeeded]; 317 | }]; 318 | 319 | [[LNAnimator animatorWithDuration:0.5 320 | animations:@[[LNViewAnimation animationWithView:_fullScreenWindow keyPath:@"frame" toValue:[NSValue valueWithCGRect:[UIScreen mainScreen].bounds]]] 321 | completionHandler:^(BOOL completed) 322 | { 323 | [UIView performWithoutAnimation:^{ 324 | inputView.window.hidden = NO; 325 | [helperView resignFirstResponder]; 326 | [_fullScreenWindow makeKeyAndVisible]; 327 | 328 | rv.backgroundColor = originalBackgroundColor; 329 | }]; 330 | _performingExpandTransition = NO; 331 | }] start]; 332 | } 333 | } 334 | } 335 | 336 | RCT_EXPORT_METHOD(resetSizeForInput:(nonnull NSNumber*)inputFieldTag) 337 | { 338 | if (_fullScreenWindow == nil || _performingExpandTransition) 339 | { 340 | return; 341 | } 342 | 343 | UIView* inputField = [self.bridge.uiManager viewForReactTag:inputFieldTag]; 344 | if(inputField != nil) 345 | { 346 | _WXInputHelperView* helperView = [inputField.superview viewWithTag:kHlperViewTag]; 347 | if(helperView != nil) 348 | { 349 | _performingExpandTransition = YES; 350 | 351 | __block CGRect keyboardTargetFrame; 352 | UIInputView *inputView = helperView.inputViewController.inputView; 353 | 354 | [UIView performWithoutAnimation:^{ 355 | [helperView.window makeKeyWindow]; 356 | [helperView becomeFirstResponder]; 357 | [helperView layoutIfNeeded]; 358 | 359 | keyboardTargetFrame = [inputView.window convertRect:inputView.bounds fromView:inputView]; 360 | }]; 361 | 362 | _fullScreenWindow.windowLevel = inputView.window.windowLevel + 1; 363 | 364 | [_fullScreenWindow layoutIfNeeded]; 365 | [_fullScreenWindow endEditing:YES]; 366 | 367 | [[LNAnimator animatorWithDuration:0.5 368 | animations:@[[LNViewAnimation animationWithView:_fullScreenWindow keyPath:@"frame" toValue:[NSValue valueWithCGRect:keyboardTargetFrame]]] 369 | completionHandler:^(BOOL completed) 370 | { 371 | RCTCustomKeyboardViewController *customKeyboardViewController = (RCTCustomKeyboardViewController*)helperView.inputViewController; 372 | RCTRootView *rv = (RCTRootView*)_fullScreenWindow.rootViewController.view; 373 | 374 | [UIView performWithoutAnimation:^{ 375 | 376 | _fullScreenWindow.rootViewController.view = [UIView new]; 377 | customKeyboardViewController.rootView = rv; 378 | 379 | _fullScreenWindow.hidden = YES; 380 | _fullScreenWindow = nil; 381 | }]; 382 | 383 | helperView.keepInSuperviewOnResign = NO; 384 | _performingExpandTransition = NO; 385 | }] start]; 386 | } 387 | } 388 | } 389 | 390 | #pragma mark - _WXInputHelperViewDelegate methods 391 | 392 | -(void)_WXInputHelperViewResignFirstResponder:(UIView*)wxInputHelperView 393 | { 394 | if(self.customInputComponentPresented) 395 | { 396 | [self sendEventWithName:RCTCustomInputControllerKeyboardResigendEvent body:nil]; 397 | } 398 | self.customInputComponentPresented = NO; 399 | } 400 | 401 | @end 402 | -------------------------------------------------------------------------------- /lib/ios/RCTCustomInputController/RCTCustomKeyboardViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // RCTCustomKeyboardViewController.h 3 | // 4 | // Created by Leo Natan (Wix) on 12/12/2016. 5 | // Copyright © 2016 Leo Natan. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | #if __has_include() 11 | #import 12 | #else 13 | #import "RCTRootView.h" 14 | #endif 15 | 16 | @interface RCTCustomKeyboardViewController : UIInputViewController 17 | 18 | - (instancetype)initWithUsingSafeArea:(BOOL)useSafeArea; 19 | - (void) setAllowsSelfSizing:(BOOL)allowsSelfSizing; 20 | 21 | @property (nonatomic, strong) NSLayoutConstraint *heightConstraint; 22 | @property (nonatomic, strong) RCTRootView *rootView; 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /lib/ios/RCTCustomInputController/RCTCustomKeyboardViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // RCTCustomKeyboardViewController.m 3 | // 4 | // Created by Leo Natan (Wix) on 12/12/2016. 5 | // Copyright © 2016 Leo Natan. All rights reserved. 6 | // 7 | 8 | #import "RCTCustomKeyboardViewController.h" 9 | 10 | #if __has_include() 11 | #import 12 | #define ObservingInputAccessoryView_IsAvailable true 13 | #endif 14 | 15 | @interface RCTCustomKeyboardViewController () 16 | @property (nonatomic, assign, getter=isUsingSafeArea) BOOL useSafeArea; 17 | @end 18 | 19 | @implementation RCTCustomKeyboardViewController 20 | 21 | - (instancetype)initWithUsingSafeArea:(BOOL)useSafeArea 22 | { 23 | self = [super init]; 24 | 25 | if(self) 26 | { 27 | self.inputView = [[UIInputView alloc] initWithFrame:CGRectZero inputViewStyle:UIInputViewStyleKeyboard]; 28 | 29 | self.heightConstraint = [self.inputView.heightAnchor constraintEqualToConstant:0]; 30 | self.useSafeArea = useSafeArea; 31 | 32 | #ifdef ObservingInputAccessoryView_IsAvailable 33 | ObservingInputAccessoryView *activeObservingInputAccessoryView = [ObservingInputAccessoryViewManager sharedInstance].activeObservingInputAccessoryView; 34 | if (activeObservingInputAccessoryView != nil) 35 | { 36 | CGFloat keyboardHeight = activeObservingInputAccessoryView.keyboardHeight; 37 | if (keyboardHeight > 0) 38 | { 39 | self.heightConstraint.constant = keyboardHeight; 40 | [self setAllowsSelfSizing:YES]; 41 | } 42 | } 43 | #endif 44 | //!!! 45 | self.view.translatesAutoresizingMaskIntoConstraints = NO; 46 | } 47 | 48 | return self; 49 | } 50 | 51 | - (void) setAllowsSelfSizing:(BOOL)allowsSelfSizing 52 | { 53 | if(self.inputView.allowsSelfSizing != allowsSelfSizing) 54 | { 55 | self.inputView.allowsSelfSizing = allowsSelfSizing; 56 | self.heightConstraint.active = allowsSelfSizing; 57 | } 58 | } 59 | 60 | -(void)setRootView:(RCTRootView*)rootView 61 | { 62 | if(_rootView != nil) 63 | { 64 | [_rootView removeFromSuperview]; 65 | } 66 | 67 | _rootView = rootView; 68 | _rootView.translatesAutoresizingMaskIntoConstraints = NO; 69 | [self.inputView addSubview:_rootView]; 70 | 71 | [self updateRootViewConstraints]; 72 | [self.inputView setNeedsLayout]; 73 | } 74 | 75 | - (void)updateRootViewConstraints { 76 | [_rootView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor].active = YES; 77 | [_rootView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor].active = YES; 78 | [_rootView.topAnchor constraintEqualToAnchor:self.view.topAnchor].active = YES; 79 | 80 | NSLayoutYAxisAnchor *yAxisAnchor = [self bottomLayoutAnchorUsingSafeArea:self.isUsingSafeArea]; 81 | [_rootView.bottomAnchor constraintEqualToAnchor:yAxisAnchor].active = YES; 82 | } 83 | 84 | - (NSLayoutYAxisAnchor *)bottomLayoutAnchorUsingSafeArea:(BOOL)useSafeArea { 85 | NSLayoutYAxisAnchor *yAxisAnchor = self.view.bottomAnchor; 86 | 87 | if (!useSafeArea) { 88 | return yAxisAnchor; 89 | } 90 | 91 | #if __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_10_3 92 | if (@available(iOS 11.0, *)) { 93 | yAxisAnchor = self.view.safeAreaLayoutGuide.bottomAnchor; 94 | } 95 | #endif 96 | return yAxisAnchor; 97 | } 98 | @end 99 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-keyboard-input", 3 | "description": "React Native Custom Input Controller", 4 | "version": "6.0.2", 5 | "main": "src/index.js", 6 | "publishConfig": { 7 | "registry": "https://registry.npmjs.org/" 8 | }, 9 | "homepage": "https://github.com/wix/react-native-keyboard-input", 10 | "author": "Leo Natan ", 11 | "license": "MIT", 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/wix/react-native-keyboard-input.git" 15 | }, 16 | "nativePackage": true, 17 | "keywords": [ 18 | "react-native", 19 | "react", 20 | "react-component", 21 | "custom-input", 22 | "input" 23 | ], 24 | "bugs": { 25 | "url": "https://github.com/wix/react-native-keyboard-input/issues" 26 | }, 27 | "scripts": { 28 | "android": "react-native run-android", 29 | "ios": "react-native run-ios", 30 | "start": "react-native start", 31 | "test": "jest", 32 | "lint": "eslint src -c .eslintrc", 33 | "e2e": "detox test" 34 | }, 35 | "dependencies": { 36 | "lodash": "^4.17.4", 37 | "react-native-keyboard-tracking-view": "^5.5.0" 38 | }, 39 | "peerDependencies": { 40 | "react": "16.9.0", 41 | "react-native": "0.61.4" 42 | }, 43 | "devDependencies": { 44 | "@babel/core": "7.7.2", 45 | "@babel/runtime": "7.7.2", 46 | "babel-eslint": "^7.2.1", 47 | "babel-jest": "24.9.0", 48 | "babel-preset-react-native": "1.9.1", 49 | "detox": "latest", 50 | "eslint": "6.6.0", 51 | "eslint-config-airbnb": "^14.0.0", 52 | "eslint-plugin-import": "^2.2.0", 53 | "eslint-plugin-jsx-a11y": "4.0.0", 54 | "eslint-plugin-react": "^6.9.0", 55 | "eslint-plugin-react-native": "^2.0.0", 56 | "jest": "24.9.0", 57 | "mocha": "^3.3.0", 58 | "react": "16.9.0", 59 | "react-native": "0.61.4", 60 | "react-test-renderer": "16.9.0", 61 | "react-native-autogrow-textinput": "^5.0.0", 62 | "react-native-navigation": "^3.4.0", 63 | "metro-react-native-babel-preset": "0.56.3", 64 | "prop-types": "^15.7.2" 65 | }, 66 | "jest": { 67 | "preset": "react-native", 68 | "testPathIgnorePatterns": [ 69 | "/e2e/", 70 | "/ios/", 71 | "/android/", 72 | "/node_modules/" 73 | ], 74 | "testRegex": "(/__tests__/.*|(\\.|/)(spec|test))\\.js?$" 75 | }, 76 | "detox": { 77 | "configurations": { 78 | "ios.sim.debug": { 79 | "binaryPath": "ios/DerivedData/KeyboardInput/Build/Products/Debug-iphonesimulator/KeyboardInput.app", 80 | "build": "xcodebuild -project ios/KeyboardInput.xcodeproj -scheme KeyboardInput -configuration Debug -sdk iphonesimulator -derivedDataPath ios", 81 | "type": "ios.simulator", 82 | "name": "iPhone 7" 83 | } 84 | } 85 | }, 86 | "babel": { 87 | "env": { 88 | "test": { 89 | "presets": [ 90 | "react-native" 91 | ], 92 | "retainLines": true 93 | } 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/CustomKeyboardView.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import {Platform, requireNativeComponent} from 'react-native'; 4 | import TextInputKeyboardManagerIOS from './TextInputKeyboardMangerIOS'; 5 | import TextInputKeyboardManagerAndroid from './TextInputKeyboardManagerAndroid'; 6 | import KeyboardRegistry from './KeyboardsRegistry'; 7 | 8 | const IsAndroid = Platform.OS === 'android'; 9 | const IsIOS = Platform.OS === 'ios'; 10 | 11 | const CustomKeyboardViewNativeAndroid = requireNativeComponent('CustomKeyboardViewNative'); 12 | 13 | export default class CustomKeyboardView extends Component { 14 | static propTypes = { 15 | inputRef: PropTypes.object, 16 | initialProps: PropTypes.object, 17 | component: PropTypes.string, 18 | onItemSelected: PropTypes.func, 19 | useSafeArea: PropTypes.bool, 20 | }; 21 | static defaultProps = { 22 | initialProps: {}, 23 | useSafeArea: true, 24 | }; 25 | 26 | constructor(props) { 27 | super(props); 28 | 29 | const {inputRef, component, initialProps, onItemSelected, useSafeArea} = props; 30 | if (component) { 31 | this.addOnItemSelectListener(onItemSelected, component); 32 | 33 | if (TextInputKeyboardManagerIOS && inputRef) { 34 | TextInputKeyboardManagerIOS.setInputComponent(inputRef, {component, initialProps, useSafeArea}); 35 | } 36 | 37 | this.registeredRequestShowKeyboard = false; 38 | } 39 | 40 | this.keyboardExpandedToggle = {}; 41 | if (IsIOS && TextInputKeyboardManagerIOS) { 42 | KeyboardRegistry.addListener('onToggleExpandedKeyboard', (args) => { 43 | if (this.props.inputRef) { 44 | if (this.keyboardExpandedToggle[args.keyboardId] === undefined) { 45 | this.keyboardExpandedToggle[args.keyboardId] = false; 46 | } 47 | this.keyboardExpandedToggle[args.keyboardId] = !this.keyboardExpandedToggle[args.keyboardId]; 48 | TextInputKeyboardManagerIOS.toggleExpandKeyboard( 49 | this.props.inputRef, this.keyboardExpandedToggle[args.keyboardId], this.props.initialProps.expandWithLayoutAnimation, 50 | ); 51 | } 52 | }); 53 | } 54 | } 55 | 56 | shouldComponentUpdate(nextProps) { 57 | return (nextProps.component !== this.props.component); 58 | } 59 | 60 | componentWillUnmount() { 61 | KeyboardRegistry.removeListeners('onRequestShowKeyboard'); 62 | KeyboardRegistry.removeListeners('onToggleExpandedKeyboard'); 63 | 64 | if (this.keyboardEventListeners) { 65 | this.keyboardEventListeners.forEach(eventListener => eventListener.remove()); 66 | } 67 | if (this.props.component) { 68 | KeyboardRegistry.removeListeners(`${this.props.component}.onItemSelected`); 69 | } 70 | } 71 | 72 | addOnItemSelectListener(onItemSelected, component) { 73 | if (onItemSelected) { 74 | KeyboardRegistry.addListener(`${component}.onItemSelected`, (args) => { 75 | onItemSelected(component, args); 76 | }); 77 | } 78 | } 79 | 80 | async UNSAFE_componentWillReceiveProps(nextProps) { //eslint-disable-line 81 | const {inputRef, component, initialProps, onRequestShowKeyboard, useSafeArea} = nextProps; 82 | 83 | if (IsAndroid) { 84 | if (this.props.component !== component && !component) { 85 | await TextInputKeyboardManagerAndroid.reset(); 86 | } 87 | } 88 | 89 | if (IsIOS && TextInputKeyboardManagerIOS && inputRef && component !== this.props.component) { 90 | if (component) { 91 | TextInputKeyboardManagerIOS.setInputComponent(inputRef, { 92 | component, 93 | initialProps, 94 | useSafeArea, 95 | }); 96 | } else { 97 | TextInputKeyboardManagerIOS.removeInputComponent(inputRef); 98 | } 99 | } 100 | 101 | if (onRequestShowKeyboard && !this.registeredRequestShowKeyboard) { 102 | this.registeredRequestShowKeyboard = true; 103 | KeyboardRegistry.addListener('onRequestShowKeyboard', (args) => { 104 | onRequestShowKeyboard(args.keyboardId); 105 | }); 106 | } 107 | this.registerListener(this.props, nextProps); 108 | } 109 | 110 | registerListener(props, nextProps) { 111 | const {component, onItemSelected} = nextProps; 112 | if (component && props.component !== component) { 113 | if (props.component) { 114 | KeyboardRegistry.removeListeners(`${props.component}.onItemSelected`); 115 | } 116 | KeyboardRegistry.removeListeners(`${component}.onItemSelected`); 117 | this.addOnItemSelectListener(onItemSelected, component); 118 | } 119 | } 120 | 121 | render() { 122 | if (IsAndroid) { 123 | const {component, initialProps} = this.props; 124 | const KeyboardComponent = component && KeyboardRegistry.getKeyboard(component); 125 | return ( 126 | 127 | {KeyboardComponent && } 128 | 129 | ); 130 | } 131 | return null; 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/KeyboardAccessoryView.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import {StyleSheet, Platform, NativeModules, NativeEventEmitter, DeviceEventEmitter, processColor, BackHandler} from 'react-native'; 4 | import {KeyboardTrackingView} from 'react-native-keyboard-tracking-view'; 5 | import CustomKeyboardView from './CustomKeyboardView'; 6 | import KeyboardUtils from './utils/KeyboardUtils'; 7 | 8 | const IsIOS = Platform.OS === 'ios'; 9 | const IsAndroid = Platform.OS === 'android'; 10 | 11 | export default class KeyboardAccessoryView extends Component { 12 | static propTypes = { 13 | renderContent: PropTypes.func, 14 | onHeightChanged: PropTypes.func, 15 | kbInputRef: PropTypes.object, 16 | kbComponent: PropTypes.string, 17 | kbInitialProps: PropTypes.object, 18 | onItemSelected: PropTypes.func, 19 | onRequestShowKeyboard: PropTypes.func, 20 | onKeyboardResigned: PropTypes.func, 21 | iOSScrollBehavior: PropTypes.number, 22 | revealKeyboardInteractive: PropTypes.bool, 23 | manageScrollView: PropTypes.bool, 24 | requiresSameParentToManageScrollView: PropTypes.bool, 25 | addBottomView: PropTypes.bool, 26 | allowHitsOutsideBounds: PropTypes.bool, 27 | useSafeArea: PropTypes.bool, 28 | }; 29 | static defaultProps = { 30 | iOSScrollBehavior: -1, 31 | revealKeyboardInteractive: false, 32 | manageScrollView: true, 33 | requiresSameParentToManageScrollView: false, 34 | addBottomView: false, 35 | allowHitsOutsideBounds: false, 36 | }; 37 | 38 | constructor(props) { 39 | super(props); 40 | 41 | this.onContainerComponentHeightChanged = this.onContainerComponentHeightChanged.bind(this); 42 | this.processInitialProps = this.processInitialProps.bind(this); 43 | this.registerForKeyboardResignedEvent = this.registerForKeyboardResignedEvent.bind(this); 44 | this.registerAndroidBackHandler = this.registerAndroidBackHandler.bind(this); 45 | this.onAndroidBackPressed = this.onAndroidBackPressed.bind(this); 46 | 47 | this.registerForKeyboardResignedEvent(); 48 | this.registerAndroidBackHandler(); 49 | } 50 | 51 | componentWillUnmount() { 52 | if (this.customInputControllerEventsSubscriber) { 53 | this.customInputControllerEventsSubscriber.remove(); 54 | } 55 | if (IsAndroid) { 56 | BackHandler.removeEventListener('hardwareBackPress', this.onAndroidBackPressed); 57 | } 58 | } 59 | 60 | onContainerComponentHeightChanged(event) { 61 | if (this.props.onHeightChanged) { 62 | this.props.onHeightChanged(event.nativeEvent.layout.height); 63 | } 64 | } 65 | 66 | onAndroidBackPressed() { 67 | if (this.props.kbComponent) { 68 | KeyboardUtils.dismiss(); 69 | return true; 70 | } 71 | return false; 72 | } 73 | 74 | getIOSTrackingScrollBehavior() { 75 | let scrollBehavior = this.props.iOSScrollBehavior; 76 | if (IsIOS && NativeModules.KeyboardTrackingViewManager && scrollBehavior === -1) { 77 | scrollBehavior = NativeModules.KeyboardTrackingViewManager.KeyboardTrackingScrollBehaviorFixedOffset; 78 | } 79 | return scrollBehavior; 80 | } 81 | 82 | async getNativeProps() { 83 | if (this.trackingViewRef) { 84 | return await this.trackingViewRef.getNativeProps(); 85 | } 86 | return {}; 87 | } 88 | 89 | registerForKeyboardResignedEvent() { 90 | let eventEmitter = null; 91 | if (IsIOS) { 92 | if (NativeModules.CustomInputController) { 93 | eventEmitter = new NativeEventEmitter(NativeModules.CustomInputController); 94 | } 95 | } else { 96 | eventEmitter = DeviceEventEmitter; 97 | } 98 | 99 | if (eventEmitter !== null) { 100 | this.customInputControllerEventsSubscriber = eventEmitter.addListener('kbdResigned', () => { 101 | if (this.props.onKeyboardResigned) { 102 | this.props.onKeyboardResigned(); 103 | } 104 | }); 105 | } 106 | } 107 | 108 | registerAndroidBackHandler() { 109 | if (IsAndroid) { 110 | BackHandler.addEventListener('hardwareBackPress', this.onAndroidBackPressed); 111 | } 112 | } 113 | 114 | processInitialProps() { 115 | if (IsIOS && this.props.kbInitialProps && this.props.kbInitialProps.backgroundColor) { 116 | const processedProps = Object.assign({}, this.props.kbInitialProps); 117 | processedProps.backgroundColor = processColor(processedProps.backgroundColor); 118 | return processedProps; 119 | } 120 | return this.props.kbInitialProps; 121 | } 122 | 123 | scrollToStart() { 124 | if (this.trackingViewRef) { 125 | this.trackingViewRef.scrollToStart(); 126 | } 127 | } 128 | 129 | render() { 130 | return ( 131 | this.trackingViewRef = r} 133 | style={styles.trackingToolbarContainer} 134 | onLayout={this.onContainerComponentHeightChanged} 135 | scrollBehavior={this.getIOSTrackingScrollBehavior()} 136 | revealKeyboardInteractive={this.props.revealKeyboardInteractive} 137 | manageScrollView={this.props.manageScrollView} 138 | requiresSameParentToManageScrollView={this.props.requiresSameParentToManageScrollView} 139 | addBottomView={this.props.addBottomView} 140 | allowHitsOutsideBounds={this.props.allowHitsOutsideBounds} 141 | > 142 | {this.props.renderContent && this.props.renderContent()} 143 | 151 | 152 | ); 153 | } 154 | } 155 | 156 | const styles = StyleSheet.create({ 157 | trackingToolbarContainer: { 158 | ...Platform.select({ 159 | ios: { 160 | position: 'absolute', 161 | bottom: 0, 162 | left: 0, 163 | right: 0, 164 | }, 165 | }), 166 | }, 167 | }); 168 | -------------------------------------------------------------------------------- /src/KeyboardRegistry.spec.js: -------------------------------------------------------------------------------- 1 | import {AppRegistry, View} from 'react-native'; 2 | import React from 'react'; 3 | import KeyboardRegistry from './KeyboardsRegistry'; 4 | 5 | describe('KeyboardRegistry - components', () => { 6 | const mockComponent = 'test_component'; 7 | const anotherMockComponent = 'test_component2'; 8 | const MockElement = React.createElement(View, [], ['Hello world']); 9 | const AnotherMockElement = React.createElement(View, [], ['Hello world again!']); 10 | const mockGenerator = () => MockElement; 11 | const anotherMockGenerator = () => AnotherMockElement; 12 | 13 | beforeEach(() => { 14 | AppRegistry.registerComponent = jest.fn(AppRegistry.registerComponent); 15 | console.error = jest.fn(); //eslint-disable-line 16 | }); 17 | 18 | it('should register the component in the keyboard registry', () => { 19 | KeyboardRegistry.registerKeyboard(mockComponent, mockGenerator); 20 | expect(KeyboardRegistry.getKeyboard(mockComponent)).toEqual(MockElement); 21 | }); 22 | 23 | it('should register the component in the App Registry as well', () => { 24 | KeyboardRegistry.registerKeyboard(mockComponent, mockGenerator); 25 | 26 | expect(AppRegistry.registerComponent).toHaveBeenCalledTimes(1); 27 | expect(AppRegistry.registerComponent.mock.calls[0][0]).toEqual(mockComponent); 28 | expect(AppRegistry.registerComponent.mock.calls[0][1]).toEqual(mockGenerator); 29 | }); 30 | 31 | it('should fail if generator is not provided and produce an error', () => { 32 | KeyboardRegistry.registerKeyboard(mockComponent); 33 | expect(AppRegistry.registerComponent).not.toHaveBeenCalled(); 34 | expect(console.error).toHaveBeenCalledTimes(1); //eslint-disable-line 35 | }); 36 | 37 | it('should only allow to register a generator function and produce an error', () => { 38 | KeyboardRegistry.registerKeyboard(mockComponent, MockElement); 39 | expect(AppRegistry.registerComponent).not.toHaveBeenCalled(); 40 | expect(console.error).toHaveBeenCalledTimes(1); //eslint-disable-line 41 | }); 42 | 43 | it('should produce an error if component was not and return undefined', () => { 44 | const res = KeyboardRegistry.getKeyboard('not_existing_component'); 45 | expect(console.error).toHaveBeenCalledTimes(1); //eslint-disable-line 46 | expect(res).toBe(undefined); 47 | }); 48 | 49 | it('should get all keyboards, id of the keyboard included as a param', () => { 50 | const mockParams = {icon: 5, title: 'mock title'}; 51 | KeyboardRegistry.registerKeyboard(mockComponent, mockGenerator, mockParams); 52 | const keyboards = KeyboardRegistry.getAllKeyboards(); 53 | expect(keyboards).toEqual([{id: mockComponent, ...mockParams}]); 54 | }); 55 | 56 | it('should get specific keyboards by demand', () => { 57 | const mockParams1 = {icon: 5, title: 'mock title'}; 58 | const mockParams2 = {icon: 6, title: 'mock title2'}; 59 | KeyboardRegistry.registerKeyboard(mockComponent, mockGenerator, mockParams1); 60 | KeyboardRegistry.registerKeyboard(anotherMockComponent, anotherMockGenerator, mockParams2); 61 | 62 | let keyboards = KeyboardRegistry.getKeyboards([mockComponent]); 63 | expect(keyboards).toEqual([{id: mockComponent, ...mockParams1}]); 64 | 65 | keyboards = KeyboardRegistry.getKeyboards([anotherMockComponent]); 66 | expect(keyboards).toEqual([{id: anotherMockComponent, ...mockParams2}]); 67 | 68 | keyboards = KeyboardRegistry.getKeyboards([mockComponent, anotherMockComponent]); 69 | expect(keyboards).toEqual([{id: mockComponent, ...mockParams1}, {id: anotherMockComponent, ...mockParams2}]); 70 | 71 | keyboards = KeyboardRegistry.getKeyboards(['not_existing']); 72 | expect(keyboards).toEqual([]); 73 | 74 | keyboards = KeyboardRegistry.getKeyboards(['not_existing', mockComponent]); 75 | expect(keyboards).toEqual([{id: mockComponent, ...mockParams1}]); 76 | 77 | keyboards = KeyboardRegistry.getKeyboards([]); 78 | expect(keyboards).toEqual([]); 79 | 80 | keyboards = KeyboardRegistry.getKeyboards(); 81 | expect(keyboards).toEqual([]); 82 | }); 83 | 84 | it('should get specific keyboards by order', () => { 85 | KeyboardRegistry.registerKeyboard(mockComponent, mockGenerator); 86 | KeyboardRegistry.registerKeyboard(anotherMockComponent, anotherMockGenerator); 87 | 88 | let keyboards = KeyboardRegistry.getKeyboards([anotherMockComponent, mockComponent]); 89 | expect(keyboards).toEqual([{id: anotherMockComponent}, {id: mockComponent}]); 90 | }); 91 | }); 92 | 93 | describe('KeyboardRegistry - listeners', () => { 94 | const mockId = 'a_listener'; 95 | const mockCallback = () => {}; 96 | const mockArgs = {param1: '1', param2: '2'}; 97 | 98 | beforeEach(() => { 99 | KeyboardRegistry.eventEmitter = { 100 | listenOn: jest.fn(), 101 | emitEvent: jest.fn(), 102 | removeListeners: jest.fn(), 103 | }; 104 | }); 105 | 106 | it('should listen', () => { 107 | KeyboardRegistry.addListener(mockId, mockCallback); 108 | expect(KeyboardRegistry.eventEmitter.listenOn).toHaveBeenCalledTimes(1); 109 | expect(KeyboardRegistry.eventEmitter.listenOn.mock.calls[0][0]).toEqual(mockId); 110 | expect(KeyboardRegistry.eventEmitter.listenOn.mock.calls[0][1]).toEqual(mockCallback); 111 | }); 112 | 113 | it('should notify', () => { 114 | KeyboardRegistry.notifyListeners(mockId, mockArgs); 115 | expect(KeyboardRegistry.eventEmitter.emitEvent).toHaveBeenCalledTimes(1); 116 | expect(KeyboardRegistry.eventEmitter.emitEvent.mock.calls[0][0]).toEqual(mockId); 117 | expect(KeyboardRegistry.eventEmitter.emitEvent.mock.calls[0][1]).toEqual(mockArgs); 118 | }); 119 | 120 | it('should remove', () => { 121 | KeyboardRegistry.removeListeners(mockId); 122 | expect(KeyboardRegistry.eventEmitter.removeListeners.mock.calls[0][0]).toEqual(mockId); 123 | }); 124 | 125 | it('should notify when calling onItemSelected with dedicated onItemSelected id', () => { 126 | KeyboardRegistry.onItemSelected(mockId, mockArgs); 127 | expect(KeyboardRegistry.eventEmitter.emitEvent).toHaveBeenCalledTimes(1); 128 | expect(KeyboardRegistry.eventEmitter.emitEvent.mock.calls[0][0]).toEqual(`${mockId}.onItemSelected`); 129 | expect(KeyboardRegistry.eventEmitter.emitEvent.mock.calls[0][1]).toEqual(mockArgs); 130 | }); 131 | 132 | it('should notify when calling requestShowKeyboard with the keyboard id', () => { 133 | KeyboardRegistry.requestShowKeyboard(mockId, mockArgs); 134 | expect(KeyboardRegistry.eventEmitter.emitEvent).toHaveBeenCalledTimes(1); 135 | expect(KeyboardRegistry.eventEmitter.emitEvent.mock.calls[0][0]).toEqual('onRequestShowKeyboard'); 136 | expect(KeyboardRegistry.eventEmitter.emitEvent.mock.calls[0][1]).toEqual({keyboardId: mockId}); 137 | }); 138 | 139 | it('should notify when calling requestShowKeyboard with the keyboard id', () => { 140 | KeyboardRegistry.toggleExpandedKeyboard(mockId, mockArgs); 141 | expect(KeyboardRegistry.eventEmitter.emitEvent).toHaveBeenCalledTimes(1); 142 | expect(KeyboardRegistry.eventEmitter.emitEvent.mock.calls[0][0]).toEqual('onToggleExpandedKeyboard'); 143 | expect(KeyboardRegistry.eventEmitter.emitEvent.mock.calls[0][1]).toEqual({keyboardId: mockId}); 144 | }); 145 | }); 146 | -------------------------------------------------------------------------------- /src/KeyboardsRegistry.js: -------------------------------------------------------------------------------- 1 | import {AppRegistry} from 'react-native'; 2 | import _ from 'lodash'; 3 | import EventEmitterManager from './utils/EventEmitterManager'; 4 | 5 | /* 6 | * Tech debt: how to deal with multiple registries in the app? 7 | */ 8 | 9 | const getKeyboardsWithIDs = (keyboardIDs) => { 10 | return keyboardIDs.map((keyboardId) => { 11 | return { 12 | id: keyboardId, 13 | ...KeyboardRegistry.registeredKeyboards[keyboardId].params, 14 | }; 15 | }); 16 | }; 17 | 18 | export default class KeyboardRegistry { 19 | static registeredKeyboards = {}; 20 | static eventEmitter = new EventEmitterManager(); 21 | 22 | static registerKeyboard = (componentID, generator, params = {}) => { 23 | if (!_.isFunction(generator)) { 24 | console.error(`KeyboardRegistry.registerKeyboard: ${componentID} you must register a generator function`);//eslint-disable-line 25 | return; 26 | } 27 | KeyboardRegistry.registeredKeyboards[componentID] = {generator, params, componentID}; 28 | AppRegistry.registerComponent(componentID, generator); 29 | }; 30 | 31 | static getKeyboard = (componentID) => { 32 | const res = KeyboardRegistry.registeredKeyboards[componentID]; 33 | if (!res || !res.generator) { 34 | console.error(`KeyboardRegistry.getKeyboard: ${componentID} used but not yet registered`);//eslint-disable-line 35 | return undefined; 36 | } 37 | return res.generator(); 38 | }; 39 | 40 | static getKeyboards = (componentIDs = []) => { 41 | const validKeyboardIDs = _.intersection(componentIDs, Object.keys(KeyboardRegistry.registeredKeyboards)); 42 | return getKeyboardsWithIDs(validKeyboardIDs); 43 | }; 44 | 45 | static getAllKeyboards = () => { 46 | return getKeyboardsWithIDs(Object.keys(KeyboardRegistry.registeredKeyboards)); 47 | }; 48 | 49 | static addListener = (globalID, callback) => { 50 | KeyboardRegistry.eventEmitter.listenOn(globalID, callback); 51 | }; 52 | 53 | static notifyListeners = (globalID, args) => { 54 | KeyboardRegistry.eventEmitter.emitEvent(globalID, args); 55 | }; 56 | 57 | static removeListeners = (globalID) => { 58 | KeyboardRegistry.eventEmitter.removeListeners(globalID); 59 | }; 60 | 61 | static onItemSelected = (globalID, args) => { 62 | KeyboardRegistry.notifyListeners(`${globalID}.onItemSelected`, args); 63 | }; 64 | 65 | static requestShowKeyboard = (globalID) => { 66 | KeyboardRegistry.notifyListeners('onRequestShowKeyboard', {keyboardId: globalID}); 67 | }; 68 | 69 | static toggleExpandedKeyboard = (globalID) => { 70 | KeyboardRegistry.notifyListeners('onToggleExpandedKeyboard', {keyboardId: globalID}); 71 | }; 72 | } 73 | -------------------------------------------------------------------------------- /src/TextInputKeyboardManagerAndroid.js: -------------------------------------------------------------------------------- 1 | import {NativeModules} from 'react-native'; 2 | 3 | const CustomKeyboardInput = NativeModules.CustomKeyboardInput; 4 | 5 | export default class TextInputKeyboardManagerAndroid { 6 | static reset = () => CustomKeyboardInput.reset(); 7 | 8 | static dismissKeyboard = async () => { 9 | CustomKeyboardInput.clearFocusedView(); 10 | await TextInputKeyboardManagerAndroid.reset(); 11 | }; 12 | } 13 | -------------------------------------------------------------------------------- /src/TextInputKeyboardMangerIOS.js: -------------------------------------------------------------------------------- 1 | import ReactNative, {NativeModules, LayoutAnimation} from 'react-native'; 2 | 3 | const CustomInputController = NativeModules.CustomInputController; 4 | 5 | export default class TextInputKeyboardManagerIOS { 6 | 7 | static setInputComponent = (textInputRef, {component, initialProps, useSafeArea}) => { 8 | if (!textInputRef || !CustomInputController) { 9 | return; 10 | } 11 | const reactTag = findNodeHandle(textInputRef); 12 | if (reactTag) { 13 | CustomInputController.presentCustomInputComponent(reactTag, {component, initialProps, useSafeArea}); 14 | } 15 | }; 16 | 17 | static removeInputComponent = (textInputRef) => { 18 | if (!textInputRef || !CustomInputController) { 19 | return; 20 | } 21 | const reactTag = findNodeHandle(textInputRef); 22 | if (reactTag) { 23 | CustomInputController.resetInput(reactTag); 24 | } 25 | }; 26 | 27 | static dismissKeyboard = () => { 28 | CustomInputController.dismissKeyboard(); 29 | }; 30 | 31 | static toggleExpandKeyboard = (textInputRef, expand, performLayoutAnimation = false) => { 32 | if (textInputRef) { 33 | if (performLayoutAnimation) { 34 | LayoutAnimation.configureNext(springAnimation); 35 | } 36 | const reactTag = findNodeHandle(textInputRef); 37 | if (expand) { 38 | CustomInputController.expandFullScreenForInput(reactTag); 39 | } else { 40 | CustomInputController.resetSizeForInput(reactTag); 41 | } 42 | } 43 | }; 44 | } 45 | 46 | function findNodeHandle(ref) { 47 | return ReactNative.findNodeHandle(ref.current || ref); 48 | } 49 | 50 | const springAnimation = { 51 | duration: 400, 52 | create: { 53 | type: LayoutAnimation.Types.linear, 54 | property: LayoutAnimation.Properties.opacity, 55 | }, 56 | update: { 57 | type: LayoutAnimation.Types.spring, 58 | springDamping: 1.0, 59 | }, 60 | delete: { 61 | type: LayoutAnimation.Types.linear, 62 | property: LayoutAnimation.Properties.opacity, 63 | }, 64 | }; 65 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import TextInputKeyboardMangerIOS from './TextInputKeyboardMangerIOS'; 2 | import CustomKeyboardView from './CustomKeyboardView'; 3 | import KeyboardAccessoryView from './KeyboardAccessoryView'; 4 | import KeyboardRegistry from './KeyboardsRegistry'; 5 | import KeyboardUtils from './utils/KeyboardUtils'; 6 | 7 | export {TextInputKeyboardMangerIOS, CustomKeyboardView, KeyboardRegistry, KeyboardAccessoryView, KeyboardUtils}; 8 | -------------------------------------------------------------------------------- /src/utils/EventEmitterManager.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | 3 | export default class EventEmitterManager { 4 | constructor() { 5 | this.handlerCallbacks = {}; 6 | } 7 | 8 | listenOn(eventName, handlerCallback) { 9 | if (!this.handlerCallbacks[eventName]) { 10 | this.handlerCallbacks[eventName] = []; 11 | } 12 | if (_.indexOf(this.handlerCallbacks[eventName], handlerCallback) === -1) { 13 | this.handlerCallbacks[eventName].push(handlerCallback); 14 | } 15 | } 16 | 17 | emitEvent(eventName, params = {}) { 18 | if (this.handlerCallbacks[eventName]) { 19 | (this.handlerCallbacks[eventName]).forEach(callback => callback(params)); 20 | } 21 | } 22 | 23 | removeListeners(eventName) { 24 | delete this.handlerCallbacks[eventName]; 25 | } 26 | 27 | removeListener(eventName, listener) { 28 | const handlers = this.handlerCallbacks[eventName]; 29 | 30 | if (handlers) { 31 | (handlers).forEach((handler, index) => { 32 | if (handler === listener) { 33 | handlers.splice(index, 1); 34 | } 35 | }); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/utils/EventEmitterManager.spec.js: -------------------------------------------------------------------------------- 1 | const EventEmitterManager = require('./EventEmitterManager').default; 2 | 3 | let EventEmitter; 4 | 5 | describe('EventEmitterManager tests', () => { 6 | let mockCallback; 7 | let otherMockCallback; 8 | 9 | beforeEach(() => { 10 | mockCallback = jest.fn(); 11 | otherMockCallback = jest.fn(); 12 | EventEmitter = new EventEmitterManager(); 13 | }); 14 | 15 | it('should have a EventEmitterManager default instance', () => { 16 | expect(EventEmitterManager).toBeDefined(); 17 | expect(EventEmitterManager).not.toBeNull(); 18 | }); 19 | 20 | it('should register a listener and receive a callback', () => { 21 | EventEmitter.listenOn('MyEvent', mockCallback); 22 | 23 | expect(mockCallback).not.toHaveBeenCalled(); 24 | 25 | EventEmitter.emitEvent('MyEvent'); 26 | 27 | expect(mockCallback).toHaveBeenCalledTimes(1); 28 | expect(mockCallback).toHaveBeenCalledWith({}); 29 | }); 30 | 31 | it('should register a listener and receive a callback with params', () => { 32 | EventEmitter.listenOn('MyEvent', mockCallback); 33 | 34 | expect(mockCallback).not.toHaveBeenCalled(); 35 | 36 | EventEmitter.emitEvent('MyEvent', {param1: 'param1', param2: 'param2'}); 37 | 38 | expect(mockCallback).toHaveBeenCalledTimes(1); 39 | expect(mockCallback).toHaveBeenCalledWith({param1: 'param1', param2: 'param2'}); 40 | }); 41 | 42 | it('should receive only registered events callbacks', () => { 43 | EventEmitter.listenOn('MyEvent', mockCallback); 44 | 45 | expect(mockCallback).not.toHaveBeenCalled(); 46 | 47 | EventEmitter.emitEvent('MyEvent2', {param1: 'param1', param2: 'param2'}); 48 | 49 | expect(mockCallback).not.toHaveBeenCalled(); 50 | }); 51 | 52 | it('should receive multiple registered events with the same callback', () => { 53 | EventEmitter.listenOn('MyEvent', mockCallback); 54 | EventEmitter.listenOn('MyEvent2', mockCallback); 55 | 56 | expect(mockCallback).not.toHaveBeenCalled(); 57 | 58 | EventEmitter.emitEvent('MyEvent', {param1: 'param1', param2: 'param2'}); 59 | EventEmitter.emitEvent('MyEvent2', {param1: 'param1', param2: 'param2'}); 60 | 61 | expect(mockCallback).toHaveBeenCalledTimes(2); 62 | }); 63 | 64 | it('should receive multiple registered events with different callbacks', () => { 65 | EventEmitter.listenOn('MyEvent', mockCallback); 66 | EventEmitter.listenOn('MyEvent2', otherMockCallback); 67 | 68 | expect(mockCallback).not.toHaveBeenCalled(); 69 | expect(otherMockCallback).not.toHaveBeenCalled(); 70 | 71 | EventEmitter.emitEvent('MyEvent', {param1: 'param1', param2: 'param2'}); 72 | EventEmitter.emitEvent('MyEvent2', {param1: 'param1', param2: 'param2'}); 73 | 74 | expect(mockCallback).toHaveBeenCalledTimes(1); 75 | expect(otherMockCallback).toHaveBeenCalledTimes(1); 76 | }); 77 | 78 | it('should receive multiple callbacks for the same event', () => { 79 | EventEmitter.listenOn('MyEvent', mockCallback); 80 | EventEmitter.listenOn('MyEvent', otherMockCallback); 81 | 82 | expect(mockCallback).not.toHaveBeenCalled(); 83 | expect(otherMockCallback).not.toHaveBeenCalled(); 84 | 85 | EventEmitter.emitEvent('MyEvent', {param1: 'param1', param2: 'param2'}); 86 | 87 | expect(mockCallback).toHaveBeenCalledTimes(1); 88 | expect(otherMockCallback).toHaveBeenCalledTimes(1); 89 | }); 90 | 91 | it('should remove listeners', () => { 92 | EventEmitter.listenOn('MyEvent', mockCallback); 93 | EventEmitter.removeListeners('MyEvent'); 94 | 95 | EventEmitter.emitEvent('MyEvent', {param1: 'param1', param2: 'param2'}); 96 | 97 | expect(mockCallback).not.toHaveBeenCalled(); 98 | }); 99 | 100 | it('should do nothing when trying to emit an undefined or null even name', () => { 101 | EventEmitter.listenOn('MyEvent', mockCallback); 102 | 103 | EventEmitter.emitEvent(undefined, {param1: 'param1', param2: 'param2'}); 104 | EventEmitter.emitEvent(null, {param1: 'param1', param2: 'param2'}); 105 | 106 | expect(mockCallback).not.toHaveBeenCalled(); 107 | }); 108 | 109 | it('should ignore removing listeners without an event name', () => { 110 | EventEmitter.listenOn('MyEvent', mockCallback); 111 | EventEmitter.removeListeners(); 112 | 113 | EventEmitter.emitEvent('MyEvent', {param1: 'param1', param2: 'param2'}); 114 | 115 | expect(mockCallback).toHaveBeenCalled(); 116 | }); 117 | 118 | it('should not register the same callback for the same event', () => { 119 | EventEmitter.listenOn('MyEvent', mockCallback); 120 | EventEmitter.listenOn('MyEvent', mockCallback); 121 | expect(EventEmitter.handlerCallbacks.MyEvent.length).toBe(1); 122 | }); 123 | }); 124 | -------------------------------------------------------------------------------- /src/utils/KeyboardUtils.js: -------------------------------------------------------------------------------- 1 | import {Keyboard, Platform} from 'react-native'; 2 | import TextInputKeyboardMangerIOS from '../TextInputKeyboardMangerIOS'; 3 | import TextInputKeyboardManagerAndroid from '../TextInputKeyboardManagerAndroid'; 4 | 5 | const IsIOS = Platform.OS === 'ios'; 6 | 7 | export default class KeyboardUtils { 8 | static dismiss = () => { 9 | Keyboard.dismiss(); 10 | if (IsIOS) { 11 | TextInputKeyboardMangerIOS.dismissKeyboard(); 12 | } else { 13 | TextInputKeyboardManagerAndroid.dismissKeyboard(); 14 | } 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /wallaby.js: -------------------------------------------------------------------------------- 1 | /*eslint-disable*/ 2 | 'use strict'; 3 | process.env.BABEL_ENV = 'test'; 4 | const babelOptions = require('./package.json').babel.env.test; 5 | module.exports = function(wallaby) { 6 | return { 7 | env: { 8 | type: 'node', 9 | runner: 'node' 10 | }, 11 | 12 | testFramework: 'jest', 13 | 14 | files: [ 15 | 'package.json', 16 | 'src/**/*.js', 17 | '!src/**/*.spec.js' 18 | ], 19 | 20 | tests: [ 21 | 'src/**/*.spec.js' 22 | ], 23 | 24 | compilers: { 25 | '**/*.js': wallaby.compilers.babel(babelOptions) 26 | }, 27 | 28 | setup: function(w) { 29 | require('babel-polyfill'); 30 | w.testFramework.configure(require('./package.json').jest); 31 | } 32 | }; 33 | }; 34 | --------------------------------------------------------------------------------