├── .ghp └── wix.json ├── .gitignore ├── .npmignore ├── Interactable.podspec ├── LICENSE ├── PROPS.md ├── README.md ├── SUPPORT.md ├── UX-INSPIRATIONS.md ├── android ├── e2e ├── firstTest.spec.js ├── init.js └── mocha.opts ├── index.js ├── ios ├── lib ├── android │ ├── .gitignore │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── proguard-rules.pro │ └── src │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── wix │ │ │ │ └── interactable │ │ │ │ ├── Events.java │ │ │ │ ├── Interactable.java │ │ │ │ ├── InteractableArea.java │ │ │ │ ├── InteractablePoint.java │ │ │ │ ├── InteractableSpring.java │ │ │ │ ├── InteractableView.java │ │ │ │ ├── InteractableViewManager.java │ │ │ │ ├── RNConvert │ │ │ │ └── RNConvert.java │ │ │ │ └── physics │ │ │ │ ├── PhysicsAnchorBehavior.java │ │ │ │ ├── PhysicsAnimator.java │ │ │ │ ├── PhysicsArea.java │ │ │ │ ├── PhysicsBehavior.java │ │ │ │ ├── PhysicsBounceBehavior.java │ │ │ │ ├── PhysicsFrictionBehavior.java │ │ │ │ ├── PhysicsGravityWellBehavior.java │ │ │ │ ├── PhysicsObject.java │ │ │ │ └── PhysicsSpringBehavior.java │ │ └── res │ │ │ └── values │ │ │ ├── colors.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ └── test │ │ └── java │ │ └── com │ │ └── wix │ │ └── interactable │ │ └── ExampleUnitTest.java ├── index.js ├── ios │ ├── Interactable-tvOS │ │ └── Info.plist │ ├── Interactable-tvOSTests │ │ └── Info.plist │ ├── Interactable.xcodeproj │ │ ├── project.pbxproj │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ ├── Interactable-tvOS.xcscheme │ │ │ └── Interactable.xcscheme │ ├── Interactable.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ ├── Interactable │ │ ├── InteractableArea.h │ │ ├── InteractableArea.m │ │ ├── InteractablePoint.h │ │ ├── InteractablePoint.m │ │ ├── InteractableSpring.h │ │ ├── InteractableSpring.m │ │ ├── InteractableView.h │ │ ├── InteractableView.m │ │ ├── InteractableViewManager.h │ │ ├── InteractableViewManager.m │ │ ├── PhysicsAnchorBehavior.h │ │ ├── PhysicsAnchorBehavior.m │ │ ├── PhysicsAnimator.h │ │ ├── PhysicsAnimator.m │ │ ├── PhysicsArea.h │ │ ├── PhysicsArea.m │ │ ├── PhysicsBehavior.h │ │ ├── PhysicsBehavior.m │ │ ├── PhysicsBounceBehavior.h │ │ ├── PhysicsBounceBehavior.m │ │ ├── PhysicsFrictionBehavior.h │ │ ├── PhysicsFrictionBehavior.m │ │ ├── PhysicsGravityWellBehavior.h │ │ ├── PhysicsGravityWellBehavior.m │ │ ├── PhysicsObject.h │ │ ├── PhysicsObject.m │ │ ├── PhysicsSpringBehavior.h │ │ ├── PhysicsSpringBehavior.m │ │ ├── RCTConvert+Interactable.h │ │ └── RCTConvert+Interactable.m │ └── InteractableTests │ │ ├── Info.plist │ │ └── InteractableTests.m └── src │ └── InteractableView.js ├── logos ├── circle-notitle.png ├── circle-title.png ├── square-notitle.png └── square-title.png ├── package.json ├── playground ├── .babelrc-IGNORE ├── .buckconfig ├── .flowconfig ├── android │ ├── app │ │ ├── BUCK │ │ ├── build.gradle │ │ ├── build_defs.bzl │ │ ├── proguard-rules.pro │ │ └── src │ │ │ ├── androidTest │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── playground │ │ │ │ └── DetoxTest.java │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── java │ │ │ └── com │ │ │ │ └── playground │ │ │ │ ├── MainActivity.java │ │ │ │ └── MainApplication.java │ │ │ └── res │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ └── values │ │ │ ├── strings.xml │ │ │ └── styles.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── playground.keystore │ └── settings.gradle ├── app.json ├── assets │ ├── airport-photo.jpg │ ├── calendar-body.png │ ├── calendar-header.png │ ├── card-photo.jpg │ ├── chatheads-delete.png │ ├── chatheads-face1.jpg │ ├── chatheads-face2.jpg │ ├── icon-check.png │ ├── icon-clock.png │ ├── icon-menu.png │ ├── icon-trash.png │ ├── icon-up.png │ ├── map-bg.jpg │ └── tinder-photo.jpg ├── babel.config.js ├── index.js ├── ios │ ├── Playground-tvOS │ │ └── Info.plist │ ├── Playground-tvOSTests │ │ └── Info.plist │ ├── PlaygroundTests │ │ ├── Info.plist │ │ └── PlaygroundTests.m │ ├── Podfile │ ├── Podfile.lock │ ├── playground.xcodeproj │ │ ├── project.pbxproj │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ ├── playground-tvOS.xcscheme │ │ │ └── playground.xcscheme │ ├── playground.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── playground │ │ ├── AppDelegate.h │ │ ├── AppDelegate.m │ │ ├── Base.lproj │ │ └── LaunchScreen.xib │ │ ├── Images.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ │ ├── Info.plist │ │ └── main.m ├── metro.config.js └── src │ ├── app.js │ ├── examples │ ├── AlertAreas.js │ ├── ChangePosition.js │ ├── ChatHeads.js │ ├── CollapsingHeader.js │ ├── CollapsingHeaderWithScroll.js │ ├── HandleRelayout.js │ ├── HandleTouches.js │ ├── IconDrawer.js │ ├── MoreChatHeads.js │ ├── MoreDrawers.js │ ├── SideMenu.js │ ├── SnapTo.js │ ├── SwipeableCard.js │ ├── TouchesInside.js │ └── TouchesInsideStatic.js │ └── real-life-examples │ ├── CollapsibleCalendar.js │ ├── CollapsibleFilter.js │ ├── Documentation.js │ ├── MapPanel.js │ ├── NotifPanel.js │ ├── NowCard.js │ ├── RealChatHeads.js │ ├── RowActions1.js │ ├── RowActions2.js │ ├── TinderCard.js │ └── UxInspirations.js ├── rn-cli.config.js ├── scripts ├── ci.android.sh └── ci.ios.sh └── typings └── react-native-interactable.d.ts /.ghp/wix.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-interactable", 3 | "description": "Declarative API for handling fluid user interactions with views at 60 FPS in React Native", 4 | "title": "React Native Interactable", 5 | "github": "https://github.com/wix/react-native-interactable", 6 | "AndroidVideoUrl": "https://github.com/wix/react-native/blob/master/src/videos/react-interactable-android.mp4?raw=true", 7 | "IOSVideoUrl": "https://github.com/wix/react-native/blob/master/src/videos/react-iphone-interactable.mp4?raw=true", 8 | "IOSDemoAppLink": "https://itunes.apple.com/us/app/react-native-interactions/id1209875831?mt=8", 9 | "image": "", 10 | "poster": "https://github.com/wix/react-native/blob/master/src/img/react-native-interactable-poster.jpg?raw=true", 11 | "size" : "normal", 12 | "AndroidDemoAppLink": "https://play.google.com/store/apps/details?id=com.wix.interactions&hl=en" 13 | } 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Xcode 6 | # 7 | build/ 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | *.xccheckout 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | *.xcuserstate 23 | project.xcworkspace 24 | 25 | # Android/IJ 26 | # 27 | *.iml 28 | .idea 29 | .gradle 30 | local.properties 31 | android/.settings/* 32 | android/.project 33 | lib/android/.project 34 | playground/android/app/.project 35 | playground/android/.project 36 | 37 | # node.js 38 | # 39 | node_modules/ 40 | npm-debug.log 41 | 42 | # npm 43 | package-lock.json 44 | 45 | # yarn 46 | yarn.lock 47 | yarn-error.log 48 | 49 | # BUCK 50 | buck-out/ 51 | \.buckd/ 52 | android/app/libs 53 | *.keystore 54 | !debug.keystore 55 | 56 | # Bundle artifact 57 | *.jsbundle 58 | 59 | # CocoaPods 60 | playground/ios/Pods 61 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .ghp/ 2 | 3 | 4 | playground 5 | e2e 6 | logos 7 | ./index.ios.js 8 | ./index.android.js 9 | ./rn-cli.config.js 10 | 11 | ################# 12 | # from .gitignore: 13 | ################ 14 | 15 | 16 | ############ 17 | # Node 18 | ############ 19 | # Logs 20 | logs 21 | *.log 22 | npm-debug.log* 23 | 24 | # Runtime data 25 | pids 26 | *.pid 27 | *.seed 28 | 29 | # Directory for instrumented libs generated by jscoverage/JSCover 30 | lib-cov 31 | 32 | # Coverage directory used by tools like istanbul 33 | coverage 34 | 35 | # nyc test coverage 36 | .nyc_output 37 | 38 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 39 | .grunt 40 | 41 | # node-waf configuration 42 | .lock-wscript 43 | 44 | # Compiled binary addons (http://nodejs.org/api/addons.html) 45 | build/Release 46 | 47 | # Dependency directories 48 | node_modules 49 | jspm_packages 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional REPL history 55 | .node_repl_history 56 | 57 | ################ 58 | # JetBrains 59 | ################ 60 | .idea 61 | 62 | ## File-based project format: 63 | *.iws 64 | 65 | ## Plugin-specific files: 66 | 67 | # IntelliJ 68 | /out/ 69 | 70 | # mpeltonen/sbt-idea plugin 71 | .idea_modules/ 72 | 73 | # JIRA plugin 74 | atlassian-ide-plugin.xml 75 | 76 | # Crashlytics plugin (for Android Studio and IntelliJ) 77 | com_crashlytics_export_strings.xml 78 | crashlytics.properties 79 | crashlytics-build.properties 80 | fabric.properties 81 | 82 | 83 | ############ 84 | # iOS 85 | ############ 86 | # Xcode 87 | # 88 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 89 | 90 | #Cocoapods 91 | # *.podspec 92 | 93 | ## Build generated 94 | ios/build/ 95 | ios/DerivedData/ 96 | 97 | ## Various settings 98 | *.pbxuser 99 | !default.pbxuser 100 | *.mode1v3 101 | !default.mode1v3 102 | *.mode2v3 103 | !default.mode2v3 104 | *.perspectivev3 105 | !default.perspectivev3 106 | ios/xcuserdata/ 107 | 108 | ## Other 109 | *.moved-aside 110 | *.xcuserstate 111 | 112 | ## Obj-C/Swift specific 113 | *.hmap 114 | *.ipa 115 | *.dSYM.zip 116 | *.dSYM 117 | 118 | # CocoaPods 119 | # 120 | # We recommend against adding the Pods directory to your .gitignore. However 121 | # you should judge for yourself, the pros and cons are mentioned at: 122 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 123 | # 124 | ios/Pods/ 125 | 126 | # Carthage 127 | # 128 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 129 | # Carthage/Checkouts 130 | 131 | Carthage/Build 132 | 133 | # fastlane 134 | # 135 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 136 | # screenshots whenever they are needed. 137 | # For more information about the recommended setup visit: 138 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md 139 | 140 | fastlane/report.xml 141 | fastlane/screenshots 142 | 143 | 144 | ############ 145 | # Android 146 | ############ 147 | # Built application files 148 | *.apk 149 | *.ap_ 150 | 151 | # Files for the Dalvik VM 152 | *.dex 153 | 154 | # Java class files 155 | *.class 156 | 157 | # Generated files 158 | android/bin/ 159 | android/gen/ 160 | android/out/ 161 | android/app/build/ 162 | lib/android/gradle/ 163 | 164 | # Gradle files 165 | android/.gradle/ 166 | android/build/ 167 | 168 | # Local configuration file (sdk path, etc) 169 | local.properties 170 | 171 | # Proguard folder generated by Eclipse 172 | android/proguard/ 173 | 174 | # Log Files 175 | *.log 176 | 177 | # Android Studio Navigation editor temp files 178 | android/.navigation/ 179 | 180 | # Android Studio captures folder 181 | android/captures/ 182 | 183 | # Intellij 184 | *.iml 185 | 186 | # Keystore files 187 | *.jks 188 | 189 | ################## 190 | # React-Native 191 | ################## 192 | # OSX 193 | # 194 | .DS_Store 195 | 196 | # Xcode 197 | # 198 | build/ 199 | *.pbxuser 200 | !default.pbxuser 201 | *.mode1v3 202 | !default.mode1v3 203 | *.mode2v3 204 | !default.mode2v3 205 | *.perspectivev3 206 | !default.perspectivev3 207 | xcuserdata 208 | *.xccheckout 209 | *.moved-aside 210 | DerivedData 211 | *.hmap 212 | *.ipa 213 | *.xcuserstate 214 | project.xcworkspace 215 | 216 | # Android/IJ 217 | # 218 | .idea 219 | .gradle 220 | local.properties 221 | 222 | # node.js 223 | # 224 | node_modules/ 225 | npm-debug.log 226 | 227 | # BUCK 228 | buck-out/ 229 | \.buckd/ 230 | android/app/libs 231 | android/keystores/debug.keystore 232 | 233 | example 234 | real-life-example 235 | android/build 236 | -------------------------------------------------------------------------------- /Interactable.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 = "Interactable" 7 | s.version = package["version"] 8 | s.summary = package["description"] 9 | 10 | s.homepage = "https://github.com/wix/react-native-interactable" 11 | 12 | s.license = "MIT" 13 | s.authors = package["author"] 14 | s.platform = :ios, "7.0" 15 | 16 | s.source = { :git => "https://github.com/wix/react-native-interactable.git" } 17 | s.source_files = "lib/ios/Interactable/*.{h,m}" 18 | 19 | s.dependency 'React' 20 | end 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 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 | -------------------------------------------------------------------------------- /SUPPORT.md: -------------------------------------------------------------------------------- 1 | # Support 2 | 3 | ### Issues 4 | 5 | If you have any issues with the app please open an issue [here](https://github.com/wix/react-native-interactable/issues) 6 | 7 | ### Twitter 8 | 9 | Contact the developer via twitter at `@koltal` 10 | 11 | ### Contact 12 | 13 | http://www.wix.engineering 14 | 15 | or via email academy@wix.com 16 | -------------------------------------------------------------------------------- /UX-INSPIRATIONS.md: -------------------------------------------------------------------------------- 1 | # UX Inspirations 2 | 3 | The [showcase app](real-life-example) contains several demonstrations that draw inspiration from beautiful real life apps that we really like. The purpose of the showcase app is to learn from these use-cases and see how they could have been implemented with this library. 4 | 5 | These are the great apps that inspired the demonstrations: 6 | 7 | * **Row Actions (Google Style)** 8 |
[Inbox by Gmail](https://itunes.apple.com/us/app/inbox-by-gmail/id905060486?mt=8) app by Google Inc. 9 | 10 | * **Row Actions (Apple Style)** 11 |
The official iOS Mail app by Apple 12 | 13 | * **Google Now-Style Card** 14 |
[Google Now](https://itunes.apple.com/us/app/google-search-made-just-for-mobile/id284815942?mt=8) app by Google Inc. 15 | 16 | * **Tinder-Style Card** 17 |
[Flic](https://itunes.apple.com/us/app/flic-delete-manage-camera-roll-easily-delete-photos/id918263212?mt=8) app by Lifehack Labs 18 |
[Tinder](https://itunes.apple.com/us/app/tinder/id547702041?mt=8) app by Tinder Inc. 19 | 20 | * **Notification Panel** 21 |
The official iOS top notification panel by Apple 22 | 23 | * **Apple Maps-Style Panel** 24 |
The official iOS Maps app by Apple 25 | 26 | * **Collapsible Filter** 27 |
[Airbnb](https://itunes.apple.com/us/app/airbnb/id401626263?mt=8) app by Airbnb Inc. 28 | 29 | * **Collapsible Calendar (Any.do-Style)** 30 |
[Cal](https://itunes.apple.com/us/app/cal-shared-calendar-daily-organizer-weekly-planner/id648287824?mt=8) app by Any.DO 31 | 32 | * **Chat Heads** 33 |
[Messenger](https://play.google.com/store/apps/details?id=com.facebook.orca&hl=en) app by Facebook 34 | 35 | ### Disclaimer 36 | 37 | The showcase app is an independent learning project and has not been authorized, sponsored, or otherwise approved by the creators of the inspiring apps listed above. 38 | -------------------------------------------------------------------------------- /android: -------------------------------------------------------------------------------- 1 | lib/android -------------------------------------------------------------------------------- /e2e/firstTest.spec.js: -------------------------------------------------------------------------------- 1 | describe('Example', () => { 2 | beforeEach(async () => { 3 | await device.reloadReactNative(); 4 | }); 5 | 6 | it('should show Overview screen', async () => { 7 | await expect(element(by.id('Overview'))).toBeVisible(); 8 | }); 9 | 10 | it('Chat Heads is clickable', async () => { 11 | await element(by.text('Chat Heads')).tap(); 12 | }); 13 | 14 | }) 15 | -------------------------------------------------------------------------------- /e2e/init.js: -------------------------------------------------------------------------------- 1 | const detox = require('detox'); 2 | const config = require('../package.json').detox; 3 | const adapter = require('detox/runners/mocha/adapter'); 4 | 5 | before(async () => { 6 | await detox.init(config); 7 | }); 8 | 9 | beforeEach(async function () { 10 | await adapter.beforeEach(this); 11 | }); 12 | 13 | afterEach(async function () { 14 | await adapter.afterEach(this); 15 | }); 16 | 17 | after(async () => { 18 | await detox.cleanup(); 19 | }); 20 | -------------------------------------------------------------------------------- /e2e/mocha.opts: -------------------------------------------------------------------------------- 1 | --recursive --timeout 120000 --bail -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | require('./playground/index.js'); -------------------------------------------------------------------------------- /ios: -------------------------------------------------------------------------------- 1 | lib/ios -------------------------------------------------------------------------------- /lib/android/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /lib/android/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion rootProject.ext.has("compileSdkVersion") ? rootProject.ext.compileSdkVersion : 27 5 | buildToolsVersion rootProject.ext.has("buildToolsVersion") ? rootProject.ext.buildToolsVersion : "27.0.3" 6 | defaultConfig { 7 | minSdkVersion rootProject.ext.has("minSdkVersion") ? rootProject.ext.minSdkVersion : 16 8 | targetSdkVersion rootProject.ext.has("targetSdkVersion") ? rootProject.ext.targetSdkVersion : 26 9 | versionCode 1 10 | versionName "1.0" 11 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | 20 | testOptions { 21 | unitTests.all { t -> 22 | reports { 23 | html.enabled true 24 | } 25 | testLogging { 26 | events "passed", "skipped", "failed", "standardOut", "standardError" 27 | } 28 | afterSuite { desc, result -> 29 | if (!desc.parent) { // will match the outermost suite 30 | def output = " ${result.resultType} (${result.successfulTestCount} successes, ${result.failedTestCount} failures, ${result.skippedTestCount} skipped) " 31 | def repeatLength = output.length() 32 | println '\n' + ('-' * repeatLength) + '\n' + output + '\n' + ('-' * repeatLength) + '\n' 33 | 34 | println "see report at file://${t.reports.html.destination}/index.html" 35 | } 36 | } 37 | } 38 | } 39 | } 40 | 41 | dependencies { 42 | implementation fileTree(dir: 'libs', include: ['*.jar']) 43 | testImplementation 'junit:junit:4.12' 44 | 45 | // node_modules 46 | implementation 'com.facebook.react:react-native:+' 47 | } 48 | -------------------------------------------------------------------------------- /lib/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 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | -------------------------------------------------------------------------------- /lib/android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wix-incubator/react-native-interactable/5ea6b0f3d13b3a9169fab58e84c6736471f0b83a/lib/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /lib/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Mar 27 14:06:33 IDT 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.5-all.zip 7 | -------------------------------------------------------------------------------- /lib/android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /lib/android/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 /Users/rotemm/Library/Android/sdk/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 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /lib/android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /lib/android/src/main/java/com/wix/interactable/Events.java: -------------------------------------------------------------------------------- 1 | package com.wix.interactable; 2 | 3 | import com.facebook.react.bridge.Arguments; 4 | import com.facebook.react.bridge.WritableMap; 5 | import com.facebook.react.uimanager.PixelUtil; 6 | import com.facebook.react.uimanager.events.Event; 7 | import com.facebook.react.uimanager.events.RCTEventEmitter; 8 | 9 | /** 10 | * Created by rotemm on 14/02/2017. 11 | */ 12 | 13 | public class Events { 14 | 15 | public static class OnSnapEvent extends Event { 16 | 17 | WritableMap eventData; 18 | 19 | public OnSnapEvent(int viewTag, int indexOfSnapPoint, String snapPointId) { 20 | super(viewTag); 21 | eventData = Arguments.createMap(); 22 | eventData.putInt("index",indexOfSnapPoint); 23 | eventData.putString("id", snapPointId); 24 | } 25 | 26 | @Override 27 | public String getEventName() { 28 | return "onSnap"; 29 | } 30 | 31 | @Override 32 | public void dispatch(RCTEventEmitter rctEventEmitter) { 33 | rctEventEmitter.receiveEvent(getViewTag(), getEventName(), eventData); 34 | } 35 | } 36 | 37 | public static class OnSnapStartEvent extends Event { 38 | 39 | WritableMap eventData; 40 | 41 | public OnSnapStartEvent(int viewTag, int indexOfSnapPoint, String snapPointId) { 42 | super(viewTag); 43 | eventData = Arguments.createMap(); 44 | eventData.putInt("index",indexOfSnapPoint); 45 | eventData.putString("id", snapPointId); 46 | } 47 | 48 | @Override 49 | public String getEventName() { 50 | return "onSnapStart"; 51 | } 52 | 53 | @Override 54 | public void dispatch(RCTEventEmitter rctEventEmitter) { 55 | rctEventEmitter.receiveEvent(getViewTag(), getEventName(), eventData); 56 | } 57 | } 58 | 59 | public static class OnAnimatedEvent extends Event { 60 | 61 | WritableMap eventData; 62 | 63 | public OnAnimatedEvent(int viewTag, float x, float y) { 64 | super(viewTag); 65 | eventData = Arguments.createMap(); 66 | eventData.putDouble("x", PixelUtil.toDIPFromPixel(x)); 67 | eventData.putDouble("y", PixelUtil.toDIPFromPixel(y)); 68 | } 69 | 70 | @Override 71 | public String getEventName() { 72 | return "onAnimatedEvent"; 73 | } 74 | 75 | @Override 76 | public void dispatch(RCTEventEmitter rctEventEmitter) { 77 | rctEventEmitter.receiveEvent(getViewTag(), getEventName(), eventData); 78 | } 79 | } 80 | public static class OnAlertEvent extends Event { 81 | 82 | WritableMap eventData; 83 | 84 | public OnAlertEvent(int viewTag, String alertAreaId, String alertType) { 85 | super(viewTag); 86 | eventData = Arguments.createMap(); 87 | eventData.putString(alertAreaId, alertType); 88 | } 89 | 90 | @Override 91 | public String getEventName() { 92 | return "onAlert"; 93 | } 94 | 95 | @Override 96 | public void dispatch(RCTEventEmitter rctEventEmitter) { 97 | rctEventEmitter.receiveEvent(getViewTag(), getEventName(), eventData); 98 | } 99 | } 100 | 101 | public static class onDrag extends Event { 102 | 103 | WritableMap eventData; 104 | 105 | public onDrag(int viewTag, String state, float x, float y, String targetSnapPointId) { 106 | super(viewTag); 107 | eventData = Arguments.createMap(); 108 | eventData.putString("state", state); 109 | eventData.putDouble("x", PixelUtil.toDIPFromPixel(x)); 110 | eventData.putDouble("y", PixelUtil.toDIPFromPixel(y)); 111 | eventData.putString("targetSnapPointId", targetSnapPointId); 112 | } 113 | 114 | @Override 115 | public String getEventName() { 116 | return "onDrag"; 117 | } 118 | 119 | @Override 120 | public void dispatch(RCTEventEmitter rctEventEmitter) { 121 | rctEventEmitter.receiveEvent(getViewTag(), getEventName(), eventData); 122 | } 123 | } 124 | 125 | public static class onStop extends Event { 126 | 127 | WritableMap eventData; 128 | 129 | public onStop(int viewTag, float x, float y) { 130 | super(viewTag); 131 | eventData = Arguments.createMap(); 132 | eventData.putDouble("x", PixelUtil.toDIPFromPixel(x)); 133 | eventData.putDouble("y", PixelUtil.toDIPFromPixel(y)); 134 | } 135 | 136 | @Override 137 | public String getEventName() { return "onStop"; } 138 | 139 | @Override 140 | public void dispatch(RCTEventEmitter rctEventEmitter) { 141 | rctEventEmitter.receiveEvent(getViewTag(), getEventName(), eventData); 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /lib/android/src/main/java/com/wix/interactable/Interactable.java: -------------------------------------------------------------------------------- 1 | package com.wix.interactable; 2 | 3 | import com.facebook.react.ReactPackage; 4 | import com.facebook.react.bridge.JavaScriptModule; 5 | import com.facebook.react.bridge.NativeModule; 6 | import com.facebook.react.bridge.ReactApplicationContext; 7 | import com.facebook.react.uimanager.ViewManager; 8 | 9 | import java.util.Collections; 10 | import java.util.List; 11 | 12 | public class Interactable implements ReactPackage { 13 | 14 | @Override 15 | public List createNativeModules(ReactApplicationContext reactContext) { 16 | return Collections.emptyList(); 17 | } 18 | 19 | // Deprecated RN 0.47 20 | public List> createJSModules() { 21 | return Collections.emptyList(); 22 | } 23 | 24 | @Override 25 | public List createViewManagers(ReactApplicationContext reactContext) { 26 | return Collections.singletonList(new InteractableViewManager()); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lib/android/src/main/java/com/wix/interactable/InteractableArea.java: -------------------------------------------------------------------------------- 1 | package com.wix.interactable; 2 | import android.graphics.PointF; 3 | 4 | public class InteractableArea { 5 | private float top; 6 | private float left; 7 | private float bottom; 8 | private float right; 9 | private float bounce; 10 | private boolean haptics; 11 | 12 | public InteractableArea(float top, float left, float bottom, float right, float bounce, boolean haptics) { 13 | this.top = top; 14 | this.left = left; 15 | this.bottom = bottom; 16 | this.right = right; 17 | this.bounce = bounce; 18 | this.haptics = haptics; 19 | } 20 | 21 | public boolean pointInside(PointF point) { 22 | float cx = point.x; 23 | float cy = point.y; 24 | 25 | if (cx < this.left) return false; 26 | if (cx > this.right) return false; 27 | 28 | if (cy < this.top) return false; 29 | if (cy > this.bottom) return false; 30 | 31 | return true; 32 | } 33 | 34 | public boolean pointInsideWithOrigin(PointF point, PointF origin) { 35 | return this.pointInside(new PointF(point.x - origin.x, point.y - origin.y)); 36 | } 37 | 38 | 39 | public float getTop() { 40 | return top; 41 | } 42 | 43 | public float getLeft() { 44 | return left; 45 | } 46 | 47 | public float getBottom() { 48 | return bottom; 49 | } 50 | 51 | public float getRight() { 52 | return right; 53 | } 54 | 55 | public float getBounce() { 56 | return bounce; 57 | } 58 | 59 | public boolean isHaptic() { 60 | return haptics; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /lib/android/src/main/java/com/wix/interactable/InteractablePoint.java: -------------------------------------------------------------------------------- 1 | package com.wix.interactable; 2 | 3 | import android.graphics.Point; 4 | import android.graphics.PointF; 5 | import android.util.Log; 6 | 7 | import java.util.ArrayList; 8 | 9 | /** 10 | * Created by zachik on 12/02/2017. 11 | */ 12 | 13 | public class InteractablePoint { 14 | public String id; 15 | public float x; 16 | public float y; 17 | public float damping; 18 | public float tension; 19 | public float strength; 20 | public float falloff; 21 | public InteractableArea influenceArea; 22 | 23 | public InteractablePoint(String id, float x, float y, float damping, float tension, float strength, float falloff, InteractableArea influenceArea) { 24 | this.id = id; 25 | this.x = x; 26 | this.y = y; 27 | this.damping = damping; 28 | this.tension = tension; 29 | this.influenceArea = influenceArea; 30 | this.strength = strength; 31 | this.falloff = falloff; 32 | } 33 | 34 | public PointF positionWithOrigin() { 35 | PointF res = new PointF(0,0); 36 | if (this.x != Float.MAX_VALUE) res.x += this.x; 37 | if (this.y != Float.MAX_VALUE) res.y += this.y; 38 | return res; 39 | } 40 | 41 | public float distanceFromPoint(PointF point) { 42 | float dx = Float.MAX_VALUE; 43 | float dy = Float.MAX_VALUE; 44 | if (this.x != Float.MAX_VALUE) dx = point.x - this.x; 45 | if (this.y != Float.MAX_VALUE) dy = point.y - this.y; 46 | if (dx == Float.MAX_VALUE && dy == Float.MAX_VALUE) return Float.MAX_VALUE; 47 | if (dx == Float.MAX_VALUE) return Math.abs(dy); 48 | if (dy == Float.MAX_VALUE) return Math.abs(dx); 49 | return (float) Math.sqrt(dx*dx + dy*dy); 50 | } 51 | 52 | public static Point deltaBetweenPointAndOrigin(Point point,Point origin) { 53 | return new Point(point.x - origin.x, point.y - origin.y); 54 | } 55 | 56 | public static InteractablePoint findClosestPoint(ArrayList pointArray, 57 | PointF relativePoint) { 58 | // Log.d("InteractableView","findClosestPoint size = " + pointArray.size()); 59 | float minDist = Float.MAX_VALUE; 60 | InteractablePoint closestPoint = null; 61 | for (InteractablePoint point : pointArray) { 62 | // Log.d("InteractableView","findClosestPoint point x = " + point.x); 63 | 64 | float curDist = point.distanceFromPoint(relativePoint); 65 | if (curDist < minDist) { 66 | minDist = curDist; 67 | closestPoint = point; 68 | } 69 | } 70 | return closestPoint; 71 | 72 | } 73 | 74 | 75 | } 76 | -------------------------------------------------------------------------------- /lib/android/src/main/java/com/wix/interactable/InteractableSpring.java: -------------------------------------------------------------------------------- 1 | package com.wix.interactable; 2 | 3 | /** 4 | * Created by zachik on 12/02/2017. 5 | */ 6 | 7 | public class InteractableSpring { 8 | float tension; 9 | float damping; 10 | 11 | 12 | public InteractableSpring(float tension, float damping) { 13 | this.tension = tension; 14 | this.damping = damping; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/android/src/main/java/com/wix/interactable/RNConvert/RNConvert.java: -------------------------------------------------------------------------------- 1 | package com.wix.interactable.RNConvert; 2 | 3 | import android.graphics.PointF; 4 | 5 | import com.facebook.react.bridge.ReadableArray; 6 | import com.facebook.react.bridge.ReadableMap; 7 | import com.facebook.react.uimanager.PixelUtil; 8 | import com.wix.interactable.InteractableArea; 9 | import com.wix.interactable.InteractablePoint; 10 | import com.wix.interactable.InteractableSpring; 11 | 12 | import java.util.ArrayList; 13 | 14 | /** 15 | * Created by rotemm on 12/02/2017. 16 | */ 17 | 18 | public class RNConvert { 19 | 20 | public static InteractableArea interactableLimit(ReadableMap params) { 21 | float top = params.hasKey("top") ? PixelUtil.toPixelFromDIP((float) params.getDouble("top")) : -Float.MAX_VALUE; 22 | float left = params.hasKey("left") ? PixelUtil.toPixelFromDIP((float) params.getDouble("left")) : -Float.MAX_VALUE; 23 | float bottom = params.hasKey("bottom") ? PixelUtil.toPixelFromDIP((float) params.getDouble("bottom")) : Float.MAX_VALUE; 24 | float right = params.hasKey("right") ? PixelUtil.toPixelFromDIP((float) params.getDouble("right")) : Float.MAX_VALUE; 25 | float bounce = params.hasKey("bounce") ? (float) params.getDouble("bounce") : 0f; 26 | boolean haptics = params.hasKey("haptics") ? params.getBoolean("haptics") : false; 27 | 28 | return new InteractableArea(top, left, bottom, right, bounce, haptics); 29 | } 30 | 31 | public static InteractablePoint interactablePoint(ReadableMap params) { 32 | String id = params.hasKey("id") ? params.getString("id") : null; 33 | float x = params.hasKey("x") ? PixelUtil.toPixelFromDIP(params.getDouble("x")) : Float.MAX_VALUE; 34 | float y = params.hasKey("y") ? PixelUtil.toPixelFromDIP(params.getDouble("y")) : Float.MAX_VALUE; 35 | float damping = params.hasKey("damping") ? (float) params.getDouble("damping") : 0f; 36 | float tension = params.hasKey("tension") ? (float) params.getDouble("tension") : 300f; 37 | float strength = params.hasKey("strength") ? (float) params.getDouble("strength") : 400f; 38 | float falloff = params.hasKey("falloff") ? PixelUtil.toPixelFromDIP( params.getDouble("falloff")) : 40f; 39 | InteractableArea influenceArea = params.hasKey("influenceArea") ? interactableLimit(params.getMap("influenceArea")) : null; 40 | 41 | return new InteractablePoint(id, x, y, damping, tension, strength, falloff, influenceArea); 42 | } 43 | 44 | public static InteractableSpring interactableDrag(ReadableMap params) { 45 | float tension = params.hasKey("tension") ? (float) params.getDouble("tension") : Float.MAX_VALUE; 46 | float damping = params.hasKey("damping") ? (float) params.getDouble("damping") : 0f; 47 | 48 | return new InteractableSpring(tension, damping); 49 | } 50 | 51 | public static ArrayList interactablePoints(ReadableArray points) { 52 | ArrayList interactablePoints = new ArrayList(points.size()); 53 | for (int i = 0 ; i < points.size() ; i++) { 54 | ReadableMap rawSpring = points.getMap(i); 55 | interactablePoints.add(RNConvert.interactablePoint(rawSpring)); 56 | } 57 | 58 | return interactablePoints; 59 | } 60 | 61 | public static PointF pointF(ReadableMap params) { 62 | float x = PixelUtil.toPixelFromDIP(params.hasKey("x") ? params.getDouble("x") : 0); 63 | float y = PixelUtil.toPixelFromDIP(params.hasKey("y") ? params.getDouble("y") : 0); 64 | 65 | return new PointF(x, y); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /lib/android/src/main/java/com/wix/interactable/physics/PhysicsAnchorBehavior.java: -------------------------------------------------------------------------------- 1 | package com.wix.interactable.physics; 2 | 3 | import android.graphics.PointF; 4 | import android.util.Log; 5 | import android.view.View; 6 | 7 | /** 8 | * Created by zachik on 12/02/2017. 9 | */ 10 | 11 | public class PhysicsAnchorBehavior extends PhysicsBehavior { 12 | 13 | 14 | public PhysicsAnchorBehavior(View target,PointF anchorPoint) { 15 | super(target, anchorPoint); 16 | } 17 | 18 | @Override 19 | public void executeFrameWithDeltaTime(float deltaTime, PhysicsObject physicsObject) { 20 | if (deltaTime == 0.0) return; 21 | 22 | // Log.d("InteractableView"," PhysicsAnchorBehavior executeFrameWithDeltaTime: " + deltaTime); 23 | 24 | float dx = this.anchorPoint.x - this.target.getTranslationX(); 25 | float vx = dx / deltaTime; 26 | 27 | float dy = this.anchorPoint.y - this.target.getTranslationY(); 28 | float vy = dy / deltaTime; 29 | 30 | physicsObject.velocity = new PointF(vx, vy); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/android/src/main/java/com/wix/interactable/physics/PhysicsArea.java: -------------------------------------------------------------------------------- 1 | package com.wix.interactable.physics; 2 | 3 | import android.graphics.Point; 4 | import android.graphics.PointF; 5 | 6 | /** 7 | * Created by zachik on 12/02/2017. 8 | */ 9 | 10 | public class PhysicsArea { 11 | PointF minPoint; 12 | PointF maxPoint; 13 | 14 | public PhysicsArea(PointF minPoint, PointF maxPoint) { 15 | this.minPoint = minPoint; 16 | this.maxPoint = maxPoint; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/android/src/main/java/com/wix/interactable/physics/PhysicsBehavior.java: -------------------------------------------------------------------------------- 1 | package com.wix.interactable.physics; 2 | 3 | import android.content.Context; 4 | import android.graphics.PointF; 5 | import android.os.Vibrator; 6 | import android.view.View; 7 | 8 | 9 | /** 10 | * Created by rotemm on 09/02/2017. 11 | */ 12 | 13 | public abstract class PhysicsBehavior { 14 | private static int DURATION_BETWEEN_HAPTICS = 500; 15 | 16 | View target; 17 | long lastHapticsAction; 18 | 19 | public void setAnchorPoint(PointF anchorPoint) { 20 | this.anchorPoint = anchorPoint; 21 | } 22 | public void moveAnchorPoint(float dx,float dy) { 23 | this.anchorPoint.x += dx; 24 | this.anchorPoint.y += dy; 25 | } 26 | 27 | PointF anchorPoint; 28 | 29 | public void setInfluence(PhysicsArea influence) { 30 | this.influence = influence; 31 | } 32 | 33 | private PhysicsArea influence; 34 | 35 | boolean isTemp = false; 36 | boolean haptics = false; 37 | boolean lastIsWithinInfluenceInitialized = false; 38 | boolean lastIsWithinInfluence = false; 39 | 40 | int priority = 1; 41 | 42 | public PhysicsBehavior(View target, boolean haptics) { 43 | this.target = target; 44 | this.haptics = haptics; 45 | } 46 | public PhysicsBehavior(View target, PointF anchorPoint) { 47 | this(target, false); 48 | this.anchorPoint = anchorPoint; 49 | } 50 | 51 | 52 | public abstract void executeFrameWithDeltaTime(float timeInterval,PhysicsObject physicsObject); 53 | 54 | public boolean isWithinInfluence() { 55 | boolean res = true; 56 | if (influence == null) { 57 | return true; 58 | } 59 | if (target.getTranslationX() < influence.minPoint.x) res = false; 60 | if (target.getTranslationX() > influence.maxPoint.x) res = false; 61 | 62 | if (target.getTranslationY() < influence.minPoint.y) res = false; 63 | if (target.getTranslationY() > influence.maxPoint.y) res = false; 64 | 65 | if (lastIsWithinInfluenceInitialized) { 66 | if (res != lastIsWithinInfluence) { 67 | doHaptic(); 68 | } 69 | 70 | } 71 | lastIsWithinInfluenceInitialized = true; 72 | lastIsWithinInfluence = res; 73 | return res; 74 | } 75 | 76 | protected void doHaptic() { 77 | 78 | if ( haptics && (System.currentTimeMillis() - lastHapticsAction > DURATION_BETWEEN_HAPTICS)) { 79 | Vibrator v = (Vibrator) target.getContext().getSystemService(Context.VIBRATOR_SERVICE); 80 | v.vibrate(1); 81 | lastHapticsAction = System.currentTimeMillis(); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /lib/android/src/main/java/com/wix/interactable/physics/PhysicsBounceBehavior.java: -------------------------------------------------------------------------------- 1 | package com.wix.interactable.physics; 2 | 3 | import android.graphics.PointF; 4 | import android.util.Log; 5 | import android.view.View; 6 | 7 | /** 8 | * Created by zachik on 12/02/2017. 9 | */ 10 | 11 | public class PhysicsBounceBehavior extends PhysicsBehavior { 12 | 13 | private PointF minPoint; 14 | private PointF maxPoint; 15 | private float bounce = 0.5f; 16 | 17 | public PhysicsBounceBehavior(View target, PointF minPoint, PointF maxPoint,float bounce, boolean haptics) { 18 | super(target, haptics); 19 | this.minPoint = minPoint; 20 | this.maxPoint = maxPoint; 21 | this.bounce = bounce; 22 | this.priority = 3; 23 | } 24 | 25 | @Override 26 | public void executeFrameWithDeltaTime(float timeInterval, PhysicsObject physicsObject) { 27 | // Log.d("InteractableView"," PhysicsBounceBehavior executeFrameWithDeltaTime: " + timeInterval + " minX = " + minPoint.x); 28 | applyLimits(); 29 | if (this.minPoint.x == this.target.getTranslationX() && physicsObject.velocity.x < 0.0) 30 | { 31 | float vx = -physicsObject.velocity.x * this.bounce; 32 | float vy = physicsObject.velocity.y; 33 | physicsObject.velocity = new PointF(vx, vy); 34 | doHaptic(); 35 | } 36 | if (this.minPoint.y == this.target.getTranslationY() && physicsObject.velocity.y < 0.0) 37 | { 38 | float vx = physicsObject.velocity.x; 39 | float vy = -physicsObject.velocity.y * this.bounce; 40 | physicsObject.velocity = new PointF(vx, vy); 41 | doHaptic(); 42 | } 43 | if (this.maxPoint.x == this.target.getTranslationX() && physicsObject.velocity.x > 0.0) 44 | { 45 | float vx = -physicsObject.velocity.x * this.bounce; 46 | float vy = physicsObject.velocity.y; 47 | physicsObject.velocity = new PointF(vx, vy); 48 | doHaptic(); 49 | } 50 | if (this.maxPoint.y == this.target.getTranslationY() && physicsObject.velocity.y > 0.0) 51 | { 52 | float vx = physicsObject.velocity.x; 53 | float vy = -physicsObject.velocity.y * this.bounce; 54 | physicsObject.velocity = new PointF(vx, vy); 55 | doHaptic(); 56 | } 57 | } 58 | 59 | private void applyLimits() { 60 | if (this.minPoint.x > this.target.getTranslationX()) { 61 | this.target.setTranslationX(this.minPoint.x); 62 | } 63 | if (this.minPoint.y > this.target.getTranslationY()) { 64 | this.target.setTranslationY(this.minPoint.y); 65 | } 66 | if (this.maxPoint.x < this.target.getTranslationX()) { 67 | this.target.setTranslationX(this.maxPoint.x); 68 | } 69 | if (this.maxPoint.y < this.target.getTranslationY()) { 70 | this.target.setTranslationY(this.maxPoint.y); 71 | } 72 | } 73 | 74 | 75 | } 76 | -------------------------------------------------------------------------------- /lib/android/src/main/java/com/wix/interactable/physics/PhysicsFrictionBehavior.java: -------------------------------------------------------------------------------- 1 | package com.wix.interactable.physics; 2 | 3 | import android.graphics.PointF; 4 | import android.util.Log; 5 | import android.view.View; 6 | 7 | /** 8 | * Created by zachik on 12/02/2017. 9 | */ 10 | 11 | public class PhysicsFrictionBehavior extends PhysicsBehavior { 12 | 13 | private float friction; 14 | public PhysicsFrictionBehavior(View target, float friction) { 15 | super(target, false); 16 | this.friction = friction; 17 | this.priority = 2; 18 | } 19 | 20 | @Override 21 | public void executeFrameWithDeltaTime(float deltaTime, PhysicsObject physicsObject) { 22 | // Log.d("InteractableView"," PhysicsFrictionBehavior executeFrameWithDeltaTime friction = " + friction); 23 | if (!isWithinInfluence()) { 24 | return; 25 | } 26 | 27 | float vx = (float) (Math.pow(this.friction, 60.0 * deltaTime) * physicsObject.velocity.x); 28 | float vy = (float) (Math.pow(this.friction, 60.0 * deltaTime) * physicsObject.velocity.y); 29 | 30 | // Log.d("InteractableView"," vx = " + vx + " cur vx = " + physicsObject.velocity.x); 31 | 32 | physicsObject.velocity = new PointF(vx,vy); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/android/src/main/java/com/wix/interactable/physics/PhysicsGravityWellBehavior.java: -------------------------------------------------------------------------------- 1 | package com.wix.interactable.physics; 2 | 3 | import android.graphics.PointF; 4 | import android.view.View; 5 | 6 | /** 7 | * Created by zachik on 12/02/2017. 8 | */ 9 | 10 | public class PhysicsGravityWellBehavior extends PhysicsBehavior { 11 | 12 | public void setStrength(float strength) { 13 | this.strength = strength; 14 | } 15 | 16 | public void setFalloff(float falloff) { 17 | this.falloff = falloff; 18 | } 19 | 20 | private float strength = 400f; 21 | private float falloff = 40f; 22 | 23 | public PhysicsGravityWellBehavior(View target, PointF anchorPoint) { 24 | super(target, anchorPoint); 25 | } 26 | 27 | @Override 28 | public void executeFrameWithDeltaTime(float deltaTime, PhysicsObject physicsObject) { 29 | if (!isWithinInfluence()) { 30 | return; 31 | } 32 | 33 | float dx = this.target.getTranslationX() - this.anchorPoint.x; 34 | float dy = this.target.getTranslationY() - this.anchorPoint.y; 35 | double dr = Math.sqrt(dx*dx + dy*dy); 36 | if (dr == 0.0) return; 37 | 38 | double a = (-this.strength * dr * Math.exp(-0.5f * (dr * dr) / (this.falloff * this.falloff))) / physicsObject.mass; 39 | 40 | double ax = dx / dr * a; 41 | float vx = (float) (physicsObject.velocity.x + deltaTime * ax); 42 | 43 | 44 | double ay = dy / dr * a; 45 | float vy = (float) (physicsObject.velocity.y + deltaTime * ay); 46 | 47 | // Log.d("InteractableView"," PhysicsGravityWellBehavior executeFrameWithDeltaTime: " + deltaTime 48 | // + " vx = " + vx + " cur vx = " + physicsObject.velocity.x); 49 | 50 | physicsObject.velocity = new PointF(vx, vy); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /lib/android/src/main/java/com/wix/interactable/physics/PhysicsObject.java: -------------------------------------------------------------------------------- 1 | package com.wix.interactable.physics; 2 | 3 | import android.graphics.Point; 4 | import android.graphics.PointF; 5 | 6 | /** 7 | * Created by rotemm on 09/02/2017. 8 | */ 9 | 10 | public class PhysicsObject { 11 | 12 | PointF velocity; 13 | float mass; 14 | 15 | public PhysicsObject() { 16 | this.velocity = new PointF(0,0); 17 | this.mass = 1.0f; 18 | } 19 | 20 | public PhysicsObject(PointF velocity, float mass) { 21 | this.velocity = velocity; 22 | this.mass = mass; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/android/src/main/java/com/wix/interactable/physics/PhysicsSpringBehavior.java: -------------------------------------------------------------------------------- 1 | package com.wix.interactable.physics; 2 | 3 | import android.graphics.Point; 4 | import android.graphics.PointF; 5 | import android.util.Log; 6 | import android.view.View; 7 | 8 | /** 9 | * Created by zachik on 12/02/2017. 10 | */ 11 | 12 | public class PhysicsSpringBehavior extends PhysicsBehavior { 13 | public float tension = 300f; 14 | public PhysicsSpringBehavior(View target, PointF anchorPoint) { 15 | super(target, anchorPoint); 16 | } 17 | 18 | @Override 19 | public void executeFrameWithDeltaTime(float deltaTime, PhysicsObject physicsObject) { 20 | if (!isWithinInfluence()) { 21 | return; 22 | } 23 | 24 | float dx = this.target.getTranslationX() - this.anchorPoint.x; 25 | float ax = (-this.tension * dx) / physicsObject.mass; 26 | float vx = physicsObject.velocity.x + deltaTime * ax; 27 | 28 | float dy = this.target.getTranslationY() - this.anchorPoint.y; 29 | float ay = (-this.tension * dy) / physicsObject.mass; 30 | float vy = physicsObject.velocity.y + deltaTime * ay; 31 | 32 | // Log.d("InteractableView"," PhysicsSpringBehavior executeFrameWithDeltaTime cur Vx = " + physicsObject.velocity.x + " vx = " + vx); 33 | 34 | physicsObject.velocity = new PointF(vx, vy); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/android/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /lib/android/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | react-native-interactable 3 | 4 | -------------------------------------------------------------------------------- /lib/android/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /lib/android/src/test/java/com/wix/interactable/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.wix.interactable; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() throws Exception { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | import InteractableView from './src/InteractableView'; 2 | 3 | module.exports = { 4 | View: InteractableView 5 | }; 6 | -------------------------------------------------------------------------------- /lib/ios/Interactable-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 | -------------------------------------------------------------------------------- /lib/ios/Interactable-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 | -------------------------------------------------------------------------------- /lib/ios/Interactable.xcodeproj/xcshareddata/xcschemes/Interactable.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 46 | 47 | 53 | 54 | 55 | 56 | 57 | 58 | 68 | 70 | 76 | 77 | 78 | 79 | 80 | 81 | 87 | 89 | 95 | 96 | 97 | 98 | 100 | 101 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /lib/ios/Interactable.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /lib/ios/Interactable.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /lib/ios/Interactable/InteractableArea.h: -------------------------------------------------------------------------------- 1 | // 2 | // InteractableArea.h 3 | // Interactable 4 | // 5 | // Created by Tal Kol on 2/6/17. 6 | // Copyright © 2017 Wix. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface InteractableArea : NSObject 13 | 14 | @property (nonatomic, assign) CGFloat top; 15 | @property (nonatomic, assign) CGFloat left; 16 | @property (nonatomic, assign) CGFloat bottom; 17 | @property (nonatomic, assign) CGFloat right; 18 | @property (nonatomic, assign) CGFloat bounce; 19 | @property (nonatomic, assign) BOOL haptics; 20 | 21 | - (BOOL)pointInside:(CGPoint)point withOrigin:(CGPoint)origin; 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /lib/ios/Interactable/InteractableArea.m: -------------------------------------------------------------------------------- 1 | // 2 | // InteractableArea.m 3 | // Interactable 4 | // 5 | // Created by Tal Kol on 2/6/17. 6 | // Copyright © 2017 Wix. All rights reserved. 7 | // 8 | 9 | #import "InteractableArea.h" 10 | 11 | @implementation InteractableArea 12 | 13 | - (id)copyWithZone:(__unused NSZone *)zone 14 | { 15 | return self; 16 | } 17 | 18 | - (BOOL)pointInside:(CGPoint)point withOrigin:(CGPoint)origin 19 | { 20 | CGFloat cx = point.x - origin.x; 21 | CGFloat cy = point.y - origin.y; 22 | 23 | if (cx < self.left) return NO; 24 | if (cx > self.right) return NO; 25 | 26 | if (cy < self.top) return NO; 27 | if (cy > self.bottom) return NO; 28 | 29 | return YES; 30 | } 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /lib/ios/Interactable/InteractablePoint.h: -------------------------------------------------------------------------------- 1 | // 2 | // InteractablePoint.h 3 | // Interactable 4 | // 5 | // Created by Tal Kol on 1/27/17. 6 | // Copyright © 2017 Wix. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "InteractableArea.h" 12 | 13 | @interface InteractablePoint : NSObject 14 | 15 | @property (nonatomic, assign) CGFloat x; 16 | @property (nonatomic, assign) CGFloat y; 17 | @property (nonatomic, assign) CGFloat damping; 18 | @property (nonatomic, assign) CGFloat tension; 19 | @property (nonatomic, assign) CGFloat strength; 20 | @property (nonatomic, assign) CGFloat falloff; 21 | @property (nonatomic, copy) id id; 22 | @property (nonatomic, copy) InteractableArea *influenceArea; 23 | @property (nonatomic, assign) BOOL haptics; 24 | 25 | - (CGPoint)positionWithOrigin:(CGPoint)origin; 26 | - (CGFloat)distanceFromPoint:(CGPoint)point withOrigin:(CGPoint)origin; 27 | + (CGPoint)deltaBetweenPoint:(CGPoint)point andOrigin:(CGPoint)origin; 28 | + (InteractablePoint*)findClosestPoint:(NSArray*)points toPoint:(CGPoint)relativeToPoint withOrigin:(CGPoint)origin; 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /lib/ios/Interactable/InteractablePoint.m: -------------------------------------------------------------------------------- 1 | // 2 | // InteractablePoint.m 3 | // Interactable 4 | // 5 | // Created by Tal Kol on 1/27/17. 6 | // Copyright © 2017 Wix. All rights reserved. 7 | // 8 | 9 | #import "InteractablePoint.h" 10 | 11 | @implementation InteractablePoint 12 | 13 | - (id)copyWithZone:(__unused NSZone *)zone 14 | { 15 | return self; 16 | } 17 | 18 | - (CGPoint)positionWithOrigin:(CGPoint)origin 19 | { 20 | CGPoint res = origin; 21 | if (self.x != CGFLOAT_MAX) res.x += self.x; 22 | if (self.y != CGFLOAT_MAX) res.y += self.y; 23 | return res; 24 | } 25 | 26 | - (CGFloat)distanceFromPoint:(CGPoint)point withOrigin:(CGPoint)origin 27 | { 28 | CGFloat dx = CGFLOAT_MAX; 29 | CGFloat dy = CGFLOAT_MAX; 30 | if (self.x != CGFLOAT_MAX) dx = point.x - (origin.x + self.x); 31 | if (self.y != CGFLOAT_MAX) dy = point.y - (origin.y + self.y); 32 | if (dx == CGFLOAT_MAX && dy == CGFLOAT_MAX) return CGFLOAT_MAX; 33 | if (dx == CGFLOAT_MAX) return ABS(dy); 34 | if (dy == CGFLOAT_MAX) return ABS(dx); 35 | return sqrt(dx*dx + dy*dy); 36 | } 37 | 38 | + (CGPoint)deltaBetweenPoint:(CGPoint)point andOrigin:(CGPoint)origin 39 | { 40 | return CGPointMake(point.x - origin.x, point.y - origin.y); 41 | } 42 | 43 | + (InteractablePoint*)findClosestPoint:(NSArray*)points toPoint:(CGPoint)relativeToPoint withOrigin:(CGPoint)origin 44 | { 45 | InteractablePoint *res = nil; 46 | CGFloat minDistance = CGFLOAT_MAX; 47 | for (InteractablePoint *point in points) 48 | { 49 | CGFloat distance = [point distanceFromPoint:relativeToPoint withOrigin:origin]; 50 | if (distance < minDistance) 51 | { 52 | minDistance = distance; 53 | res = point; 54 | } 55 | } 56 | return res; 57 | } 58 | 59 | @end 60 | -------------------------------------------------------------------------------- /lib/ios/Interactable/InteractableSpring.h: -------------------------------------------------------------------------------- 1 | // 2 | // InteractableSpring.h 3 | // Interactable 4 | // 5 | // Created by Tal Kol on 2/9/17. 6 | // Copyright © 2017 Wix. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface InteractableSpring : NSObject 13 | 14 | @property (nonatomic, assign) CGFloat tension; 15 | @property (nonatomic, assign) CGFloat damping; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /lib/ios/Interactable/InteractableSpring.m: -------------------------------------------------------------------------------- 1 | // 2 | // InteractableSpring.m 3 | // Interactable 4 | // 5 | // Created by Tal Kol on 2/9/17. 6 | // Copyright © 2017 Wix. All rights reserved. 7 | // 8 | 9 | #import "InteractableSpring.h" 10 | 11 | @implementation InteractableSpring 12 | 13 | - (id)copyWithZone:(__unused NSZone *)zone 14 | { 15 | return self; 16 | } 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /lib/ios/Interactable/InteractableView.h: -------------------------------------------------------------------------------- 1 | // 2 | // InteractableView.h 3 | // Interactable 4 | // 5 | // Created by Tal Kol on 1/27/17. 6 | // Copyright © 2017 Wix. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import 12 | #import "InteractablePoint.h" 13 | #import "InteractableArea.h" 14 | #import "InteractableSpring.h" 15 | #import "PhysicsAnimator.h" 16 | 17 | @interface InteractableView : UIView 18 | 19 | @property (nonatomic, assign) BOOL verticalOnly; 20 | @property (nonatomic, assign) BOOL horizontalOnly; 21 | @property (nonatomic, assign) BOOL dragEnabled; 22 | @property (nonatomic, strong) RCTBridge *bridge; 23 | @property (nonatomic, copy) NSArray *snapPoints; 24 | @property (nonatomic, copy) NSArray *springPoints; 25 | @property (nonatomic, copy) NSArray *gravityPoints; 26 | @property (nonatomic, copy) NSArray *frictionAreas; 27 | @property (nonatomic, copy) NSArray *alertAreas; 28 | @property (nonatomic, copy) InteractableArea *boundaries; 29 | @property (nonatomic, copy) InteractableSpring *dragWithSpring; 30 | @property (nonatomic, assign) CGFloat dragToss; 31 | @property (nonatomic, copy) RCTDirectEventBlock onSnap; 32 | @property (nonatomic, copy) RCTDirectEventBlock onSnapStart; 33 | @property (nonatomic, copy) RCTDirectEventBlock onStop; 34 | @property (nonatomic, copy) RCTDirectEventBlock onAlert; 35 | @property (nonatomic, copy) RCTDirectEventBlock onDrag; 36 | @property (nonatomic, assign) CGPoint initialPosition; 37 | @property (nonatomic, copy) RCTDirectEventBlock onAnimatedEvent; 38 | @property (nonatomic, assign) BOOL reportOnAnimatedEvents; 39 | 40 | - (instancetype)initWithBridge:(RCTBridge*)bridge; 41 | - (void)setVelocity:(NSDictionary*)params; 42 | - (void)snapTo:(NSDictionary*)params; 43 | - (void)changePosition:(NSDictionary*)params; 44 | - (void)bringToFront:(NSDictionary*)params; 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /lib/ios/Interactable/InteractableViewManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // InteractableViewManager.h 3 | // InteractableViewManager 4 | // 5 | // Created by Tal Kol on 1/27/17. 6 | // Copyright © 2017 Wix. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface InteractableViewManager : RCTViewManager 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /lib/ios/Interactable/InteractableViewManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // InteractableViewManager.m 3 | // InteractableViewManager 4 | // 5 | // Created by Tal Kol on 1/27/17. 6 | // Copyright © 2017 Wix. All rights reserved. 7 | // 8 | 9 | #import "InteractableViewManager.h" 10 | #import "InteractableView.h" 11 | #import "InteractablePoint.h" 12 | #import "InteractableArea.h" 13 | #import "InteractableSpring.h" 14 | #import "RCTConvert+Interactable.h" 15 | #import 16 | #import 17 | 18 | @implementation InteractableViewManager 19 | 20 | RCT_EXPORT_MODULE() 21 | 22 | - (UIView *)view 23 | { 24 | return [[InteractableView alloc] initWithBridge:self.bridge]; 25 | } 26 | 27 | RCT_EXPORT_VIEW_PROPERTY(verticalOnly, BOOL) 28 | RCT_EXPORT_VIEW_PROPERTY(horizontalOnly, BOOL) 29 | RCT_EXPORT_VIEW_PROPERTY(dragEnabled, BOOL) 30 | RCT_EXPORT_VIEW_PROPERTY(snapPoints, NSArray) 31 | RCT_EXPORT_VIEW_PROPERTY(springPoints, NSArray) 32 | RCT_EXPORT_VIEW_PROPERTY(gravityPoints, NSArray) 33 | RCT_EXPORT_VIEW_PROPERTY(frictionAreas, NSArray) 34 | RCT_EXPORT_VIEW_PROPERTY(alertAreas, NSArray) 35 | RCT_EXPORT_VIEW_PROPERTY(boundaries, InteractableArea) 36 | RCT_EXPORT_VIEW_PROPERTY(dragWithSpring, InteractableSpring) 37 | RCT_EXPORT_VIEW_PROPERTY(dragToss, CGFloat) 38 | RCT_EXPORT_VIEW_PROPERTY(onSnap, RCTDirectEventBlock) 39 | RCT_EXPORT_VIEW_PROPERTY(onSnapStart, RCTDirectEventBlock) 40 | RCT_EXPORT_VIEW_PROPERTY(onStop, RCTDirectEventBlock) 41 | RCT_EXPORT_VIEW_PROPERTY(onAlert, RCTDirectEventBlock) 42 | RCT_EXPORT_VIEW_PROPERTY(onDrag, RCTDirectEventBlock) 43 | RCT_EXPORT_VIEW_PROPERTY(initialPosition, CGPoint) 44 | RCT_EXPORT_VIEW_PROPERTY(onAnimatedEvent, RCTDirectEventBlock) 45 | RCT_EXPORT_VIEW_PROPERTY(reportOnAnimatedEvents, BOOL) 46 | 47 | RCT_EXPORT_METHOD(setVelocity:(nonnull NSNumber *)reactTag 48 | params:(NSDictionary*)params) 49 | { 50 | [self.bridge.uiManager addUIBlock: 51 | ^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) 52 | { 53 | UIView *view = viewRegistry[reactTag]; 54 | if ([view isKindOfClass:[InteractableView class]]) 55 | { 56 | [(InteractableView*)view setVelocity:params]; 57 | } 58 | else 59 | { 60 | RCTLogError(@"tried to setVelocity: on non-InteractableView view %@ " 61 | "with tag #%@", view, reactTag); 62 | } 63 | }]; 64 | } 65 | 66 | RCT_EXPORT_METHOD(bringToFront:(nonnull NSNumber *)reactTag 67 | params:(NSDictionary*)params) 68 | { 69 | 70 | } 71 | 72 | RCT_EXPORT_METHOD(snapTo:(nonnull NSNumber *)reactTag 73 | params:(NSDictionary*)params) 74 | { 75 | [self.bridge.uiManager addUIBlock: 76 | ^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) 77 | { 78 | UIView *view = viewRegistry[reactTag]; 79 | if ([view isKindOfClass:[InteractableView class]]) 80 | { 81 | [(InteractableView*)view snapTo:params]; 82 | } 83 | else 84 | { 85 | RCTLogError(@"tried to snapTo: on non-InteractableView view %@ " 86 | "with tag #%@", view, reactTag); 87 | } 88 | }]; 89 | } 90 | 91 | RCT_EXPORT_METHOD(changePosition:(nonnull NSNumber *)reactTag 92 | params:(NSDictionary*)params) 93 | { 94 | [self.bridge.uiManager addUIBlock: 95 | ^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) 96 | { 97 | UIView *view = viewRegistry[reactTag]; 98 | if ([view isKindOfClass:[InteractableView class]]) 99 | { 100 | [(InteractableView*)view changePosition:params]; 101 | } 102 | else 103 | { 104 | RCTLogError(@"tried to changePosition: on non-InteractableView view %@ " 105 | "with tag #%@", view, reactTag); 106 | } 107 | }]; 108 | } 109 | 110 | @end 111 | -------------------------------------------------------------------------------- /lib/ios/Interactable/PhysicsAnchorBehavior.h: -------------------------------------------------------------------------------- 1 | // 2 | // PhysicsAnchorBehavior.h 3 | // Interactable 4 | // 5 | // Created by Tal Kol on 2/9/17. 6 | // Copyright © 2017 Wix. All rights reserved. 7 | // 8 | 9 | #import "PhysicsBehavior.h" 10 | 11 | @interface PhysicsAnchorBehavior : PhysicsBehavior 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /lib/ios/Interactable/PhysicsAnchorBehavior.m: -------------------------------------------------------------------------------- 1 | // 2 | // PhysicsAnchorBehavior.m 3 | // Interactable 4 | // 5 | // Created by Tal Kol on 2/9/17. 6 | // Copyright © 2017 Wix. All rights reserved. 7 | // 8 | 9 | #import "PhysicsAnchorBehavior.h" 10 | 11 | @implementation PhysicsAnchorBehavior 12 | 13 | - (instancetype)initWithTarget:(UIView*)target anchorPoint:(CGPoint)anchorPoint 14 | { 15 | if ((self = [super initWithTarget:target anchorPoint:anchorPoint])) 16 | { 17 | self.priority = 3; 18 | } 19 | return self; 20 | } 21 | 22 | - (void)executeFrameWithDeltaTime:(CFTimeInterval)deltaTime onObject:(PhysicsObject*)object 23 | { 24 | if (deltaTime == 0.0) return; 25 | 26 | CGFloat dx = self.anchorPoint.x - self.target.center.x; 27 | CGFloat vx = dx / deltaTime; 28 | 29 | CGFloat dy = self.anchorPoint.y - self.target.center.y; 30 | CGFloat vy = dy / deltaTime; 31 | 32 | object.velocity = CGPointMake(vx, vy); 33 | } 34 | 35 | @end 36 | -------------------------------------------------------------------------------- /lib/ios/Interactable/PhysicsAnimator.h: -------------------------------------------------------------------------------- 1 | // 2 | // PhysicsAnimator.h 3 | // Interactable 4 | // 5 | // Created by Tal Kol on 2/4/17. 6 | // Copyright © 2017 Wix. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "PhysicsSpringBehavior.h" 12 | #import "PhysicsBounceBehavior.h" 13 | #import "PhysicsFrictionBehavior.h" 14 | #import "PhysicsAnchorBehavior.h" 15 | #import "PhysicsGravityWellBehavior.h" 16 | 17 | @protocol PhysicsAnimatorDelegate; 18 | 19 | @interface PhysicsAnimator : NSObject 20 | 21 | @property (nonatomic, assign) id delegate; 22 | 23 | - (void)addBehavior:(PhysicsBehavior*)behavior; 24 | - (void)addTempBehavior:(PhysicsBehavior*)behavior; 25 | - (void)removeAllBehaviors; 26 | - (void)removeTempBehaviors; 27 | - (void)ensureRunning; 28 | - (void)stopRunning; 29 | - (void)setTarget:(UIView*)target mass:(CGFloat)mass; 30 | - (void)setTarget:(UIView*)target velocity:(CGPoint)velocity; 31 | - (CGPoint)getTargetVelocity:(UIView*)target; 32 | 33 | @end 34 | 35 | @protocol PhysicsAnimatorDelegate 36 | 37 | - (void)physicsAnimatorDidPause:(PhysicsAnimator *)animator; 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /lib/ios/Interactable/PhysicsArea.h: -------------------------------------------------------------------------------- 1 | // 2 | // PhysicsArea.h 3 | // Interactable 4 | // 5 | // Created by Tal Kol on 2/11/17. 6 | // Copyright © 2017 Wix. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface PhysicsArea : NSObject 13 | 14 | @property (nonatomic, assign) CGPoint minPoint; 15 | @property (nonatomic, assign) CGPoint maxPoint; 16 | 17 | - (instancetype)initWithMinPoint:(CGPoint)minPoint maxPoint:(CGPoint)maxPoint; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /lib/ios/Interactable/PhysicsArea.m: -------------------------------------------------------------------------------- 1 | // 2 | // PhysicsArea.m 3 | // Interactable 4 | // 5 | // Created by Tal Kol on 2/11/17. 6 | // Copyright © 2017 Wix. All rights reserved. 7 | // 8 | 9 | #import "PhysicsArea.h" 10 | 11 | @implementation PhysicsArea 12 | 13 | - (instancetype)init 14 | { 15 | if ((self = [super init])) 16 | { 17 | self.minPoint = CGPointMake(-CGFLOAT_MAX, -CGFLOAT_MAX); 18 | self.maxPoint = CGPointMake(CGFLOAT_MAX, CGFLOAT_MAX); 19 | } 20 | return self; 21 | } 22 | 23 | - (instancetype)initWithMinPoint:(CGPoint)minPoint maxPoint:(CGPoint)maxPoint 24 | { 25 | if ((self = [super init])) 26 | { 27 | self.minPoint = minPoint; 28 | self.maxPoint = maxPoint; 29 | } 30 | return self; 31 | } 32 | 33 | - (id)copyWithZone:(__unused NSZone *)zone 34 | { 35 | return self; 36 | } 37 | 38 | @end 39 | -------------------------------------------------------------------------------- /lib/ios/Interactable/PhysicsBehavior.h: -------------------------------------------------------------------------------- 1 | // 2 | // PhysicsBehavior.h 3 | // Interactable 4 | // 5 | // Created by Tal Kol on 2/4/17. 6 | // Copyright © 2017 Wix. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "PhysicsObject.h" 12 | #import "PhysicsArea.h" 13 | 14 | @interface PhysicsBehavior : NSObject 15 | 16 | @property (nonatomic) UIView *target; 17 | @property (nonatomic, assign) int priority; // priority 1 is handled first, 2 handled 2nd 18 | @property (nonatomic, assign) BOOL temp; 19 | @property (nonatomic, assign) CGPoint anchorPoint; 20 | @property (nonatomic, copy) PhysicsArea *influence; 21 | @property (nonatomic, assign) BOOL haptics; 22 | 23 | - (instancetype)initWithTarget:(UIView*)target; 24 | - (instancetype)initWithTarget:(UIView*)target anchorPoint:(CGPoint)anchorPoint; 25 | - (void)executeFrameWithDeltaTime:(CFTimeInterval)deltaTime onObject:(PhysicsObject*)object; 26 | - (NSUInteger)findSortIndexInArray:(NSArray*)array; 27 | - (BOOL)isWithinInfluence; 28 | - (void)doHaptics; 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /lib/ios/Interactable/PhysicsBehavior.m: -------------------------------------------------------------------------------- 1 | // 2 | // PhysicsBehavior.m 3 | // Interactable 4 | // 5 | // Created by Tal Kol on 2/4/17. 6 | // Copyright © 2017 Wix. All rights reserved. 7 | // 8 | 9 | #import "PhysicsBehavior.h" 10 | 11 | const CFTimeInterval DURATION_BETWEEN_HAPTICS = 0.5; 12 | 13 | @interface PhysicsBehavior() 14 | @property (nonatomic) UIImpactFeedbackGenerator *hapticsEngine; 15 | @property (nonatomic, assign) CFTimeInterval lastHapticsAction; 16 | @property (nonatomic, assign) BOOL lastIsWithinInfluence; 17 | @property (nonatomic, assign) BOOL lastIsWithinInfluenceInitialized; 18 | @end 19 | 20 | 21 | @implementation PhysicsBehavior 22 | 23 | - (void)setup:(UIView*)target 24 | { 25 | self.target = target; 26 | self.temp = NO; 27 | self.haptics = NO; 28 | self.hapticsEngine = nil; 29 | self.lastHapticsAction = 0.0; 30 | self.lastIsWithinInfluence = NO; 31 | self.lastIsWithinInfluenceInitialized = NO; 32 | self.priority = 1; 33 | } 34 | 35 | - (instancetype)initWithTarget:(UIView*)target 36 | { 37 | if ((self = [super init])) 38 | { 39 | [self setup:target]; 40 | } 41 | return self; 42 | } 43 | 44 | - (instancetype)initWithTarget:(UIView*)target anchorPoint:(CGPoint)anchorPoint 45 | { 46 | if ((self = [super init])) 47 | { 48 | [self setup:target]; 49 | self.anchorPoint = anchorPoint; 50 | } 51 | return self; 52 | } 53 | 54 | - (void)dealloc 55 | { 56 | self.hapticsEngine = nil; 57 | } 58 | 59 | - (void)setHaptics:(BOOL)haptics 60 | { 61 | if (haptics) 62 | { 63 | Class kUIImpactFeedbackGenerator = NSClassFromString(@"UIImpactFeedbackGenerator"); 64 | if (kUIImpactFeedbackGenerator) self.hapticsEngine = [[kUIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleLight]; 65 | } 66 | _haptics = haptics; 67 | } 68 | 69 | - (NSUInteger)findSortIndexInArray:(NSArray*)array 70 | { 71 | return [array indexOfObject:self 72 | inSortedRange:(NSRange){0, [array count]} 73 | options:NSBinarySearchingInsertionIndex 74 | usingComparator:^NSComparisonResult(id obja, id objb) 75 | { 76 | PhysicsBehavior *a = (PhysicsBehavior*)obja; 77 | PhysicsBehavior *b = (PhysicsBehavior*)objb; 78 | if (a.priority == b.priority) return (NSComparisonResult)NSOrderedSame; 79 | if (a.priority > b.priority) return (NSComparisonResult)NSOrderedDescending; 80 | else return (NSComparisonResult)NSOrderedAscending; 81 | }]; 82 | } 83 | 84 | - (BOOL)isWithinInfluence 85 | { 86 | BOOL res = YES; 87 | 88 | if (self.influence) 89 | { 90 | if (self.target.center.x < self.influence.minPoint.x) res = NO; 91 | if (self.target.center.x > self.influence.maxPoint.x) res = NO; 92 | 93 | if (self.target.center.y < self.influence.minPoint.y) res = NO; 94 | if (self.target.center.y > self.influence.maxPoint.y) res = NO; 95 | 96 | // haptics 97 | if (self.lastIsWithinInfluenceInitialized) 98 | { 99 | if (self.lastIsWithinInfluence != res) [self doHaptics]; 100 | } 101 | else 102 | { 103 | self.lastIsWithinInfluenceInitialized = YES; 104 | } 105 | self.lastIsWithinInfluence = res; 106 | } 107 | 108 | return res; 109 | } 110 | 111 | - (void)doHaptics 112 | { 113 | if (!self.hapticsEngine) return; 114 | CFTimeInterval now = CFAbsoluteTimeGetCurrent(); 115 | if (self.lastHapticsAction == 0.0 || (now - self.lastHapticsAction > DURATION_BETWEEN_HAPTICS)) 116 | { 117 | [self.hapticsEngine impactOccurred]; 118 | } 119 | self.lastHapticsAction = now; 120 | } 121 | 122 | @end 123 | -------------------------------------------------------------------------------- /lib/ios/Interactable/PhysicsBounceBehavior.h: -------------------------------------------------------------------------------- 1 | // 2 | // PhysicsBounceBehavior.h 3 | // Interactable 4 | // 5 | // Created by Tal Kol on 2/8/17. 6 | // Copyright © 2017 Wix. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "PhysicsBehavior.h" 11 | 12 | @interface PhysicsBounceBehavior : PhysicsBehavior 13 | 14 | @property (nonatomic, assign) CGPoint minPoint; 15 | @property (nonatomic, assign) CGPoint maxPoint; 16 | @property (nonatomic, assign) CGFloat bounce; 17 | 18 | - (instancetype)initWithTarget:(UIView*)target minPoint:(CGPoint)minPoint maxPoint:(CGPoint)maxPoint; 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /lib/ios/Interactable/PhysicsBounceBehavior.m: -------------------------------------------------------------------------------- 1 | // 2 | // PhysicsBounceBehavior.m 3 | // Interactable 4 | // 5 | // Created by Tal Kol on 2/8/17. 6 | // Copyright © 2017 Wix. All rights reserved. 7 | // 8 | 9 | #import "PhysicsBounceBehavior.h" 10 | 11 | @implementation PhysicsBounceBehavior 12 | 13 | - (instancetype)initWithTarget:(UIView*)target minPoint:(CGPoint)minPoint maxPoint:(CGPoint)maxPoint 14 | { 15 | if ((self = [super initWithTarget:target])) 16 | { 17 | self.minPoint = minPoint; 18 | self.maxPoint = maxPoint; 19 | self.bounce = 0.5; 20 | } 21 | return self; 22 | } 23 | 24 | - (void)executeFrameWithDeltaTime:(CFTimeInterval)deltaTime onObject:(PhysicsObject*)object 25 | { 26 | if (self.minPoint.x == self.target.center.x && object.velocity.x < 0.0) 27 | { 28 | CGFloat vx = -object.velocity.x * self.bounce; 29 | CGFloat vy = object.velocity.y; 30 | object.velocity = CGPointMake(vx, vy); 31 | [self doHaptics]; 32 | } 33 | if (self.minPoint.y == self.target.center.y && object.velocity.y < 0.0) 34 | { 35 | CGFloat vx = object.velocity.x; 36 | CGFloat vy = -object.velocity.y * self.bounce; 37 | object.velocity = CGPointMake(vx, vy); 38 | [self doHaptics]; 39 | } 40 | if (self.maxPoint.x == self.target.center.x && object.velocity.x > 0.0) 41 | { 42 | CGFloat vx = -object.velocity.x * self.bounce; 43 | CGFloat vy = object.velocity.y; 44 | object.velocity = CGPointMake(vx, vy); 45 | [self doHaptics]; 46 | } 47 | if (self.maxPoint.y == self.target.center.y && object.velocity.y > 0.0) 48 | { 49 | CGFloat vx = object.velocity.x; 50 | CGFloat vy = -object.velocity.y * self.bounce; 51 | object.velocity = CGPointMake(vx, vy); 52 | [self doHaptics]; 53 | } 54 | } 55 | 56 | 57 | @end 58 | -------------------------------------------------------------------------------- /lib/ios/Interactable/PhysicsFrictionBehavior.h: -------------------------------------------------------------------------------- 1 | // 2 | // PhysicsFrictionBehavior.h 3 | // Interactable 4 | // 5 | // Created by Tal Kol on 2/9/17. 6 | // Copyright © 2017 Wix. All rights reserved. 7 | // 8 | 9 | #import "PhysicsBehavior.h" 10 | 11 | @interface PhysicsFrictionBehavior : PhysicsBehavior 12 | 13 | @property (nonatomic, assign) CGFloat friction; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /lib/ios/Interactable/PhysicsFrictionBehavior.m: -------------------------------------------------------------------------------- 1 | // 2 | // PhysicsFrictionBehavior.m 3 | // Interactable 4 | // 5 | // Created by Tal Kol on 2/9/17. 6 | // Copyright © 2017 Wix. All rights reserved. 7 | // 8 | 9 | #import "PhysicsFrictionBehavior.h" 10 | 11 | @implementation PhysicsFrictionBehavior 12 | 13 | - (instancetype)initWithTarget:(UIView*)target 14 | { 15 | if ((self = [super initWithTarget:target])) 16 | { 17 | self.priority = 2; 18 | self.friction = 0.7; 19 | } 20 | return self; 21 | } 22 | 23 | - (void)executeFrameWithDeltaTime:(CFTimeInterval)deltaTime onObject:(PhysicsObject*)object 24 | { 25 | if (![self isWithinInfluence]) return; 26 | 27 | CGFloat vx = pow(self.friction, 60.0 * deltaTime) * object.velocity.x; 28 | CGFloat vy = pow(self.friction, 60.0 * deltaTime) * object.velocity.y; 29 | 30 | object.velocity = CGPointMake(vx, vy); 31 | } 32 | 33 | @end 34 | -------------------------------------------------------------------------------- /lib/ios/Interactable/PhysicsGravityWellBehavior.h: -------------------------------------------------------------------------------- 1 | // 2 | // PhysicsGravityWellBehavior.h 3 | // Interactable 4 | // 5 | // Created by Tal Kol on 2/4/17. 6 | // Copyright © 2017 Wix. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "PhysicsBehavior.h" 11 | 12 | @interface PhysicsGravityWellBehavior : PhysicsBehavior 13 | 14 | @property (nonatomic, assign) CGFloat strength; 15 | @property (nonatomic, assign) CGFloat falloff; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /lib/ios/Interactable/PhysicsGravityWellBehavior.m: -------------------------------------------------------------------------------- 1 | // 2 | // PhysicsGravityWellBehavior.m 3 | // Interactable 4 | // 5 | // Created by Tal Kol on 2/4/17. 6 | // Copyright © 2017 Wix. All rights reserved. 7 | // 8 | 9 | #import "PhysicsGravityWellBehavior.h" 10 | 11 | @implementation PhysicsGravityWellBehavior 12 | 13 | - (instancetype)initWithTarget:(UIView*)target anchorPoint:(CGPoint)anchorPoint 14 | { 15 | if ((self = [super initWithTarget:target anchorPoint:anchorPoint])) 16 | { 17 | self.strength = 400.0; 18 | self.falloff = 40.0; 19 | } 20 | return self; 21 | } 22 | 23 | // regular gravitational force is proportional to 1/r^2 which is instable when r -> 0 24 | // instead, we're using a potential that looks like an inverted gaussian which behaves much better 25 | // its deriviative (the force) looks like this: 26 | // https://www.wolframalpha.com/input/?i=max+-5%2F(7)*sqrt(e)x*e%5E(-1%2F2%2F7%5E2*x%5E2) 27 | 28 | - (void)executeFrameWithDeltaTime:(CFTimeInterval)deltaTime onObject:(PhysicsObject*)object 29 | { 30 | if (![self isWithinInfluence]) return; 31 | 32 | CGFloat dx = self.target.center.x - self.anchorPoint.x; 33 | CGFloat dy = self.target.center.y - self.anchorPoint.y; 34 | CGFloat dr = sqrt(dx*dx + dy*dy); 35 | if (dr == 0.0) return; 36 | 37 | CGFloat a = (-self.strength * dr * exp(-0.5 * dr * dr / self.falloff / self.falloff)) / object.mass; 38 | 39 | CGFloat ax = dx / dr * a; 40 | CGFloat vx = object.velocity.x + deltaTime * ax; 41 | 42 | 43 | CGFloat ay = dy / dr * a; 44 | CGFloat vy = object.velocity.y + deltaTime * ay; 45 | 46 | object.velocity = CGPointMake(vx, vy); 47 | } 48 | 49 | @end 50 | -------------------------------------------------------------------------------- /lib/ios/Interactable/PhysicsObject.h: -------------------------------------------------------------------------------- 1 | // 2 | // PhysicsTarget.h 3 | // Interactable 4 | // 5 | // Created by Tal Kol on 2/4/17. 6 | // Copyright © 2017 Wix. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface PhysicsObject : NSObject 13 | 14 | @property (nonatomic, assign) CGPoint velocity; 15 | @property (nonatomic, assign) CGFloat mass; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /lib/ios/Interactable/PhysicsObject.m: -------------------------------------------------------------------------------- 1 | // 2 | // PhysicsObject.m 3 | // Interactable 4 | // 5 | // Created by Tal Kol on 2/4/17. 6 | // Copyright © 2017 Wix. All rights reserved. 7 | // 8 | 9 | #import "PhysicsObject.h" 10 | 11 | @implementation PhysicsObject 12 | 13 | - (instancetype)init 14 | { 15 | if ((self = [super init])) 16 | { 17 | self.velocity = CGPointZero; 18 | self.mass = 1.0; 19 | } 20 | return self; 21 | } 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /lib/ios/Interactable/PhysicsSpringBehavior.h: -------------------------------------------------------------------------------- 1 | // 2 | // PhysicsSnapBehavior.h 3 | // Interactable 4 | // 5 | // Created by Tal Kol on 2/4/17. 6 | // Copyright © 2017 Wix. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "PhysicsBehavior.h" 11 | 12 | @interface PhysicsSpringBehavior : PhysicsBehavior 13 | 14 | @property (nonatomic, assign) CGFloat tension; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /lib/ios/Interactable/PhysicsSpringBehavior.m: -------------------------------------------------------------------------------- 1 | // 2 | // PhysicsSnapBehavior.m 3 | // Interactable 4 | // 5 | // Created by Tal Kol on 2/4/17. 6 | // Copyright © 2017 Wix. All rights reserved. 7 | // 8 | 9 | #import "PhysicsSpringBehavior.h" 10 | 11 | @implementation PhysicsSpringBehavior 12 | 13 | - (instancetype)initWithTarget:(UIView*)target anchorPoint:(CGPoint)anchorPoint 14 | { 15 | if ((self = [super initWithTarget:target anchorPoint:anchorPoint])) 16 | { 17 | self.tension = 300.0; 18 | } 19 | return self; 20 | } 21 | 22 | - (void)executeFrameWithDeltaTime:(CFTimeInterval)deltaTime onObject:(PhysicsObject*)object 23 | { 24 | if (![self isWithinInfluence]) return; 25 | 26 | CGFloat dx = self.target.center.x - self.anchorPoint.x; 27 | CGFloat ax = (-self.tension * dx) / object.mass; 28 | CGFloat vx = object.velocity.x + deltaTime * ax; 29 | 30 | CGFloat dy = self.target.center.y - self.anchorPoint.y; 31 | CGFloat ay = (-self.tension * dy) / object.mass; 32 | CGFloat vy = object.velocity.y + deltaTime * ay; 33 | 34 | object.velocity = CGPointMake(vx, vy); 35 | } 36 | 37 | @end 38 | -------------------------------------------------------------------------------- /lib/ios/Interactable/RCTConvert+Interactable.h: -------------------------------------------------------------------------------- 1 | // 2 | // RCTConvert+Interactable.h 3 | // Interactable 4 | // 5 | // Created by Tal Kol on 1/27/17. 6 | // Copyright © 2017 Wix. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @class InteractablePoint; 13 | 14 | @interface RCTConvert (Interactable) 15 | 16 | + (NSArray *)InteractablePointArray:(id)json; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /lib/ios/Interactable/RCTConvert+Interactable.m: -------------------------------------------------------------------------------- 1 | // 2 | // RCTConvert+Interactable.m 3 | // Interactable 4 | // 5 | // Created by Tal Kol on 1/27/17. 6 | // Copyright © 2017 Wix. All rights reserved. 7 | // 8 | 9 | #import "RCTConvert+Interactable.h" 10 | #import "InteractablePoint.h" 11 | #import "InteractableArea.h" 12 | #import "InteractableSpring.h" 13 | 14 | @implementation RCTConvert(Interactable) 15 | 16 | + (InteractablePoint *)InteractablePoint:(id)json 17 | { 18 | json = [self NSDictionary:json]; 19 | InteractablePoint *point = [InteractablePoint new]; 20 | point.x = [self CGFloat:json[@"x"] ?: @(CGFLOAT_MAX)]; 21 | point.y = [self CGFloat:json[@"y"] ?: @(CGFLOAT_MAX)]; 22 | point.damping = [self CGFloat:json[@"damping"] ?: @(0.0)]; 23 | point.tension = [self CGFloat:json[@"tension"] ?: @(300.0)]; 24 | point.strength = [self CGFloat:json[@"strength"] ?: @(400.0)]; 25 | point.falloff = [self CGFloat:json[@"falloff"] ?: @(40.0)]; 26 | point.id = [self id:json[@"id"] ?: [NSNull null]]; 27 | point.influenceArea = [self InteractableArea:json[@"influenceArea"] ?: nil]; 28 | point.haptics = [self BOOL:json[@"haptics"] ?: @(NO)]; 29 | return point; 30 | } 31 | 32 | + (InteractableArea *)InteractableArea:(id)json 33 | { 34 | json = [self NSDictionary:json]; 35 | if (json == nil) return nil; 36 | InteractableArea *area = [InteractableArea new]; 37 | area.top = [self CGFloat:json[@"top"] ?: @(-CGFLOAT_MAX)]; 38 | area.left = [self CGFloat:json[@"left"] ?: @(-CGFLOAT_MAX)]; 39 | area.bottom = [self CGFloat:json[@"bottom"] ?: @(CGFLOAT_MAX)]; 40 | area.right = [self CGFloat:json[@"right"] ?: @(CGFLOAT_MAX)]; 41 | area.bounce = [self CGFloat:json[@"bounce"] ?: @(0.0)]; 42 | area.haptics = [self BOOL:json[@"haptics"] ?: @(NO)]; 43 | return area; 44 | } 45 | 46 | + (InteractableSpring *)InteractableSpring:(id)json 47 | { 48 | json = [self NSDictionary:json]; 49 | if (json == nil) return nil; 50 | InteractableSpring *spring = [InteractableSpring new]; 51 | spring.tension = [self CGFloat:json[@"tension"] ?: @(CGFLOAT_MAX)]; 52 | spring.damping = [self CGFloat:json[@"damping"] ?: @(0.0)]; 53 | return spring; 54 | } 55 | 56 | RCT_ARRAY_CONVERTER(InteractablePoint) 57 | 58 | @end 59 | -------------------------------------------------------------------------------- /lib/ios/InteractableTests/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 | -------------------------------------------------------------------------------- /lib/ios/InteractableTests/InteractableTests.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 InteractableTests : XCTestCase 18 | 19 | @end 20 | 21 | @implementation InteractableTests 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 | -------------------------------------------------------------------------------- /lib/src/InteractableView.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import ReactNative, { requireNativeComponent, Animated, NativeModules, UIManager, Platform } from 'react-native'; 3 | 4 | // this is required in order to perform imperative commands 5 | const NativeViewManager = NativeModules.InteractableViewManager; 6 | 7 | const NativeInteractableView = requireNativeComponent('InteractableView', null); 8 | 9 | class WrappedInteractableView extends Component { 10 | render() { 11 | return ( 12 | this._nativeViewRef = ref} 15 | /> 16 | ); 17 | } 18 | 19 | getScrollableNode() { 20 | return ReactNative.findNodeHandle(this._nativeViewRef); 21 | } 22 | } 23 | 24 | // this is required in order to support native events: 25 | const AnimatedInteractableView = Animated.createAnimatedComponent(WrappedInteractableView); 26 | 27 | class WrappedAnimatedInteractableView extends Component { 28 | constructor(props) { 29 | super(props); 30 | if (this.props.animatedValueX || this.props.animatedValueY) { 31 | this._animatedEvent = Animated.event( 32 | [{ 33 | nativeEvent: { 34 | x: this.props.animatedValueX, 35 | y: this.props.animatedValueY 36 | } 37 | }], 38 | { useNativeDriver: !!this.props.animatedNativeDriver } 39 | ); 40 | } 41 | } 42 | 43 | // this helps us verify that useNativeDriver actually works and we don't rely on the bridge - should be called in the constructor 44 | /* 45 | chokeTheBridge() { 46 | let j = 0; 47 | setInterval(() => { 48 | for (var index = 0; index < 1e9; index++) { 49 | j++; 50 | } 51 | }, 500); 52 | } 53 | */ 54 | 55 | render() { 56 | return ( 57 | 65 | ); 66 | } 67 | 68 | setVelocity(params) { 69 | if (Platform.OS === 'ios') { 70 | NativeViewManager.setVelocity(ReactNative.findNodeHandle(this), params); 71 | } else if (Platform.OS === 'android') { 72 | UIManager.dispatchViewManagerCommand( 73 | ReactNative.findNodeHandle(this), 74 | UIManager.InteractableView.Commands.setVelocity, 75 | [params], 76 | ); 77 | } 78 | } 79 | 80 | snapTo(params) { 81 | if (Platform.OS === 'ios') { 82 | NativeViewManager.snapTo(ReactNative.findNodeHandle(this), params); 83 | } else if (Platform.OS === 'android') { 84 | UIManager.dispatchViewManagerCommand( 85 | ReactNative.findNodeHandle(this), 86 | UIManager.InteractableView.Commands.snapTo, 87 | [params], 88 | ); 89 | } 90 | } 91 | 92 | changePosition(params) { 93 | if (Platform.OS === 'ios') { 94 | NativeViewManager.changePosition(ReactNative.findNodeHandle(this), params); 95 | } else if (Platform.OS === 'android') { 96 | UIManager.dispatchViewManagerCommand( 97 | ReactNative.findNodeHandle(this), 98 | UIManager.InteractableView.Commands.changePosition, 99 | [params], 100 | ); 101 | } 102 | } 103 | 104 | bringToFront() { 105 | if (Platform.OS === 'android') { 106 | UIManager.dispatchViewManagerCommand( 107 | ReactNative.findNodeHandle(this), 108 | UIManager.InteractableView.Commands.bringToFront, 109 | [], 110 | ); 111 | } 112 | } 113 | } 114 | 115 | export default WrappedAnimatedInteractableView; 116 | -------------------------------------------------------------------------------- /logos/circle-notitle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wix-incubator/react-native-interactable/5ea6b0f3d13b3a9169fab58e84c6736471f0b83a/logos/circle-notitle.png -------------------------------------------------------------------------------- /logos/circle-title.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wix-incubator/react-native-interactable/5ea6b0f3d13b3a9169fab58e84c6736471f0b83a/logos/circle-title.png -------------------------------------------------------------------------------- /logos/square-notitle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wix-incubator/react-native-interactable/5ea6b0f3d13b3a9169fab58e84c6736471f0b83a/logos/square-notitle.png -------------------------------------------------------------------------------- /logos/square-title.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wix-incubator/react-native-interactable/5ea6b0f3d13b3a9169fab58e84c6736471f0b83a/logos/square-title.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-interactable", 3 | "version": "2.0.1", 4 | "description": "High performance interactable views in React Native", 5 | "license": "MIT", 6 | "author": "Tal Kol ", 7 | "publishConfig": { 8 | "registry": "https://registry.npmjs.org/" 9 | }, 10 | "bugs": { 11 | "url": "https://github.com/wix/react-native-interactable/issues" 12 | }, 13 | "homepage": "https://github.com/wix/react-native-interactable", 14 | "readme": "https://github.com/wix/react-native-interactable#readme", 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/wix/react-native-interactable.git" 18 | }, 19 | "main": "lib/index.js", 20 | "scripts": { 21 | "start": "node ./node_modules/react-native/local-cli/cli.js start", 22 | "ios": "react-native run-ios --project-path playground/ios/", 23 | "android": "react-native run-android --root playground", 24 | "podinstall": "cd playground/ios && pod install", 25 | "test": ":", 26 | "e2e:ios": "detox test --configuration ios.sim.release --debug-synchronization", 27 | "e2e:ios-multi": "npm run e2e:ios -- -w 2", 28 | "e2e:android": "npm run build:android && detox test --configuration android.emu.release --loglevel verbose", 29 | "e2e:android:ci": "npm run build:android && detox test --configuration android.emu.release --loglevel verbose --headless", 30 | "build:ios": "detox build --configuration ios.sim.release", 31 | "build:android": "detox build --configuration android.emu.release" 32 | }, 33 | "peerDependencies": { 34 | "react": ">= 15.4.1", 35 | "react-native": ">= 0.40.0" 36 | }, 37 | "jest": { 38 | "preset": "react-native" 39 | }, 40 | "devDependencies": { 41 | "@babel/core": "7.6.0", 42 | "@babel/runtime": "^7.4.2", 43 | "@react-native-community/eslint-config": "^0.0.3", 44 | "@types/react-native": ">= 0.52.2", 45 | "babel-jest": "^24.5.0", 46 | "detox": "^18.0.0", 47 | "eslint": "^5.16.0", 48 | "jest": "^24.5.0", 49 | "metro-react-native-babel-preset": "^0.53.1", 50 | "mocha": "^6.0.0", 51 | "react": "16.9.0", 52 | "react-native": "0.61.4", 53 | "react-test-renderer": "16.8.3" 54 | }, 55 | "typings": "typings/react-native-interactable.d.ts", 56 | "babel": { 57 | "env": { 58 | "test": { 59 | "presets": [ 60 | "react-native" 61 | ] 62 | } 63 | } 64 | }, 65 | "detox_ignore": { 66 | "specs": "e2e", 67 | "configurations": { 68 | "ios.sim.debug": { 69 | "binaryPath": "playground/ios/DerivedData/playground/Build/Products/Debug-iphonesimulator/playground.app", 70 | "build": "RCT_NO_LAUNCH_PACKAGER=true xcodebuild build -scheme playground -project playground/ios/playground.xcodeproj -sdk iphonesimulator -configuration Debug -derivedDataPath playground/ios/DerivedData/playground ONLY_ACTIVE_ARCH=YES -quiet -UseModernBuildSystem=NO", 71 | "type": "ios.simulator", 72 | "name": "iPhone 11 Pro" 73 | }, 74 | "ios.sim.release": { 75 | "binaryPath": "playground/ios/DerivedData/playground/Build/Products/Release-iphonesimulator/playground.app", 76 | "build": "RCT_NO_LAUNCH_PACKAGER=true xcodebuild build -scheme playground -project playground/ios/playground.xcodeproj -sdk iphonesimulator -configuration Release -derivedDataPath playground/ios/DerivedData/playground ONLY_ACTIVE_ARCH=YES -quiet -UseModernBuildSystem=NO", 77 | "type": "ios.simulator", 78 | "name": "iPhone 11 Pro" 79 | }, 80 | "android.emu.debug": { 81 | "binaryPath": "playground/android/app/build/outputs/apk/debug/app-debug.apk", 82 | "build": "cd playground/android && ./gradlew :app:assembleDebug :app:assembleAndroidTest -DtestBuildType=debug", 83 | "type": "android.emulator", 84 | "name": "Pixel_2_API_26" 85 | }, 86 | "android.emu.release": { 87 | "binaryPath": "playground/android/app/build/outputs/apk/release/app-release.apk", 88 | "build": "cd playground/android && ./gradlew :app:assembleRelease :app:assembleAndroidTest -DtestBuildType=release", 89 | "type": "android.emulator", 90 | "name": "Pixel_2_API_26" 91 | } 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /playground/.babelrc-IGNORE: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["module:metro-react-native-babel-preset"] 3 | } 4 | -------------------------------------------------------------------------------- /playground/.buckconfig: -------------------------------------------------------------------------------- 1 | 2 | [android] 3 | target = Google Inc.:Google APIs:23 4 | 5 | [maven_repositories] 6 | central = https://repo1.maven.org/maven2 7 | -------------------------------------------------------------------------------- /playground/.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | ; We fork some components by platform 3 | .*/*[.]android.js 4 | 5 | ; Ignore "BUCK" generated dirs 6 | /\.buckd/ 7 | 8 | ; Ignore polyfills 9 | node_modules/react-native/Libraries/polyfills/.* 10 | 11 | ; These should not be required directly 12 | ; require from fbjs/lib instead: require('fbjs/lib/warning') 13 | node_modules/warning/.* 14 | 15 | ; Flow doesn't support platforms 16 | .*/Libraries/Utilities/LoadingView.js 17 | 18 | [untyped] 19 | .*/node_modules/@react-native-community/cli/.*/.* 20 | 21 | [include] 22 | 23 | [libs] 24 | node_modules/react-native/Libraries/react-native/react-native-interface.js 25 | node_modules/react-native/flow/ 26 | 27 | [options] 28 | emoji=true 29 | 30 | esproposal.optional_chaining=enable 31 | esproposal.nullish_coalescing=enable 32 | 33 | module.file_ext=.js 34 | module.file_ext=.json 35 | module.file_ext=.ios.js 36 | 37 | munge_underscores=true 38 | 39 | module.name_mapper='^react-native$' -> '/node_modules/react-native/Libraries/react-native/react-native-implementation' 40 | module.name_mapper='^react-native/\(.*\)$' -> '/node_modules/react-native/\1' 41 | module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> '/node_modules/react-native/Libraries/Image/RelativeImageStub' 42 | 43 | suppress_type=$FlowIssue 44 | suppress_type=$FlowFixMe 45 | suppress_type=$FlowFixMeProps 46 | suppress_type=$FlowFixMeState 47 | 48 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(\\)? *\\(site=[a-z,_]*react_native\\(_ios\\)?_\\(oss\\|fb\\)[a-z,_]*\\)?)\\) 49 | suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(\\)? *\\(site=[a-z,_]*react_native\\(_ios\\)?_\\(oss\\|fb\\)[a-z,_]*\\)?)\\)?:? #[0-9]+ 50 | suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError 51 | 52 | [lints] 53 | sketchy-null-number=warn 54 | sketchy-null-mixed=warn 55 | sketchy-number=warn 56 | untyped-type-import=warn 57 | nonstrict-import=warn 58 | deprecated-type=warn 59 | unsafe-getters-setters=warn 60 | inexact-spread=warn 61 | unnecessary-invariant=warn 62 | signature-verification-failure=warn 63 | deprecated-utility=error 64 | 65 | [strict] 66 | deprecated-type 67 | nonstrict-import 68 | sketchy-null 69 | unclear-type 70 | unsafe-getters-setters 71 | untyped-import 72 | untyped-type-import 73 | 74 | [version] 75 | ^0.105.0 76 | -------------------------------------------------------------------------------- /playground/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.playground", 39 | ) 40 | 41 | android_resource( 42 | name = "res", 43 | package = "com.playground", 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 | -------------------------------------------------------------------------------- /playground/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 | ) -------------------------------------------------------------------------------- /playground/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 | 12 | # Disabling obfuscation is useful if you collect stack traces from production crashes 13 | # (unless you are using a system that supports de-obfuscate the stack traces). 14 | -dontobfuscate 15 | 16 | # React Native 17 | 18 | # Keep our interfaces so they can be used by other ProGuard rules. 19 | # See http://sourceforge.net/p/proguard/bugs/466/ 20 | -keep,allowobfuscation @interface com.facebook.proguard.annotations.DoNotStrip 21 | -keep,allowobfuscation @interface com.facebook.proguard.annotations.KeepGettersAndSetters 22 | -keep,allowobfuscation @interface com.facebook.common.internal.DoNotStrip 23 | 24 | # Do not strip any method/class that is annotated with @DoNotStrip 25 | -keep @com.facebook.proguard.annotations.DoNotStrip class * 26 | -keep @com.facebook.common.internal.DoNotStrip class * 27 | -keepclassmembers class * { 28 | @com.facebook.proguard.annotations.DoNotStrip *; 29 | @com.facebook.common.internal.DoNotStrip *; 30 | } 31 | 32 | -keepclassmembers @com.facebook.proguard.annotations.KeepGettersAndSetters class * { 33 | void set*(***); 34 | *** get*(); 35 | } 36 | 37 | -keep class * extends com.facebook.react.bridge.JavaScriptModule { *; } 38 | -keep class * extends com.facebook.react.bridge.NativeModule { *; } 39 | -keepclassmembers,includedescriptorclasses class * { native ; } 40 | -keepclassmembers class * { @com.facebook.react.uimanager.UIProp ; } 41 | -keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactProp ; } 42 | -keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactPropGroup ; } 43 | 44 | -dontwarn com.facebook.react.** 45 | 46 | # TextLayoutBuilder uses a non-public Android constructor within StaticLayout. 47 | # See libs/proxy/src/main/java/com/facebook/fbui/textlayoutbuilder/proxy for details. 48 | -dontwarn android.text.StaticLayout 49 | 50 | # okhttp 51 | 52 | -keepattributes Signature 53 | -keepattributes *Annotation* 54 | -keep class okhttp3.** { *; } 55 | -keep interface okhttp3.** { *; } 56 | -dontwarn okhttp3.** 57 | 58 | # okio 59 | 60 | -keep class sun.misc.Unsafe { *; } 61 | -dontwarn java.nio.file.* 62 | -dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement 63 | -dontwarn okio.** 64 | -------------------------------------------------------------------------------- /playground/android/app/src/androidTest/java/com/playground/DetoxTest.java: -------------------------------------------------------------------------------- 1 | package com.playground; 2 | 3 | import com.wix.detox.Detox; 4 | 5 | import org.junit.Rule; 6 | import org.junit.Test; 7 | import org.junit.runner.RunWith; 8 | 9 | import androidx.test.ext.junit.runners.AndroidJUnit4; 10 | import androidx.test.filters.LargeTest; 11 | import androidx.test.rule.ActivityTestRule; 12 | 13 | @RunWith(AndroidJUnit4.class) 14 | @LargeTest 15 | public class DetoxTest { 16 | 17 | @Rule 18 | public ActivityTestRule mActivityRule = new ActivityTestRule<>(MainActivity.class, false, false); 19 | 20 | @Test 21 | public void runDetoxTests() { 22 | Detox.runTests(mActivityRule); 23 | } 24 | } -------------------------------------------------------------------------------- /playground/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /playground/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 12 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /playground/android/app/src/main/java/com/playground/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.playground; 2 | 3 | import com.facebook.react.ReactActivity; 4 | 5 | public class MainActivity extends ReactActivity { 6 | 7 | /** 8 | * Returns the name of the main component registered from JavaScript. 9 | * This is used to schedule rendering of the component. 10 | */ 11 | @Override 12 | protected String getMainComponentName() { 13 | return "playground"; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /playground/android/app/src/main/java/com/playground/MainApplication.java: -------------------------------------------------------------------------------- 1 | package com.playground; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | import com.facebook.react.PackageList; 6 | import com.facebook.react.ReactApplication; 7 | import com.facebook.react.ReactNativeHost; 8 | import com.facebook.react.ReactPackage; 9 | import com.facebook.react.shell.MainReactPackage; 10 | import com.facebook.soloader.SoLoader; 11 | import com.wix.interactable.Interactable; 12 | 13 | import java.lang.reflect.InvocationTargetException; 14 | import java.util.Arrays; 15 | import java.util.List; 16 | 17 | public class MainApplication extends Application implements ReactApplication { 18 | 19 | private final ReactNativeHost mReactNativeHost = 20 | new ReactNativeHost(this) { 21 | @Override 22 | public boolean getUseDeveloperSupport() { 23 | return BuildConfig.DEBUG; 24 | } 25 | 26 | @Override 27 | protected List getPackages() { 28 | return Arrays.asList( 29 | new MainReactPackage(), 30 | new Interactable() 31 | ); 32 | } 33 | 34 | @Override 35 | protected String getJSMainModuleName() { 36 | return "index"; 37 | } 38 | }; 39 | 40 | @Override 41 | public ReactNativeHost getReactNativeHost() { 42 | return mReactNativeHost; 43 | } 44 | 45 | @Override 46 | public void onCreate() { 47 | super.onCreate(); 48 | SoLoader.init(this, /* native exopackage */ false); 49 | initializeFlipper(this); // Remove this line if you don't want Flipper enabled 50 | } 51 | /** 52 | * Loads Flipper in React Native templates. 53 | * 54 | * @param context 55 | */ 56 | private static void initializeFlipper(Context context) { 57 | if (BuildConfig.DEBUG) { 58 | try { 59 | /* 60 | We use reflection here to pick up the class that initializes Flipper, 61 | since Flipper library is not available in release mode 62 | */ 63 | Class aClass = Class.forName("com.facebook.flipper.ReactNativeFlipper"); 64 | aClass.getMethod("initializeFlipper", Context.class).invoke(null, context); 65 | } catch (ClassNotFoundException e) { 66 | e.printStackTrace(); 67 | } catch (NoSuchMethodException e) { 68 | e.printStackTrace(); 69 | } catch (IllegalAccessException e) { 70 | e.printStackTrace(); 71 | } catch (InvocationTargetException e) { 72 | e.printStackTrace(); 73 | } 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /playground/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wix-incubator/react-native-interactable/5ea6b0f3d13b3a9169fab58e84c6736471f0b83a/playground/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /playground/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wix-incubator/react-native-interactable/5ea6b0f3d13b3a9169fab58e84c6736471f0b83a/playground/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /playground/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wix-incubator/react-native-interactable/5ea6b0f3d13b3a9169fab58e84c6736471f0b83a/playground/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /playground/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wix-incubator/react-native-interactable/5ea6b0f3d13b3a9169fab58e84c6736471f0b83a/playground/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /playground/android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | playground 3 | 4 | -------------------------------------------------------------------------------- /playground/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /playground/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 | supportLibVersion = "28.0.0" 10 | kotlinVersion = '1.3.31' 11 | } 12 | repositories { 13 | google() 14 | mavenLocal() 15 | jcenter() 16 | } 17 | dependencies { 18 | classpath('com.android.tools.build:gradle:3.4.2') 19 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" 20 | 21 | // NOTE: Do not place your application dependencies here; they belong 22 | // in the individual module build.gradle files 23 | } 24 | } 25 | 26 | allprojects { 27 | repositories { 28 | mavenLocal() 29 | maven { 30 | // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm 31 | url "$rootDir/../../node_modules/react-native/android" 32 | } 33 | maven { 34 | // Android JSC is installed from npm 35 | url("$rootDir/../../node_modules/jsc-android/dist") 36 | } 37 | jcenter() 38 | google() 39 | maven { url 'https://jitpack.io' } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /playground/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 | -------------------------------------------------------------------------------- /playground/android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wix-incubator/react-native-interactable/5ea6b0f3d13b3a9169fab58e84c6736471f0b83a/playground/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /playground/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 | -------------------------------------------------------------------------------- /playground/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 https://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 | -------------------------------------------------------------------------------- /playground/android/playground.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wix-incubator/react-native-interactable/5ea6b0f3d13b3a9169fab58e84c6736471f0b83a/playground/android/playground.keystore -------------------------------------------------------------------------------- /playground/android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'playground' 2 | include ':app' 3 | 4 | include ':react-native-interactable' 5 | project(':react-native-interactable').projectDir = new File(rootProject.projectDir, '../../lib/android') 6 | 7 | include ':detox' 8 | project(':detox').projectDir = new File(rootProject.projectDir, '../../`node_modules/detox/android/detox') -------------------------------------------------------------------------------- /playground/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "playground", 3 | "displayName": "playground" 4 | } -------------------------------------------------------------------------------- /playground/assets/airport-photo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wix-incubator/react-native-interactable/5ea6b0f3d13b3a9169fab58e84c6736471f0b83a/playground/assets/airport-photo.jpg -------------------------------------------------------------------------------- /playground/assets/calendar-body.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wix-incubator/react-native-interactable/5ea6b0f3d13b3a9169fab58e84c6736471f0b83a/playground/assets/calendar-body.png -------------------------------------------------------------------------------- /playground/assets/calendar-header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wix-incubator/react-native-interactable/5ea6b0f3d13b3a9169fab58e84c6736471f0b83a/playground/assets/calendar-header.png -------------------------------------------------------------------------------- /playground/assets/card-photo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wix-incubator/react-native-interactable/5ea6b0f3d13b3a9169fab58e84c6736471f0b83a/playground/assets/card-photo.jpg -------------------------------------------------------------------------------- /playground/assets/chatheads-delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wix-incubator/react-native-interactable/5ea6b0f3d13b3a9169fab58e84c6736471f0b83a/playground/assets/chatheads-delete.png -------------------------------------------------------------------------------- /playground/assets/chatheads-face1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wix-incubator/react-native-interactable/5ea6b0f3d13b3a9169fab58e84c6736471f0b83a/playground/assets/chatheads-face1.jpg -------------------------------------------------------------------------------- /playground/assets/chatheads-face2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wix-incubator/react-native-interactable/5ea6b0f3d13b3a9169fab58e84c6736471f0b83a/playground/assets/chatheads-face2.jpg -------------------------------------------------------------------------------- /playground/assets/icon-check.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wix-incubator/react-native-interactable/5ea6b0f3d13b3a9169fab58e84c6736471f0b83a/playground/assets/icon-check.png -------------------------------------------------------------------------------- /playground/assets/icon-clock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wix-incubator/react-native-interactable/5ea6b0f3d13b3a9169fab58e84c6736471f0b83a/playground/assets/icon-clock.png -------------------------------------------------------------------------------- /playground/assets/icon-menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wix-incubator/react-native-interactable/5ea6b0f3d13b3a9169fab58e84c6736471f0b83a/playground/assets/icon-menu.png -------------------------------------------------------------------------------- /playground/assets/icon-trash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wix-incubator/react-native-interactable/5ea6b0f3d13b3a9169fab58e84c6736471f0b83a/playground/assets/icon-trash.png -------------------------------------------------------------------------------- /playground/assets/icon-up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wix-incubator/react-native-interactable/5ea6b0f3d13b3a9169fab58e84c6736471f0b83a/playground/assets/icon-up.png -------------------------------------------------------------------------------- /playground/assets/map-bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wix-incubator/react-native-interactable/5ea6b0f3d13b3a9169fab58e84c6736471f0b83a/playground/assets/map-bg.jpg -------------------------------------------------------------------------------- /playground/assets/tinder-photo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wix-incubator/react-native-interactable/5ea6b0f3d13b3a9169fab58e84c6736471f0b83a/playground/assets/tinder-photo.jpg -------------------------------------------------------------------------------- /playground/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['module:metro-react-native-babel-preset'], 3 | }; 4 | -------------------------------------------------------------------------------- /playground/index.js: -------------------------------------------------------------------------------- 1 | import { AppRegistry } from 'react-native'; 2 | import App from './src/app'; 3 | 4 | AppRegistry.registerComponent('playground', () => App); 5 | -------------------------------------------------------------------------------- /playground/ios/Playground-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 | -------------------------------------------------------------------------------- /playground/ios/Playground-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 | -------------------------------------------------------------------------------- /playground/ios/PlaygroundTests/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 | -------------------------------------------------------------------------------- /playground/ios/PlaygroundTests/PlaygroundTests.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 PlaygroundTests : XCTestCase 18 | 19 | @end 20 | 21 | @implementation PlaygroundTests 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 | -------------------------------------------------------------------------------- /playground/ios/Podfile: -------------------------------------------------------------------------------- 1 | platform :ios, '9.0' 2 | require_relative '../../node_modules/@react-native-community/cli-platform-ios/native_modules' 3 | 4 | target 'playground' do 5 | # Pods for Playground 6 | pod 'Interactable', :path => "../../Interactable.podspec" 7 | pod 'FBLazyVector', :path => "../../node_modules/react-native/Libraries/FBLazyVector" 8 | pod 'FBReactNativeSpec', :path => "../../node_modules/react-native/Libraries/FBReactNativeSpec" 9 | pod 'RCTRequired', :path => "../../node_modules/react-native/Libraries/RCTRequired" 10 | pod 'RCTTypeSafety', :path => "../../node_modules/react-native/Libraries/TypeSafety" 11 | pod 'React', :path => '../../node_modules/react-native/' 12 | pod 'React-Core', :path => '../../node_modules/react-native/' 13 | pod 'React-CoreModules', :path => '../../node_modules/react-native/React/CoreModules' 14 | pod 'React-Core/DevSupport', :path => '../../node_modules/react-native/' 15 | pod 'React-RCTActionSheet', :path => '../../node_modules/react-native/Libraries/ActionSheetIOS' 16 | pod 'React-RCTAnimation', :path => '../../node_modules/react-native/Libraries/NativeAnimation' 17 | pod 'React-RCTBlob', :path => '../../node_modules/react-native/Libraries/Blob' 18 | pod 'React-RCTImage', :path => '../../node_modules/react-native/Libraries/Image' 19 | pod 'React-RCTLinking', :path => '../../node_modules/react-native/Libraries/LinkingIOS' 20 | pod 'React-RCTNetwork', :path => '../../node_modules/react-native/Libraries/Network' 21 | pod 'React-RCTSettings', :path => '../../node_modules/react-native/Libraries/Settings' 22 | pod 'React-RCTText', :path => '../../node_modules/react-native/Libraries/Text' 23 | pod 'React-RCTVibration', :path => '../../node_modules/react-native/Libraries/Vibration' 24 | pod 'React-Core/RCTWebSocket', :path => '../../node_modules/react-native/' 25 | pod 'React-cxxreact', :path => '../../node_modules/react-native/ReactCommon/cxxreact' 26 | pod 'React-jsi', :path => '../../node_modules/react-native/ReactCommon/jsi' 27 | pod 'React-jsiexecutor', :path => '../../node_modules/react-native/ReactCommon/jsiexecutor' 28 | pod 'React-jsinspector', :path => '../../node_modules/react-native/ReactCommon/jsinspector' 29 | pod 'ReactCommon/jscallinvoker', :path => "../../node_modules/react-native/ReactCommon" 30 | pod 'ReactCommon/turbomodule/core', :path => "../../node_modules/react-native/ReactCommon" 31 | pod 'Yoga', :path => '../../node_modules/react-native/ReactCommon/yoga' 32 | 33 | pod 'DoubleConversion', :podspec => '../../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec' 34 | pod 'glog', :podspec => '../../node_modules/react-native/third-party-podspecs/glog.podspec' 35 | pod 'Folly', :podspec => '../../node_modules/react-native/third-party-podspecs/Folly.podspec' 36 | 37 | use_native_modules! 38 | 39 | pre_install do |installer| 40 | Pod::Installer::Xcode::TargetValidator.send(:define_method, :verify_no_static_framework_transitive_dependencies) {} 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /playground/ios/playground.xcodeproj/xcshareddata/xcschemes/playground.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 43 | 49 | 50 | 51 | 52 | 53 | 58 | 59 | 60 | 61 | 67 | 68 | 69 | 70 | 71 | 72 | 82 | 84 | 90 | 91 | 92 | 93 | 94 | 95 | 101 | 103 | 109 | 110 | 111 | 112 | 114 | 115 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /playground/ios/playground.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /playground/ios/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /playground/ios/playground/AppDelegate.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import 11 | #import 12 | 13 | @interface AppDelegate : UIResponder 14 | 15 | @property (nonatomic, strong) UIWindow *window; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /playground/ios/playground/AppDelegate.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import "AppDelegate.h" 11 | 12 | #import 13 | #import 14 | #import 15 | 16 | @implementation AppDelegate 17 | 18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 19 | { 20 | RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions]; 21 | RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge 22 | moduleName:@"playground" 23 | initialProperties:nil]; 24 | rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1]; 25 | 26 | self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; 27 | UIViewController *rootViewController = [UIViewController new]; 28 | rootViewController.view = rootView; 29 | self.window.rootViewController = rootViewController; 30 | [self.window makeKeyAndVisible]; 31 | return YES; 32 | } 33 | 34 | - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge 35 | { 36 | #if DEBUG 37 | return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil]; 38 | #else 39 | return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; 40 | #endif 41 | } 42 | 43 | @end 44 | -------------------------------------------------------------------------------- /playground/ios/playground/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 | -------------------------------------------------------------------------------- /playground/ios/playground/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ios-marketing", 45 | "size" : "1024x1024", 46 | "scale" : "1x" 47 | } 48 | ], 49 | "info" : { 50 | "version" : 1, 51 | "author" : "xcode" 52 | } 53 | } -------------------------------------------------------------------------------- /playground/ios/playground/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /playground/ios/playground/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | playground 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier) 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 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UIViewControllerBasedStatusBarAppearance 40 | 41 | NSLocationWhenInUseUsageDescription 42 | 43 | NSAppTransportSecurity 44 | 45 | 46 | NSExceptionDomains 47 | 48 | localhost 49 | 50 | NSExceptionAllowsInsecureHTTPLoads 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /playground/ios/playground/main.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import 11 | 12 | #import "AppDelegate.h" 13 | 14 | int main(int argc, char * argv[]) { 15 | @autoreleasepool { 16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /playground/metro.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Metro configuration for React Native 3 | * https://github.com/facebook/react-native 4 | * 5 | * @format 6 | */ 7 | 8 | module.exports = { 9 | transformer: { 10 | getTransformOptions: async () => ({ 11 | transform: { 12 | experimentalImportSupport: false, 13 | inlineRequires: false, 14 | }, 15 | }), 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /playground/src/examples/AlertAreas.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { StyleSheet, View, Dimensions, Text } from 'react-native'; 3 | import Interactable from 'react-native-interactable'; 4 | 5 | const Screen = { 6 | width: Dimensions.get('window').width, 7 | height: Dimensions.get('window').height - 75 8 | }; 9 | 10 | export default class AlertAreas extends Component { 11 | 12 | constructor(props) { 13 | super(props); 14 | this.state = { 15 | dragEnabled: true 16 | }; 17 | } 18 | render() { 19 | return ( 20 | 21 | 22 | 23 | 24 | 25 | Non Draggable Area 26 | 27 | 28 | 29 | 40 | 41 | 42 | 43 | ); 44 | } 45 | onAlert(event) { 46 | console.log('alert:', event.nativeEvent); 47 | if(JSON.stringify(event.nativeEvent).includes("\"blue\":\"enter\"")){ 48 | this.setState({dragEnabled: false}); 49 | } 50 | if(JSON.stringify(event.nativeEvent).includes("\"blue\":\"leave\"")){ 51 | this.setState({dragEnabled: true}); 52 | } 53 | } 54 | onDrag(event) { 55 | console.log('drag:', event.nativeEvent); 56 | } 57 | } 58 | 59 | const styles = StyleSheet.create({ 60 | container: { 61 | flex: 1, 62 | justifyContent: 'center', 63 | alignItems: 'center', 64 | backgroundColor: 'white', 65 | }, 66 | markerContainer: { 67 | flex: 1, 68 | justifyContent: 'center', 69 | alignItems: 'center', 70 | position: 'absolute', 71 | left: 0, 72 | right: 0, 73 | top: 0, 74 | bottom: 0 75 | }, 76 | }); 77 | -------------------------------------------------------------------------------- /playground/src/examples/ChangePosition.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { StyleSheet, View, TouchableOpacity, Text } from 'react-native'; 3 | import Interactable from 'react-native-interactable'; 4 | 5 | export default class ChangePosition extends Component { 6 | constructor(props) { 7 | super(props); 8 | this.state = { 9 | }; 10 | } 11 | render() { 12 | const snapPoints = [ 13 | {x: -140, y: -250}, 14 | {x: 140, y: -250}, 15 | {x: -140, y: -120}, 16 | {x: 140, y: -120}, 17 | {x: -140, y: 0}, 18 | {x: 140, y: 0}, 19 | {x: -140, y: 120}, 20 | {x: 140, y: 120}, 21 | {x: -140, y: 250}, 22 | {x: 140, y: 250} 23 | ]; 24 | const blueDestination = snapPoints[3]; 25 | return ( 26 | 27 | 33 | 34 | 35 | 41 | 42 | 43 | 49 | { 51 | this.refs['blue'].changePosition(blueDestination) 52 | }} 53 | > 54 | {"ChangePosition to " + JSON.stringify(blueDestination)} 55 | 56 | { 58 | this.refs['green'].changePosition({ 59 | x: (Math.random() - 0.5) * 280, 60 | y: (Math.random() - 0.5) * 500} 61 | ) 62 | }} 63 | > 64 | ChangePosition to random 65 | 66 | 67 | 68 | ); 69 | } 70 | } 71 | 72 | const styles = StyleSheet.create({ 73 | container: { 74 | flex: 1, 75 | justifyContent: 'center', 76 | alignItems: 'center', 77 | backgroundColor: 'white', 78 | } 79 | }); 80 | -------------------------------------------------------------------------------- /playground/src/examples/ChatHeads.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { StyleSheet, View } from 'react-native'; 3 | import Interactable from 'react-native-interactable'; 4 | 5 | export default class ChatHeads extends Component { 6 | render() { 7 | return ( 8 | 9 | 23 | 24 | 25 | 26 | ); 27 | } 28 | } 29 | 30 | const styles = StyleSheet.create({ 31 | container: { 32 | flex: 1, 33 | justifyContent: 'center', 34 | alignItems: 'center', 35 | backgroundColor: 'white', 36 | } 37 | }); 38 | -------------------------------------------------------------------------------- /playground/src/examples/CollapsingHeader.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { StyleSheet, View, Animated } from 'react-native'; 3 | import Interactable from 'react-native-interactable'; 4 | 5 | export default class CollapsingHeader extends Component { 6 | constructor(props) { 7 | super(props); 8 | this._deltaY = new Animated.Value(0); 9 | } 10 | render() { 11 | return ( 12 | 13 | 14 | 15 | 31 | 32 | 33 | 34 | 35 | 40 | 41 | 42 | 43 | 44 | ); 45 | } 46 | } 47 | 48 | const styles = StyleSheet.create({ 49 | container: { 50 | flex: 1, 51 | backgroundColor: '#e0e0e0', 52 | } 53 | }); 54 | -------------------------------------------------------------------------------- /playground/src/examples/CollapsingHeaderWithScroll.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { StyleSheet, View, Animated, ScrollView, Dimensions } from 'react-native'; 3 | import Interactable from 'react-native-interactable'; 4 | 5 | const Screen = { 6 | height: Dimensions.get('window').height 7 | }; 8 | 9 | export default class CollapsingHeaderWithScroll extends Component { 10 | constructor(props) { 11 | super(props); 12 | this._deltaY = new Animated.Value(0); 13 | this.state = { 14 | canScroll: false 15 | }; 16 | } 17 | render() { 18 | return ( 19 | 20 | 21 | 22 | 38 | 39 | 40 | 41 | 42 | 50 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | ); 68 | } 69 | onSnap(event) { 70 | const { id } = event.nativeEvent; 71 | if (id === 'bottom') { 72 | this.setState({ canScroll: true }); 73 | } 74 | } 75 | onScroll(event) { 76 | const { contentOffset } = event.nativeEvent; 77 | if (contentOffset.y <= 0) { 78 | this.setState({ canScroll: false }); 79 | } 80 | } 81 | } 82 | 83 | const styles = StyleSheet.create({ 84 | container: { 85 | flex: 1, 86 | backgroundColor: '#e0e0e0', 87 | }, 88 | placeholder: { 89 | backgroundColor: '#65C888', 90 | flex: 1, 91 | height: 120, 92 | marginHorizontal: 20, 93 | marginTop: 20 94 | } 95 | }); 96 | -------------------------------------------------------------------------------- /playground/src/examples/HandleRelayout.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { StyleSheet, View, TouchableOpacity, Text } from 'react-native'; 3 | import Interactable from 'react-native-interactable'; 4 | 5 | export default class HandleRelayout extends Component { 6 | constructor() { 7 | super(); 8 | this.state = { 9 | collapsed: false 10 | }; 11 | } 12 | render() { 13 | return ( 14 | 15 | 16 | 17 | 22 | Tap to {this.state.collapsed ? 'expand' : 'collapse'} 23 | 24 | 25 | 26 | 29 | 30 | 31 | 32 | 33 | ); 34 | } 35 | onChangeLayoutPress() { 36 | this.setState({ 37 | collapsed: !this.state.collapsed 38 | }) 39 | } 40 | } 41 | 42 | const styles = StyleSheet.create({ 43 | container: { 44 | flex: 1, 45 | justifyContent: 'center', 46 | alignItems: 'center', 47 | backgroundColor: 'white', 48 | }, 49 | card: { 50 | width: 300, 51 | height: 180, 52 | backgroundColor: '#542790', 53 | borderRadius: 8, 54 | marginVertical: 6 55 | }, 56 | label: { 57 | textAlign: 'center', 58 | fontSize: 24 59 | } 60 | }); 61 | -------------------------------------------------------------------------------- /playground/src/examples/HandleTouches.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { StyleSheet, FlatList, TouchableOpacity, Text } from 'react-native'; 3 | import Interactable from 'react-native-interactable'; 4 | 5 | export default class HandleTouches extends Component { 6 | 7 | render() { 8 | return ( 9 | item} 13 | renderItem={this.renderRow} 14 | /> 15 | ); 16 | } 17 | renderRow = (data) => { 18 | return ( 19 | 22 | 23 | 24 | Button A 25 | 26 | 27 | Button B 28 | 29 | 30 | 31 | ); 32 | } 33 | onCardPress() { 34 | alert('Card was pressed'); 35 | } 36 | onButtonPress(type) { 37 | alert(`Button ${type} was pressed`); 38 | } 39 | } 40 | 41 | const styles = StyleSheet.create({ 42 | container: { 43 | alignItems: 'center', 44 | backgroundColor: 'white' 45 | }, 46 | card: { 47 | width: 300, 48 | height: 180, 49 | backgroundColor: '#32B76C', 50 | borderRadius: 8, 51 | marginVertical: 6 52 | }, 53 | button: { 54 | width: 80, 55 | height: 40, 56 | marginLeft: 30, 57 | marginTop: 30, 58 | justifyContent: 'center', 59 | backgroundColor: '#b5d9f8' 60 | } 61 | }); 62 | -------------------------------------------------------------------------------- /playground/src/examples/IconDrawer.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { StyleSheet, View, Animated } from 'react-native'; 3 | import Interactable from 'react-native-interactable'; 4 | 5 | export default class IconDrawer extends Component { 6 | constructor(props) { 7 | super(props); 8 | this._deltaX = new Animated.Value(0); 9 | } 10 | render() { 11 | return ( 12 | 13 | 14 | 15 | 16 | 30 | 44 | 58 | 59 | 60 | 65 | 66 | 67 | 68 | 69 | 70 | ); 71 | } 72 | onDrawerSnap(event) { 73 | const snapPointId = event.nativeEvent.id; 74 | console.log(`drawer state is ${snapPointId}`); 75 | } 76 | } 77 | 78 | const styles = StyleSheet.create({ 79 | container: { 80 | flex: 1, 81 | justifyContent: 'center', 82 | backgroundColor: 'white', 83 | }, 84 | button: { 85 | width: 40, 86 | height: 40, 87 | marginRight: 25, 88 | backgroundColor: '#EE2C38' 89 | } 90 | }); 91 | -------------------------------------------------------------------------------- /playground/src/examples/MoreDrawers.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { StyleSheet, View, Text } from 'react-native'; 3 | import Interactable from 'react-native-interactable'; 4 | 5 | export default class MoreDrawers extends Component { 6 | render() { 7 | return ( 8 | 9 | 10 | 11 | 14 | 15 | Default drawer 16 | 17 | 18 | 19 | 20 | 21 | 25 | 26 | Drawer with limits 27 | 28 | 29 | 30 | 31 | 32 | 36 | 37 | Limits with bounce 38 | 39 | 40 | 41 | 42 | 43 | 47 | 48 | Drag via spring 49 | 50 | 51 | 52 | 53 | 54 | 59 | 60 | Drag with spring resistance 61 | 62 | 63 | 64 | 65 | 66 | ); 67 | } 68 | } 69 | 70 | const styles = StyleSheet.create({ 71 | container: { 72 | flex: 1, 73 | justifyContent: 'center', 74 | backgroundColor: 'white', 75 | paddingTop: 80, 76 | paddingBottom: 40 77 | }, 78 | cover: { 79 | left: 0, 80 | right: 0, 81 | height: 75, 82 | backgroundColor: '#e0e0e0', 83 | justifyContent: 'center' 84 | }, 85 | label: { 86 | textAlign: 'center', 87 | fontSize: 18 88 | } 89 | }); 90 | -------------------------------------------------------------------------------- /playground/src/examples/SideMenu.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { StyleSheet, View, TouchableOpacity, Image, Text, Button, Dimensions } from 'react-native'; 3 | import Interactable from 'react-native-interactable'; 4 | 5 | const Screen = Dimensions.get('window'); 6 | const SideMenuWidth = 280; 7 | const RemainingWidth = Screen.width - SideMenuWidth; 8 | 9 | export default class SideMenu extends Component { 10 | render() { 11 | return ( 12 | 13 | 14 | 15 | 21 | 22 | Menu 23 | alert('Button 1 pressed')}> 24 | Button 1 25 | 26 | alert('Button 2 pressed')}> 27 | Button 2 28 | 29 | alert('Button 3 pressed')}> 30 | Button 3 31 | 32 | 33 | Cancel 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | Side Menu Example 44 | 45 | 46 | 47 | Some Content Here 48 | 49 | 50 | 51 | ); 52 | } 53 | onMenuPress() { 54 | this.refs['menuInstance'].setVelocity({x: 2000}); 55 | } 56 | onClosePress() { 57 | this.refs['menuInstance'].setVelocity({x: -2000}); 58 | } 59 | } 60 | 61 | const styles = StyleSheet.create({ 62 | container: { 63 | flex: 1, 64 | alignItems: 'stretch', 65 | backgroundColor: 'white', 66 | }, 67 | sideMenuContainer: { 68 | position: 'absolute', 69 | top: 0, 70 | left: -RemainingWidth, 71 | right: 0, 72 | bottom: 0, 73 | flexDirection: 'row', 74 | zIndex: 1002 75 | }, 76 | sideMenu: { 77 | left: 0, 78 | width: Screen.width, 79 | paddingLeft: RemainingWidth, 80 | flex: 1, 81 | backgroundColor: '#e0e0e0', 82 | paddingTop: 20 83 | }, 84 | sideMenuTitle: { 85 | fontSize: 22, 86 | fontWeight: 'bold', 87 | textAlign: 'center', 88 | marginBottom: 20 89 | }, 90 | button: { 91 | color: '#542790', 92 | fontSize: 20, 93 | fontWeight: 'bold', 94 | textAlign: 'center', 95 | margin: 20 96 | }, 97 | header: { 98 | height: 60, 99 | paddingHorizontal: 20, 100 | flexDirection: 'row', 101 | backgroundColor: '#32B76C', 102 | alignItems: 'center', 103 | zIndex: 1001 104 | }, 105 | body: { 106 | flex: 1, 107 | zIndex: 1000, 108 | alignItems: 'center', 109 | justifyContent: 'center' 110 | }, 111 | menuIcon: { 112 | width: 30, 113 | height: 30 114 | }, 115 | headerTitle: { 116 | marginLeft: 24, 117 | color: 'white', 118 | fontSize: 20 119 | }, 120 | content: { 121 | fontSize: 18 122 | } 123 | }); 124 | -------------------------------------------------------------------------------- /playground/src/examples/SnapTo.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { StyleSheet, View, Button } from 'react-native'; 3 | import Interactable from 'react-native-interactable'; 4 | 5 | export default class SnapTo extends Component { 6 | constructor(props) { 7 | super(props); 8 | this.state = { 9 | snapToIndex: 0 10 | }; 11 | } 12 | render() { 13 | return ( 14 | 15 | 30 | 31 | 32 | 33 |