mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Compare commits
33
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc04bf3d96 | ||
|
|
8e34e7bd5f | ||
|
|
68f6799b7c | ||
|
|
2c130e6a9f | ||
|
|
21423bdf22 | ||
|
|
e5332504cc | ||
|
|
292f035ae8 | ||
|
|
a6c4ae0e75 | ||
|
|
e4b5e9ef85 | ||
|
|
732831dc27 | ||
|
|
b150fdfdce | ||
|
|
73f2007679 | ||
|
|
da2a8b365c | ||
|
|
143ecf4c62 | ||
|
|
c8d6cb9e49 | ||
|
|
0661c6f254 | ||
|
|
5f4a5388ac | ||
|
|
445dac2743 | ||
|
|
2d230bee78 | ||
|
|
382abb4b45 | ||
|
|
b0e4b89b58 | ||
|
|
0b8525847f | ||
|
|
09a923599e | ||
|
|
e7b7951c96 | ||
|
|
a91d0d8c35 | ||
|
|
47206fdd3c | ||
|
|
b02efd9e96 | ||
|
|
31894136f4 | ||
|
|
9b6639ad12 | ||
|
|
11f838f349 | ||
|
|
34787cc0eb | ||
|
|
d40bea925f | ||
|
|
692d3f72fb |
@@ -1,500 +0,0 @@
|
||||
aliases:
|
||||
- &restore-node-cache
|
||||
keys:
|
||||
- v1-dependencies-{{ arch }}-{{ checksum "package.json" }}
|
||||
# Fallback in case checksum fails
|
||||
- v1-dependencies-{{ arch }}-
|
||||
|
||||
- &save-node-cache
|
||||
paths:
|
||||
- node_modules
|
||||
key: v1-dependencies-{{ arch }}-{{ checksum "package.json" }}
|
||||
|
||||
- &restore-cache-analysis
|
||||
keys:
|
||||
- v1-analysis-dependencies-{{ arch }}-{{ checksum "package.json" }}{{ checksum "danger/package.json" }}
|
||||
# Fallback in case checksum fails
|
||||
- v1-analysis-dependencies-{{ arch }}-
|
||||
- &save-cache-analysis
|
||||
paths:
|
||||
- danger/node_modules
|
||||
- node_modules
|
||||
key: v1-analysis-dependencies-{{ arch }}-{{ checksum "package.json" }}{{ checksum "danger/package.json" }}
|
||||
|
||||
- &restore-cache-android-packages
|
||||
keys:
|
||||
- v2-android-sdkmanager-packages-{{ arch }}-{{ checksum "scripts/circle-ci-android-setup.sh" }}
|
||||
# Fallback in case checksum fails
|
||||
- v2-android-sdkmanager-packages-{{ arch }}-
|
||||
- &save-cache-android-packages
|
||||
paths:
|
||||
- /opt/android/sdk
|
||||
key: v2-android-sdkmanager-packages-{{ arch }}-{{ checksum "scripts/circle-ci-android-setup.sh" }}
|
||||
|
||||
- &restore-cache-ndk
|
||||
keys:
|
||||
- v1-android-ndk-{{ arch }}-r10e-32-64
|
||||
|
||||
- &install-ndk
|
||||
|
|
||||
source scripts/circle-ci-android-setup.sh && getAndroidNDK
|
||||
|
||||
- &save-cache-ndk
|
||||
paths:
|
||||
- /opt/ndk
|
||||
key: v1-android-ndk-{{ arch }}-r10e-32-64
|
||||
|
||||
- &restore-cache-buck
|
||||
keys:
|
||||
- v2-buck-{{ arch }}-v2017.09.04.02
|
||||
- &save-cache-buck
|
||||
paths:
|
||||
- ~/buck
|
||||
key: v2-buck-{{ arch }}-v2017.09.04.02
|
||||
|
||||
- &restore-cache-watchman
|
||||
keys:
|
||||
- v1-watchman-{{ arch }}-v4.9.0
|
||||
- &save-cache-watchman
|
||||
paths:
|
||||
- ~/watchman
|
||||
key: v1-watchman-{{ arch }}-v4.9.0
|
||||
|
||||
- &install-node-dependencies
|
||||
|
|
||||
npm install --no-package-lock --no-spin --no-progress
|
||||
|
||||
- &install-buck
|
||||
|
|
||||
if [[ ! -e ~/buck ]]; then
|
||||
git clone https://github.com/facebook/buck.git ~/buck --branch v2017.09.04.02 --depth=1
|
||||
fi
|
||||
cd ~/buck && ant
|
||||
buck --version
|
||||
|
||||
- &install-node
|
||||
|
|
||||
curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash -
|
||||
sudo apt-get install -y nodejs
|
||||
|
||||
- &run-node-tests
|
||||
|
|
||||
npm test -- --maxWorkers=2
|
||||
|
||||
- &run-lint-checks
|
||||
|
|
||||
npm run lint
|
||||
|
||||
- &run-flow-checks
|
||||
|
|
||||
npm run flow -- check
|
||||
|
||||
|
||||
- &filter-only-master-stable
|
||||
branches:
|
||||
only:
|
||||
- /.*-stable/
|
||||
- master
|
||||
|
||||
- &filter-only-stable
|
||||
branches:
|
||||
only:
|
||||
- /.*-stable/
|
||||
|
||||
- &filter-ignore-gh-pages
|
||||
branches:
|
||||
ignore: gh-pages
|
||||
|
||||
- &filter-ignore-master-stable
|
||||
branches:
|
||||
ignore:
|
||||
- master
|
||||
- /.*-stable/
|
||||
- gh-pages
|
||||
|
||||
- &create-ndk-directory
|
||||
|
|
||||
if [[ ! -e /opt/ndk ]]; then
|
||||
sudo mkdir /opt/ndk
|
||||
fi
|
||||
sudo chown ${USER:=$(/usr/bin/id -run)}:$USER /opt/ndk
|
||||
|
||||
# CircleCI does not support interpolating env variables in the environment
|
||||
# https://circleci.com/docs/2.0/env-vars/#interpolating-environment-variables-to-set-other-environment-variables
|
||||
- &configure-android-path
|
||||
|
|
||||
echo 'export PATH=${ANDROID_NDK}:~/react-native/gradle-2.9/bin:~/buck/bin:$PATH' >> $BASH_ENV
|
||||
source $BASH_ENV
|
||||
|
||||
- &install-android-packages
|
||||
|
|
||||
source scripts/circle-ci-android-setup.sh && getAndroidSDK
|
||||
|
||||
- &install-build-dependencies
|
||||
|
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install ant autoconf automake g++ gcc libqt5widgets5 lib32z1 lib32stdc++6 make maven python-dev python3-dev qml-module-qtquick-controls qtdeclarative5-dev file -y
|
||||
|
||||
- &build-android-app
|
||||
name: Build Android App
|
||||
command: |
|
||||
buck build ReactAndroid/src/main/java/com/facebook/react
|
||||
buck build ReactAndroid/src/main/java/com/facebook/react/shell
|
||||
|
||||
- &wait-for-avd
|
||||
name: Wait for Android Virtual Device
|
||||
command: source scripts/circle-ci-android-setup.sh && waitForAVD
|
||||
|
||||
- &check-js-bundle
|
||||
name: Check for JavaScript Bundle
|
||||
command: |
|
||||
if [[ ! -e ReactAndroid/src/androidTest/assets/AndroidTestBundle.js ]]; then
|
||||
echo "JavaScript bundle missing, verify build-js-bundle step"; exit 1;
|
||||
else
|
||||
echo "JavaScript bundle found.";
|
||||
fi
|
||||
|
||||
- &compile-native-libs
|
||||
name: Compile Native Libs for Unit and Integration Tests
|
||||
command: ./gradlew :ReactAndroid:packageReactNdkLibsForBuck -Pjobs=$BUILD_THREADS -Pcom.android.build.threadPoolSize=1
|
||||
no_output_timeout: 6m
|
||||
|
||||
- &run-android-unit-tests
|
||||
name: Unit Tests
|
||||
command: buck test ReactAndroid/src/test/... --config build.threads=$BUILD_THREADS
|
||||
|
||||
- &run-android-integration-tests
|
||||
name: Build and Install Test APK
|
||||
command: source scripts/circle-ci-android-setup.sh && NO_BUCKD=1 retry3 buck install ReactAndroid/src/androidTest/buck-runner:instrumentation-tests --config build.threads=$BUILD_THREADS
|
||||
|
||||
- &collect-android-test-results
|
||||
name: Collect Test Results
|
||||
command: |
|
||||
mkdir -p ~/junit/
|
||||
find . -type f -regex ".*/build/test-results/debug/.*xml" -exec cp {} ~/junit/ \;
|
||||
find . -type f -regex ".*/outputs/androidTest-results/connected/.*xml" -exec cp {} ~/junit/ \;
|
||||
when: always
|
||||
|
||||
defaults: &defaults
|
||||
working_directory: ~/react-native
|
||||
|
||||
android_defaults: &android_defaults
|
||||
<<: *defaults
|
||||
docker:
|
||||
- image: circleci/android:api-26-alpha
|
||||
resource_class: "large"
|
||||
environment:
|
||||
- TERM: "dumb"
|
||||
- ADB_INSTALL_TIMEOUT: 10
|
||||
- _JAVA_OPTIONS: "-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap"
|
||||
- GRADLE_OPTS: '-Dorg.gradle.daemon=false -Dorg.gradle.jvmargs="-XX:+HeapDumpOnOutOfMemoryError"'
|
||||
- ANDROID_NDK: '/opt/ndk/android-ndk-r10e'
|
||||
- BUILD_THREADS: 2
|
||||
|
||||
version: 2
|
||||
jobs:
|
||||
# Runs JavaScript lint and flow checks
|
||||
run-js-checks:
|
||||
<<: *defaults
|
||||
docker:
|
||||
- image: circleci/node:8
|
||||
steps:
|
||||
- checkout
|
||||
- restore-cache: *restore-node-cache
|
||||
- run: *install-node-dependencies
|
||||
- save-cache: *save-node-cache
|
||||
- run: *run-lint-checks
|
||||
- run: *run-flow-checks
|
||||
|
||||
# Runs JavaScript tests on Node 8
|
||||
test-js-node-8:
|
||||
<<: *defaults
|
||||
docker:
|
||||
- image: circleci/node:8
|
||||
steps:
|
||||
- checkout
|
||||
- restore-cache: *restore-node-cache
|
||||
- run: *install-node-dependencies
|
||||
- save-cache: *save-node-cache
|
||||
- run: *run-node-tests
|
||||
|
||||
# Runs JavaScript tests on Node 6
|
||||
test-js-node-6:
|
||||
<<: *defaults
|
||||
docker:
|
||||
- image: circleci/node:6
|
||||
steps:
|
||||
- checkout
|
||||
- restore-cache: *restore-node-cache
|
||||
- run: *install-node-dependencies
|
||||
- save-cache: *save-node-cache
|
||||
- run: *run-node-tests
|
||||
|
||||
# Runs unit tests on iOS devices
|
||||
test-objc-ios:
|
||||
<<: *defaults
|
||||
macos:
|
||||
xcode: "9.0"
|
||||
dependencies:
|
||||
pre:
|
||||
- xcrun instruments -w "iPhone 5s (10.3.1)" || true
|
||||
steps:
|
||||
- checkout
|
||||
- restore-cache: *restore-node-cache
|
||||
- run: *install-node-dependencies
|
||||
- save-cache: *save-node-cache
|
||||
- run: ./scripts/objc-test-ios.sh
|
||||
|
||||
# Runs unit tests on tvOS devices
|
||||
test-objc-tvos:
|
||||
<<: *defaults
|
||||
macos:
|
||||
xcode: "9.0"
|
||||
dependencies:
|
||||
pre:
|
||||
- xcrun instruments -w "Apple TV 1080p (10.0)" || true
|
||||
steps:
|
||||
- checkout
|
||||
- restore-cache: *restore-node-cache
|
||||
- run: *install-node-dependencies
|
||||
- save-cache: *save-node-cache
|
||||
- run: ./scripts/objc-test-tvos.sh
|
||||
|
||||
# Runs end to end tests
|
||||
test-objc-e2e:
|
||||
<<: *defaults
|
||||
macos:
|
||||
xcode: "9.0"
|
||||
dependencies:
|
||||
pre:
|
||||
- xcrun instruments -w "iPhone 5s (10.3.1)" || true
|
||||
steps:
|
||||
- checkout
|
||||
- restore-cache: *restore-node-cache
|
||||
- run: *install-node-dependencies
|
||||
- save-cache: *save-node-cache
|
||||
- run: node ./scripts/run-ci-e2e-tests.js --ios --js --retries 3;
|
||||
|
||||
# Checks podspec
|
||||
test-podspec:
|
||||
<<: *defaults
|
||||
macos:
|
||||
xcode: "9.0"
|
||||
steps:
|
||||
- checkout
|
||||
- restore-cache: *restore-node-cache
|
||||
- run: *install-node-dependencies
|
||||
- save-cache: *save-node-cache
|
||||
- run: ./scripts/process-podspecs.sh
|
||||
|
||||
# Publishes new version onto npm
|
||||
deploy:
|
||||
<<: *android_defaults
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
# Configure Android dependencies
|
||||
- run: *configure-android-path
|
||||
- run: *install-build-dependencies
|
||||
- restore-cache: *restore-cache-android-packages
|
||||
- run: *install-android-packages
|
||||
- save-cache: *save-cache-android-packages
|
||||
- run: *create-ndk-directory
|
||||
- restore-cache: *restore-cache-ndk
|
||||
- run: *install-ndk
|
||||
- save-cache: *save-cache-ndk
|
||||
- restore-cache: *restore-cache-buck
|
||||
- run: *install-buck
|
||||
- save-cache: *save-cache-buck
|
||||
- run: *install-node
|
||||
- restore-cache: *restore-node-cache
|
||||
- run: *install-node-dependencies
|
||||
- save-cache: *save-node-cache
|
||||
- run: buck fetch ReactAndroid/src/test/java/com/facebook/react/modules
|
||||
- run: buck fetch ReactAndroid/src/main/java/com/facebook/react
|
||||
- run: buck fetch ReactAndroid/src/main/java/com/facebook/react/shell
|
||||
- run: buck fetch ReactAndroid/src/test/...
|
||||
- run: buck fetch ReactAndroid/src/androidTest/...
|
||||
- run: ./gradlew :ReactAndroid:downloadBoost :ReactAndroid:downloadDoubleConversion :ReactAndroid:downloadFolly :ReactAndroid:downloadGlog :ReactAndroid:downloadJSCHeaders
|
||||
|
||||
- run:
|
||||
name: Publish React Native Package
|
||||
command: |
|
||||
if [ -z "$CIRCLE_PULL_REQUEST" ]; then
|
||||
echo "//registry.npmjs.org/:_authToken=${CIRCLE_NPM_TOKEN}" > ~/.npmrc
|
||||
git config --global user.email "reactjs-bot@users.noreply.github.com"
|
||||
git config --global user.name "npm Deployment Script"
|
||||
echo "machine github.com login reactjs-bot password $GITHUB_TOKEN" > ~/.netrc
|
||||
node ./scripts/publish-npm.js
|
||||
else
|
||||
echo "Skipping deploy."
|
||||
fi
|
||||
|
||||
# Build JavaScript bundle for Android tests
|
||||
build-js-bundle:
|
||||
<<: *defaults
|
||||
docker:
|
||||
- image: circleci/node:8
|
||||
steps:
|
||||
- checkout
|
||||
- restore-cache: *restore-node-cache
|
||||
- run: *install-node-dependencies
|
||||
- save-cache: *save-node-cache
|
||||
- run:
|
||||
name: Build JavaScript Bundle
|
||||
command: node local-cli/cli.js bundle --max-workers 2 --platform android --dev true --entry-file ReactAndroid/src/androidTest/js/TestBundle.js --bundle-output ReactAndroid/src/androidTest/assets/AndroidTestBundle.js
|
||||
- persist_to_workspace:
|
||||
root: ReactAndroid/src/androidTest/assets/
|
||||
paths:
|
||||
- AndroidTestBundle.js
|
||||
- store_artifacts:
|
||||
path: ReactAndroid/src/androidTest/assets/AndroidTestBundle.js
|
||||
|
||||
# Runs unit tests tests on Android
|
||||
test-android:
|
||||
<<: *android_defaults
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
# Configure Android dependencies
|
||||
- run: *configure-android-path
|
||||
- run: *install-build-dependencies
|
||||
- restore-cache: *restore-cache-android-packages
|
||||
- run: *install-android-packages
|
||||
- save-cache: *save-cache-android-packages
|
||||
|
||||
# Starting emulator in advance as it takes some time to boot.
|
||||
- run:
|
||||
name: Create Android Virtual Device
|
||||
command: source scripts/circle-ci-android-setup.sh && createAVD
|
||||
- run:
|
||||
name: Launch Android Virtual Device in Background
|
||||
command: source scripts/circle-ci-android-setup.sh && launchAVD
|
||||
background: true
|
||||
|
||||
# Keep configuring Android dependencies while AVD boots up
|
||||
- run: *create-ndk-directory
|
||||
- restore-cache: *restore-cache-ndk
|
||||
- run: *install-ndk
|
||||
- save-cache: *save-cache-ndk
|
||||
|
||||
- restore-cache: *restore-cache-buck
|
||||
- run: *install-buck
|
||||
- save-cache: *save-cache-buck
|
||||
|
||||
- run: *install-node
|
||||
|
||||
- restore-cache: *restore-node-cache
|
||||
- run: *install-node-dependencies
|
||||
- save-cache: *save-node-cache
|
||||
|
||||
- run: buck fetch ReactAndroid/src/test/java/com/facebook/react/modules
|
||||
- run: buck fetch ReactAndroid/src/main/java/com/facebook/react
|
||||
- run: buck fetch ReactAndroid/src/main/java/com/facebook/react/shell
|
||||
- run: buck fetch ReactAndroid/src/test/...
|
||||
- run: buck fetch ReactAndroid/src/androidTest/...
|
||||
- run: ./gradlew :ReactAndroid:downloadBoost :ReactAndroid:downloadDoubleConversion :ReactAndroid:downloadFolly :ReactAndroid:downloadGlog :ReactAndroid:downloadJSCHeaders
|
||||
|
||||
- run: *build-android-app
|
||||
- run: *compile-native-libs
|
||||
|
||||
# The JavaScript Bundle is built as part of the build-js-bundle workflow,
|
||||
# and is required for instrumentation tests.
|
||||
- attach_workspace:
|
||||
at: ReactAndroid/src/androidTest/assets/
|
||||
- run: *check-js-bundle
|
||||
|
||||
# Wait for AVD to finish booting before running tests
|
||||
- run: *wait-for-avd
|
||||
|
||||
# Tests
|
||||
- run: *run-android-unit-tests
|
||||
- run: *run-android-integration-tests
|
||||
|
||||
# post (always runs)
|
||||
- run: *collect-android-test-results
|
||||
- store_test_results:
|
||||
path: ~/junit
|
||||
- store_artifacts:
|
||||
path: ~/junit
|
||||
|
||||
analyze-pull-request:
|
||||
<<: *defaults
|
||||
docker:
|
||||
- image: circleci/node:8
|
||||
steps:
|
||||
- checkout
|
||||
- restore-cache: *restore-cache-analysis
|
||||
- run: *install-node-dependencies
|
||||
- run:
|
||||
name: Install Dependencies
|
||||
command: |
|
||||
if [ -n "$CIRCLE_PULL_REQUEST" ]; then
|
||||
npm install github@0.2.4
|
||||
cd danger
|
||||
npm install --no-package-lock --no-spin --no-progress
|
||||
else
|
||||
echo "Skipping dependency installation."
|
||||
fi
|
||||
- save-cache: *save-cache-analysis
|
||||
- run:
|
||||
name: Analyze Pull Request
|
||||
command: |
|
||||
if [ -n "$CIRCLE_PULL_REQUEST" ]; then
|
||||
cd danger && DANGER_GITHUB_API_TOKEN="e622517d9f1136ea8900""07c6373666312cdfaa69" npm run danger
|
||||
else
|
||||
echo "Skipping pull request analysis."
|
||||
fi
|
||||
when: always
|
||||
- run:
|
||||
name: Analyze Code
|
||||
command: |
|
||||
if [ -n "$CIRCLE_PULL_REQUEST" ]; then
|
||||
cat <(echo eslint; npm run lint --silent -- --format=json; echo flow; npm run flow --silent -- check --json) | GITHUB_TOKEN="af6ef0d15709bc91d""06a6217a5a826a226fb57b7" CI_USER=$CIRCLE_PROJECT_USERNAME CI_REPO=$CIRCLE_PROJECT_REPONAME PULL_REQUEST_NUMBER=$CIRCLE_PR_NUMBER node bots/code-analysis-bot.js
|
||||
else
|
||||
echo "Skipping code analysis."
|
||||
fi
|
||||
when: always
|
||||
|
||||
# Workflows enables us to run multiple jobs in parallel
|
||||
workflows:
|
||||
version: 2
|
||||
|
||||
build:
|
||||
jobs:
|
||||
|
||||
# Run lint and flow checks
|
||||
- run-js-checks:
|
||||
filters: *filter-ignore-gh-pages
|
||||
|
||||
# Test JavaScript on Node 8 and 6
|
||||
- test-js-node-8:
|
||||
filters: *filter-ignore-gh-pages
|
||||
- test-js-node-6:
|
||||
filters: *filter-ignore-gh-pages
|
||||
|
||||
# Test Android
|
||||
- build-js-bundle:
|
||||
filters: *filter-ignore-gh-pages
|
||||
- test-android:
|
||||
requires:
|
||||
- build-js-bundle
|
||||
|
||||
# Test iOS & tvOS
|
||||
- test-objc-ios:
|
||||
filters: *filter-ignore-gh-pages
|
||||
- test-objc-tvos:
|
||||
filters: *filter-ignore-gh-pages
|
||||
- test-objc-e2e:
|
||||
filters: *filter-ignore-gh-pages
|
||||
|
||||
# If we are on a stable branch, deploy to `npm`
|
||||
- hold:
|
||||
type: approval
|
||||
- deploy:
|
||||
filters: *filter-only-stable
|
||||
requires:
|
||||
- hold
|
||||
|
||||
- analyze-pull-request:
|
||||
filters: *filter-ignore-master-stable
|
||||
+5
-1
@@ -4,7 +4,11 @@
|
||||
**/main.js
|
||||
Libraries/vendor/**/*
|
||||
Libraries/Renderer/*
|
||||
website/node_modules
|
||||
pr-inactivity-bookmarklet.js
|
||||
question-bookmarklet.js
|
||||
flow/
|
||||
danger/
|
||||
website/core/metadata.js
|
||||
website/core/metadata-blog.js
|
||||
website/src/react-native/docs/
|
||||
website/src/react-native/blog/
|
||||
|
||||
@@ -9,15 +9,13 @@
|
||||
|
||||
"env": {
|
||||
"es6": true,
|
||||
"jest": true,
|
||||
"jasmine": true,
|
||||
},
|
||||
|
||||
"plugins": [
|
||||
"eslint-comments",
|
||||
"flowtype",
|
||||
"prettier",
|
||||
"react",
|
||||
"jest"
|
||||
"react"
|
||||
],
|
||||
|
||||
// Map from global var to bool specifying if it can be redefined
|
||||
@@ -59,26 +57,21 @@
|
||||
},
|
||||
|
||||
"rules": {
|
||||
// Flow Plugin
|
||||
// The following rules are made available via `eslint-plugin-flowtype`
|
||||
"flowtype/define-flow-type": 1,
|
||||
"flowtype/use-flow-type": 1,
|
||||
|
||||
// General
|
||||
|
||||
// This must be disallowed in this repo because the minimum supported
|
||||
// version of node is 4 which doesn't support trailing commas.
|
||||
// Once the minimum supported version is 8 or greater this can be changed
|
||||
"comma-dangle": [2, { // disallow trailing commas in object literals
|
||||
"arrays": "ignore",
|
||||
"objects": "ignore",
|
||||
"imports": "ignore",
|
||||
"exports": "ignore",
|
||||
"functions": "never"
|
||||
}],
|
||||
|
||||
"comma-dangle": 0, // disallow trailing commas in object literals
|
||||
"no-cond-assign": 1, // disallow assignment in conditional expressions
|
||||
"no-console": 0, // disallow use of console (off by default in the node environment)
|
||||
"no-const-assign": 2, // disallow assignment to const-declared variables
|
||||
"no-constant-condition": 0, // disallow use of constant expressions in conditions
|
||||
"no-control-regex": 1, // disallow control characters in regular expressions
|
||||
"no-debugger": 1, // disallow use of debugger
|
||||
"no-dupe-keys": 2, // disallow duplicate keys when creating object literals
|
||||
"no-dupe-keys": 1, // disallow duplicate keys when creating object literals
|
||||
"no-empty": 0, // disallow empty statements
|
||||
"no-ex-assign": 1, // disallow assigning to the exception in a catch block
|
||||
"no-extra-boolean-cast": 1, // disallow double-negation boolean casts in a boolean context
|
||||
@@ -92,7 +85,7 @@
|
||||
"no-regex-spaces": 1, // disallow multiple spaces in a regular expression literal
|
||||
"no-reserved-keys": 0, // disallow reserved words being used as object literal keys (off by default)
|
||||
"no-sparse-arrays": 1, // disallow sparse arrays
|
||||
"no-unreachable": 2, // disallow unreachable statements after a return, throw, continue, or break statement
|
||||
"no-unreachable": 1, // disallow unreachable statements after a return, throw, continue, or break statement
|
||||
"use-isnan": 1, // disallow comparisons with the value NaN
|
||||
"valid-jsdoc": 0, // Ensure JSDoc comments are valid (off by default)
|
||||
"valid-typeof": 1, // Ensure that the results of typeof are compared against a valid string
|
||||
@@ -113,7 +106,7 @@
|
||||
"no-div-regex": 1, // disallow division operators explicitly at beginning of regular expression (off by default)
|
||||
"no-else-return": 0, // disallow else after a return in an if (off by default)
|
||||
"no-eq-null": 0, // disallow comparisons to null without a type-checking operator (off by default)
|
||||
"no-eval": 2, // disallow use of eval()
|
||||
"no-eval": 1, // disallow use of eval()
|
||||
"no-extend-native": 1, // disallow adding to native types
|
||||
"no-extra-bind": 1, // disallow unnecessary function binding
|
||||
"no-fallthrough": 1, // disallow fallthrough of case statements
|
||||
@@ -126,7 +119,7 @@
|
||||
"no-multi-str": 0, // disallow use of multiline strings
|
||||
"no-native-reassign": 0, // disallow reassignments of native objects
|
||||
"no-new": 1, // disallow use of new operator when not part of the assignment or comparison
|
||||
"no-new-func": 2, // disallow use of new operator for Function object
|
||||
"no-new-func": 1, // disallow use of new operator for Function object
|
||||
"no-new-wrappers": 1, // disallows creating new instances of String,Number, and Boolean
|
||||
"no-octal": 1, // disallow use of octal literals
|
||||
"no-octal-escape": 1, // disallow use of octal escape sequences in string literals, such as var foo = "Copyright \251";
|
||||
@@ -171,18 +164,6 @@
|
||||
"no-restricted-modules": 1, // restrict usage of specified node modules (off by default)
|
||||
"no-sync": 0, // disallow use of synchronous methods (off by default)
|
||||
|
||||
// ESLint Comments Plugin
|
||||
// The following rules are made available via `eslint-plugin-eslint-comments`
|
||||
'eslint-comments/no-aggregating-enable': 1, // disallows eslint-enable comments for multiple eslint-disable comments
|
||||
'eslint-comments/no-unlimited-disable': 1, // disallows eslint-disable comments without rule names
|
||||
'eslint-comments/no-unused-disable': 1, // disallow disables that don't cover any errors
|
||||
'eslint-comments/no-unused-enable': 1, // // disallow enables that don't enable anything or enable rules that weren't disabled
|
||||
|
||||
// Flow Plugin
|
||||
// The following rules are made available via `eslint-plugin-flowtype`
|
||||
"flowtype/define-flow-type": 1,
|
||||
"flowtype/use-flow-type": 1,
|
||||
|
||||
// Prettier Plugin
|
||||
// https://github.com/prettier/eslint-plugin-prettier
|
||||
"prettier/prettier": [2, "fb", "@format"],
|
||||
@@ -192,12 +173,12 @@
|
||||
|
||||
"key-spacing": 0,
|
||||
"keyword-spacing": 1, // enforce spacing before and after keywords
|
||||
"jsx-quotes": [1, "prefer-double"], // enforces the usage of double quotes for all JSX attribute values which doesn’t contain a double quote
|
||||
"jsx-quotes": [1, "prefer-double"],
|
||||
"comma-spacing": 0,
|
||||
"no-multi-spaces": 0,
|
||||
"brace-style": 0, // enforce one true brace style (off by default)
|
||||
"camelcase": 0, // require camel case names
|
||||
"consistent-this": 1, // enforces consistent naming when capturing the current execution context (off by default)
|
||||
"consistent-this": [1, "self"], // enforces consistent naming when capturing the current execution context (off by default)
|
||||
"eol-last": 1, // enforce newline at the end of file, with no multiple empty lines
|
||||
"func-names": 0, // require function expressions to have a name (off by default)
|
||||
"func-style": 0, // enforces use of function declarations or expressions (off by default)
|
||||
@@ -205,7 +186,6 @@
|
||||
"new-parens": 1, // disallow the omission of parentheses when invoking a constructor with no arguments
|
||||
"no-nested-ternary": 0, // disallow nested ternary expressions (off by default)
|
||||
"no-array-constructor": 1, // disallow use of the Array constructor
|
||||
'no-empty-character-class': 1, // disallow the use of empty character classes in regular expressions
|
||||
"no-lonely-if": 0, // disallow if as the only statement in an else block (off by default)
|
||||
"no-new-object": 1, // disallow use of the Object constructor
|
||||
"no-spaced-func": 1, // disallow space between function identifier and application
|
||||
@@ -240,9 +220,8 @@
|
||||
|
||||
"react/display-name": 0,
|
||||
"react/jsx-boolean-value": 0,
|
||||
"react/jsx-no-comment-textnodes": 1,
|
||||
"react/jsx-no-duplicate-props": 2,
|
||||
"react/jsx-no-undef": 2,
|
||||
"react/jsx-no-undef": 1,
|
||||
"react/jsx-sort-props": 0,
|
||||
"react/jsx-uses-react": 1,
|
||||
"react/jsx-uses-vars": 1,
|
||||
@@ -254,13 +233,6 @@
|
||||
"react/prop-types": 0,
|
||||
"react/react-in-jsx-scope": 1,
|
||||
"react/self-closing-comp": 1,
|
||||
"react/wrap-multilines": 0,
|
||||
|
||||
// Jest Plugin
|
||||
// The following rules are made available via `eslint-plugin-jest`.
|
||||
"jest/no-disabled-tests": 1,
|
||||
"jest/no-focused-tests": 1,
|
||||
"jest/no-identical-title": 1,
|
||||
"jest/valid-expect": 1,
|
||||
"react/wrap-multilines": 0
|
||||
}
|
||||
}
|
||||
|
||||
+8
-14
@@ -5,8 +5,8 @@
|
||||
; Ignore templates for 'react-native init'
|
||||
.*/local-cli/templates/.*
|
||||
|
||||
; Ignore the Dangerfile
|
||||
<PROJECT_ROOT>/danger/dangerfile.js
|
||||
; Ignore the website subdir
|
||||
<PROJECT_ROOT>/website/.*
|
||||
|
||||
; Ignore "BUCK" generated dirs
|
||||
<PROJECT_ROOT>/\.buckd/
|
||||
@@ -19,18 +19,11 @@
|
||||
; "node_modules/react-native" but in the source repo it is in the root
|
||||
.*/Libraries/react-native/React.js
|
||||
|
||||
; Ignore polyfills
|
||||
.*/Libraries/polyfills/.*
|
||||
|
||||
; Ignore metro
|
||||
.*/node_modules/metro/.*
|
||||
|
||||
[include]
|
||||
|
||||
[libs]
|
||||
Libraries/react-native/react-native-interface.js
|
||||
flow/
|
||||
flow-github/
|
||||
|
||||
[options]
|
||||
emoji=true
|
||||
@@ -43,13 +36,14 @@ module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|we
|
||||
|
||||
suppress_type=$FlowIssue
|
||||
suppress_type=$FlowFixMe
|
||||
suppress_type=$FlowFixMeProps
|
||||
suppress_type=$FlowFixMeState
|
||||
suppress_type=$FixMe
|
||||
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(<VERSION>\\)? *\\(site=[a-z,_]*[react_native_oss|react_native_fb][a-z,_]*\\)?)\\)
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(<VERSION>\\)? *\\(site=[a-z,_]*[react_native_oss|react_native_fb][a-z,_]*\\)?)\\)?:? #[0-9]+
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(4[0-9]\\|[1-3][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native_oss[a-z,_]*\\)?)\\)
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(4[0-9]\\|[1-3][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native_oss[a-z,_]*\\)?)\\)?:? #[0-9]+
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError
|
||||
|
||||
unsafe.enable_getters_and_setters=true
|
||||
|
||||
[version]
|
||||
^0.63.0
|
||||
^0.49.1
|
||||
|
||||
+6
-11
@@ -1,14 +1,9 @@
|
||||
docs/* @hramos
|
||||
blog/* @hramos
|
||||
Libraries/Animated/* @janicduplessis
|
||||
Libraries/NativeAnimation/* @janicduplessis
|
||||
Libraries/Image/* @shergin
|
||||
Libraries/Text/* @shergin
|
||||
React/Base/* @shergin
|
||||
React/Views/* @shergin
|
||||
React/Modules/* @shergin
|
||||
React/CxxBridge/* @mhorowitz
|
||||
ReactAndroid/src/main/java/com/facebook/react/animated/* @janicduplessis
|
||||
**/*.md @hramos
|
||||
package.json @hramos
|
||||
local-cli/core/* @grabbou @kureev
|
||||
local-cli/link/* @grabbou @kureev
|
||||
local-cli/unlink/* @grabbou @kureev
|
||||
website/* @hramos
|
||||
website/showcase.json @hramos
|
||||
package.json @hramos @ericnakagawa
|
||||
website/package.json @hramos @ericnakagawa
|
||||
|
||||
+26
-24
@@ -16,42 +16,44 @@
|
||||
If you answered "No":
|
||||
|
||||
We use GitHub Issues exclusively for tracking bugs in React Native. If you're looking for help,
|
||||
the Community page at http://facebook.github.io/react-native/help.html list various resources
|
||||
that should help you get started.
|
||||
check out the How to Get In Touch section of the following guide:
|
||||
https://github.com/facebook/react-native/blob/master/CONTRIBUTING.md#how-to-get-in-touch
|
||||
|
||||
Now scroll below!
|
||||
-->
|
||||
|
||||
### Have you read the [Contributing Guidelines](https://facebook.github.io/react-native/docs/contributing.html)?
|
||||
|
||||
### Have you read the Bugs section of the Contributing to React Native Guide?
|
||||
|
||||
(Write your answer here.)
|
||||
|
||||
<!--
|
||||
Please read through the bug reporting guidelines thoroughly:
|
||||
https://github.com/facebook/react-native/blob/master/CONTRIBUTING.md#bugs
|
||||
-->
|
||||
|
||||
### Environment
|
||||
|
||||
<!--
|
||||
In your terminal run `react-native info` and paste its contents here. Next, specify your target platform, like this:
|
||||
|
||||
Environment:
|
||||
OS: macOS Sierra 10.12.6
|
||||
Node: 8.4.0
|
||||
Yarn: 0.27.5
|
||||
npm: 5.4.0
|
||||
Watchman: 4.7.0
|
||||
Xcode: Xcode 8.3.3 Build version 8E3004b
|
||||
Android Studio: 2.3 AI-162.4069837
|
||||
|
||||
Packages: (wanted => installed)
|
||||
react-native: 0.48.1 => 0.48.1
|
||||
react: 16.0.0-alpha.12 => 16.0.0-alpha.12
|
||||
|
||||
Target Platform: iOS (10.3)
|
||||
|
||||
***Make sure you are on v0.48.0 or greater of react-native, otherwise you may get this error:
|
||||
|
||||
Unrecognized command 'info'
|
||||
Run react-native --help to see list of all available commands
|
||||
Please fill in all the relevant fields by running these commands in terminal.
|
||||
-->
|
||||
|
||||
1. `react-native -v`:
|
||||
2. `node -v`:
|
||||
3. `npm -v`:
|
||||
4. `yarn --version` (if you use Yarn):
|
||||
|
||||
Then, specify:
|
||||
|
||||
<!-- (What platform are you building for? Choose any from iOS, Android, AppleTV.) -->
|
||||
- Target Platform:
|
||||
|
||||
<!-- Which operating system are you using? Specify macOS, Windows, or Linux, along with specific release versions -->
|
||||
- Development Operating System:
|
||||
|
||||
<!-- Include any additional relevant information. Are you using Xcode or Android Studio to build native code? Is the issue specific to a particular iOS or Android SDK? -->
|
||||
- Build tools:
|
||||
|
||||
### Steps to Reproduce
|
||||
|
||||
<!--
|
||||
|
||||
@@ -1,50 +1,9 @@
|
||||
<!--
|
||||
Thank you for sending the PR! We appreciate you spending the time to work on these changes.
|
||||
Thank you for sending the PR!
|
||||
|
||||
Help us understand your motivation by explaining why you decided to make this change.
|
||||
If you changed any code, please provide us with clear instructions on how you verified your changes work. In other words, a test plan is *required*. Bonus points for screenshots and videos!
|
||||
|
||||
You can learn more about contributing to React Native here: http://facebook.github.io/react-native/docs/contributing.html
|
||||
Please read the Contribution Guidelines at https://github.com/facebook/react-native/blob/master/CONTRIBUTING.md to learn more about contributing to React Native.
|
||||
|
||||
Happy contributing!
|
||||
|
||||
-->
|
||||
|
||||
## Motivation
|
||||
|
||||
(Write your motivation here.)
|
||||
|
||||
## Test Plan
|
||||
|
||||
(Write your test plan here. If you changed any code, please provide us with clear instructions on how you verified your changes work. Bonus points for screenshots and videos!)
|
||||
|
||||
## Related PRs
|
||||
|
||||
(If this PR adds or changes functionality, please take some time to update the docs at https://github.com/facebook/react-native-website, and link to your PR here.)
|
||||
|
||||
## Release Notes
|
||||
<!--
|
||||
Help reviewers and the release process by writing your own release notes
|
||||
|
||||
**INTERNAL and MINOR tagged notes will not be included in the next version's final release notes.**
|
||||
|
||||
CATEGORY
|
||||
[----------] TYPE
|
||||
[ CLI ] [-------------] LOCATION
|
||||
[ DOCS ] [ BREAKING ] [-------------]
|
||||
[ GENERAL ] [ BUGFIX ] [-{Component}-]
|
||||
[ INTERNAL ] [ ENHANCEMENT ] [ {File} ]
|
||||
[ IOS ] [ FEATURE ] [ {Directory} ] |-----------|
|
||||
[ ANDROID ] [ MINOR ] [ {Framework} ] - | {Message} |
|
||||
[----------] [-------------] [-------------] |-----------|
|
||||
|
||||
[CATEGORY] [TYPE] [LOCATION] - MESSAGE
|
||||
|
||||
EXAMPLES:
|
||||
|
||||
[IOS] [BREAKING] [FlatList] - Change a thing that breaks other things
|
||||
[ANDROID] [BUGFIX] [TextInput] - Did a thing to TextInput
|
||||
[CLI] [FEATURE] [local-cli/info/info.js] - CLI easier to do things with
|
||||
[DOCS] [BUGFIX] [GettingStarted.md] - Accidentally a thing/word
|
||||
[GENERAL] [ENHANCEMENT] [Yoga] - Added new yoga thing/position
|
||||
[INTERNAL] [FEATURE] [./scripts] - Added thing to script that nobody will see
|
||||
-->
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
# Configuration for probot-no-response - https://github.com/probot/no-response
|
||||
|
||||
# Number of days of inactivity before an Issue is closed for lack of response
|
||||
daysUntilClose: 14
|
||||
# Label requiring a response
|
||||
responseRequiredLabel: Needs more information
|
||||
# Comment to post when closing an Issue for lack of response. Set to `false` to disable
|
||||
closeComment: >
|
||||
Closing this issue as more information is needed to debug this and we have not heard back from the author.
|
||||
@@ -1,20 +0,0 @@
|
||||
# Number of days of inactivity before an issue becomes stale
|
||||
daysUntilStale: 60
|
||||
# Number of days of inactivity before a stale issue is closed
|
||||
daysUntilClose: 7
|
||||
# Issues with these labels will never be considered stale
|
||||
exemptLabels:
|
||||
- Good first issue
|
||||
- For Discussion
|
||||
- Core Team
|
||||
# Label to use when marking an issue as stale
|
||||
staleLabel: Stale
|
||||
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
This issue has been automatically marked as stale because it has not had
|
||||
recent activity. It will be closed if no further activity occurs.
|
||||
Maybe the issue has been fixed in a recent release, or perhaps it is not affecting a lot of people.
|
||||
If you think this issue should definitely remain open, please let us know why.
|
||||
Thank you for your contributions.
|
||||
# Comment to post when closing a stale issue. Set to `false` to disable
|
||||
closeComment: false
|
||||
+3
-1
@@ -47,7 +47,6 @@ local.properties
|
||||
node_modules
|
||||
*.log
|
||||
.nvm
|
||||
/danger/node_modules/
|
||||
|
||||
# OS X
|
||||
.DS_Store
|
||||
@@ -58,3 +57,6 @@ node_modules
|
||||
|
||||
/coverage
|
||||
/third-party
|
||||
|
||||
/website/src
|
||||
/website/build
|
||||
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
language: objective-c
|
||||
|
||||
osx_image: xcode8.3
|
||||
|
||||
install:
|
||||
- nvm install 7
|
||||
- rm -Rf "${TMPDIR}/jest_preprocess_cache"
|
||||
- brew install yarn --ignore-dependencies
|
||||
- yarn install
|
||||
|
||||
script:
|
||||
- if [[ "$TEST_TYPE" = objc-ios ]]; then travis_retry travis_wait 30 ./scripts/objc-test-ios.sh test; fi
|
||||
- if [[ "$TEST_TYPE" = objc-tvos ]]; then travis_retry travis_wait 30 ./scripts/objc-test-tvos.sh; fi
|
||||
- if [[ "$TEST_TYPE" = e2e-objc ]]; then node ./scripts/run-ci-e2e-tests.js --ios --js --retries 3; fi
|
||||
- if [[ ( "$TEST_TYPE" = podspecs ) && ( "$TRAVIS_PULL_REQUEST" = "false" ) ]]; then gem install cocoapods && travis_wait 30 ./scripts/process-podspecs.sh; fi
|
||||
|
||||
cache:
|
||||
- cocoapods
|
||||
- yarn
|
||||
|
||||
matrix:
|
||||
- fast_finish: true # Fail the whole build as soon as one test type fails. Should help with Travis capacity issues (very long queues).
|
||||
|
||||
# The order of these tests says which are more likely to run first and fail the whole build fast.
|
||||
env:
|
||||
- TEST_TYPE=objc-ios
|
||||
- TEST_TYPE=podspecs
|
||||
- TEST_TYPE=e2e-objc
|
||||
- TEST_TYPE=objc-tvos
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- /^.*-stable$/
|
||||
|
||||
notifications:
|
||||
email:
|
||||
recipients:
|
||||
- douglowder@mac.com # Doug Lowder built and maintains Apple TV specific code and wants to be notified about tvOS failures.
|
||||
- eloy@artsy.net # Eloy Durán maintains the podspecs test and wants to be notified about failures.
|
||||
on_failure: change
|
||||
on_success: change
|
||||
slack:
|
||||
secure: oQL2C966v7/DtxNqfM7WowjY0R5mgLHR2qHkoucwK5iVrmaptnHr8fq01xlj7VT0kDwNLqT3n4+gtCviGw89lq71m3W76c8Pms/10jpjw+LwAfQPVizNw/Bx8MFNNmjDauK/auFxaybiLZupi7zd4xFGOZvScmFdfD4CAAp2OOA=
|
||||
on_pull_requests: false
|
||||
on_failure: change
|
||||
on_success: change
|
||||
Vendored
+14
@@ -0,0 +1,14 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible Node.js debug attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Launch Program",
|
||||
"program": "${workspaceRoot}/Libraries/react-native/react-native-implementation.js"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
# [Open Source Code of Conduct](https://code.facebook.com/codeofconduct)
|
||||
|
||||
This code of conduct outlines our expectations for participants within the **Facebook Open Source** community, as well as steps to reporting unacceptable behavior. We are committed to providing a welcoming and inspiring community for all and expect our code of conduct to be honored. Anyone who violates this code of conduct may be banned from the community.
|
||||
|
||||
Our open source community strives to:
|
||||
|
||||
* **Be friendly and patient.**
|
||||
* **Be welcoming:** We strive to be a community that welcomes and supports people of all backgrounds and identities. This includes, but is not limited to members of any race, ethnicity, culture, national origin, colour, immigration status, social and economic class, educational level, sex, sexual orientation, gender identity and expression, age, size, family status, political belief, religion, and mental and physical ability.
|
||||
* **Be considerate:** Your work will be used by other people, and you in turn will depend on the work of others. Any decision you take will affect users and colleagues, and you should take those consequences into account when making decisions. Remember that we’re a world-wide community, so you might not be communicating in someone else’s primary language.
|
||||
* **Be respectful:** Not all of us will agree all the time, but disagreement is no excuse for poor behavior and poor manners. We might all experience some frustration now and then, but we cannot allow that frustration to turn into a personal attack. It’s important to remember that a community where people feel uncomfortable or threatened is not a productive one.
|
||||
* **Be careful in the words that you choose:** we are a community of professionals, and we conduct ourselves professionally. Be kind to others. Do not insult or put down other participants. Harassment and other exclusionary behavior aren’t acceptable. This includes, but is not limited to:
|
||||
* Violent threats or language directed against another person.
|
||||
* Discriminatory jokes and language.
|
||||
* Posting sexually explicit or violent material.
|
||||
* Posting (or threatening to post) other people’s personally identifying information (“doxing”).
|
||||
* Personal insults, especially those using racist or sexist terms.
|
||||
* Unwelcome sexual attention.
|
||||
* Advocating for, or encouraging, any of the above behavior.
|
||||
* Repeated harassment of others. In general, if someone asks you to stop, then stop.
|
||||
* **When we disagree, try to understand why:** Disagreements, both social and technical, happen all the time. It is important that we resolve disagreements and differing views constructively.
|
||||
* **Remember that we’re different.** The strength of our community comes from its diversity, people from a wide range of backgrounds. Different people have different perspectives on issues. Being unable to understand why someone holds a viewpoint doesn’t mean that they’re wrong. Don’t forget that it is human to err and blaming each other doesn’t get us anywhere. Instead, focus on helping to resolve issues and learning from mistakes.
|
||||
|
||||
This code is not exhaustive or complete. It serves to distill our common understanding of a collaborative, shared environment, and goals. We expect it to be followed in spirit as much as in the letter.
|
||||
|
||||
## Diversity Statement
|
||||
|
||||
We encourage everyone to participate and are committed to building a community for all. Although we may not be able to satisfy everyone, we all agree that everyone is equal. Whenever a participant has made a mistake, we expect them to take responsibility for it. If someone has been harmed or offended, it is our responsibility to listen carefully and respectfully, and do our best to right the wrong.
|
||||
|
||||
Although this list cannot be exhaustive, we explicitly honor diversity in age, gender, gender identity or expression, culture, ethnicity, language, national origin, political beliefs, profession, race, religion, sexual orientation, socioeconomic status, and technical ability. We will not tolerate discrimination based on any of the protected characteristics above, including participants with disabilities.
|
||||
|
||||
## Reporting Issues
|
||||
|
||||
If you experience or witness unacceptable behavior—or have any other concerns—please report it by contacting us via opensource@fb.com. All reports will be handled with discretion. In your report please include:
|
||||
|
||||
* Your contact information.
|
||||
* Names (real, nicknames, or pseudonyms) of any individuals involved. If there are additional witnesses, please include them as well. Your account of what occurred, and if you believe the incident is ongoing. If there is a publicly available record (e.g. a mailing list archive or a public IRC logger), please include a link.
|
||||
* Any additional information that may be helpful.
|
||||
|
||||
After filing a report, a representative will contact you personally. If the person who is harassing you is part of the response team, they will recuse themselves from handling your incident. A representative will then review the incident, follow up with any additional questions, and make a decision as to how to respond. We will respect confidentiality requests for the purpose of protecting victims of abuse.
|
||||
|
||||
Anyone asked to stop unacceptable behavior is expected to comply immediately. If an individual engages in unacceptable behavior, the representative may take any action they deem appropriate, up to and including a permanent ban from our community without warning.
|
||||
|
||||
_This Code Of Conduct follows the [template](http://todogroup.org/opencodeofconduct/) established by the [TODO Group](http://todogroup.org/)._
|
||||
+62
-80
@@ -1,123 +1,108 @@
|
||||
# Contributing to React Native
|
||||
|
||||
<!-- generated_contributing_start -->
|
||||
React Native is one of Facebook's first open source projects that is both under very active development and is also being used to ship code to everybody using Facebook's mobile apps. If you're interested in contributing to React Native, hopefully this document makes the process for contributing clear.
|
||||
React Native is one of Facebook's first open source projects that is both under very active development and is also being used to ship code to everybody on https://facebook.com. We're still working out the kinks to make contributing to this project as easy and transparent as possible, but we're not quite there yet. Hopefully this document makes the process for contributing clear and preempts some questions you may have.
|
||||
|
||||
## [Code of Conduct](https://code.facebook.com/codeofconduct)
|
||||
## Code of Conduct
|
||||
|
||||
Facebook has adopted a Code of Conduct that we expect project participants to adhere to. Please read [the full text](https://code.facebook.com/codeofconduct) so that you can understand what actions will and will not be tolerated.
|
||||
|
||||
## Get involved
|
||||
|
||||
There are many ways to contribute to React Native, and many of them do not involve writing any code. Here's a few ideas to get started:
|
||||
|
||||
* Simply start using React Native. Go through the [Getting Started](https://facebook.github.io/react-native/docs/getting-started.html) guide. Does everything work as expected? If not, we're always looking for improvements. Let us know by [opening an issue](https://facebook.github.io/react-native/docs/contributing.html#reporting-new-issues).
|
||||
* Look through the [open issues](https://github.com/facebook/react-native/issues). Provide workarounds, ask for clarification, or suggest labels. Help [triage issues](https://facebook.github.io/react-native/docs/contributing.html#triaging-issues-and-pull-requests).
|
||||
* If you find an issue you would like to fix, [open a pull request](https://facebook.github.io/react-native/docs/contributing.html#your-first-pull-request). Issues tagged as [_Good first issue_](https://github.com/facebook/react-native/labels/Good%20first%20issue) are a good place to get started.
|
||||
* Read through the [React Native docs](https://facebook.github.io/react-native/docs/getting-started.html). If you find anything that is confusing or can be improved, you can make edits by clicking "Improve this page" at the bottom of most docs.
|
||||
* Browse [Stack Overflow](https://stackoverflow.com/questions/tagged/react-native) and answer questions. This will help you get familiarized with common pitfalls or misunderstandings, which can be useful when contributing updates to the documentation.
|
||||
* Take a look at the [features requested](https://react-native.canny.io/feature-requests) by others in the community and consider opening a pull request if you see something you want to work on.
|
||||
|
||||
Contributions are very welcome. If you think you need help planning your contribution, please hop into [#react-native](https://discord.gg/0ZcbPKXt5bZjGY5n) and let people know you're looking for a mentor.
|
||||
|
||||
Core contributors to React Native meet monthly and post their meeting notes on the [React Native blog](https://facebook.github.io/react-native/blog). You can also find ad hoc discussions in the [React Native Core Contributors](https://www.facebook.com/groups/reactnativeoss/) Facebook group.
|
||||
|
||||
### Triaging issues and pull requests
|
||||
|
||||
One great way you can contribute to the project without writing any code is to help triage issues and pull requests as they come in.
|
||||
|
||||
* Ask for more information if the issue does not provide all the details required by the template.
|
||||
* Suggest [labels](https://github.com/facebook/react-native/labels) that can help categorize issues.
|
||||
* Flag issues that are stale or that should be closed.
|
||||
* Ask for test plans and review code.
|
||||
|
||||
You can learn more about handling issues in the [maintainer's guide](docs/maintainers.html#handling-issues).
|
||||
|
||||
## Our development process
|
||||
## Our Development Process
|
||||
|
||||
Some of the core team will be working directly on [GitHub](https://github.com/facebook/react-native). These changes will be public from the beginning. Other changesets will come via a bridge with Facebook's internal source control. This is a necessity as it allows engineers at Facebook outside of the core team to move fast and contribute from an environment they are comfortable in.
|
||||
|
||||
When a change made on GitHub is approved, it will first be imported into Facebook's internal source control. The change will eventually sync back to GitHub as a single commit once it has passed all internal tests.
|
||||
|
||||
### Branch organization
|
||||
## Branch Organization
|
||||
|
||||
We will do our best to keep `master` in good shape, with tests passing at all times. But in order to move fast, we will make API changes that your application might not be compatible with. We will do our best to [communicate these changes](https://github.com/facebook/react-native/releases) and version appropriately so you can lock into a specific version if need be.
|
||||
|
||||
To see what changes are coming and provide better feedback to React Native contributors, use the [latest release candidate](https://facebook.github.io/react-native/versions.html) when possible. By the time a release candidate is released, the changes it contains will have been shipped in production Facebook apps for over two weeks.
|
||||
To see what changes are coming and provide better feedback to React Native contributors, use the [latest release candidate](http://facebook.github.io/react-native/versions.html) when possible. By the time a release candidate is released, the changes it contains will have been shipped in production Facebook apps for over two weeks.
|
||||
|
||||
## Bugs
|
||||
|
||||
We use [GitHub Issues](https://github.com/facebook/react-native/issues) for our public bugs. If you would like to report a problem, take a look around and see if someone already opened an issue about it. If you a are certain this is a new, unreported bug, you can submit a [bug report](https://facebook.github.io/react-native/docs/contributing.html#reporting-new-issues).
|
||||
#### Where to Find Known Issues
|
||||
|
||||
If you have questions about using React Native, the [Community page](https://facebook.github.io/react-native/help.html) list various resources that should help you get started.
|
||||
We are using [GitHub Issues](https://github.com/facebook/react-native/issues) for our public bugs. Before filing a new task, try to make sure your problem doesn't already exist.
|
||||
|
||||
We also have a [place where you can request features or enhancements](https://react-native.canny.io/feature-requests). If you see anything you'd like to be implemented, vote it up and explain your use case.
|
||||
Questions and feature requests are tracked elsewhere:
|
||||
|
||||
## Reporting new issues
|
||||
- Have a question? [Ask on Stack Overflow](http://stackoverflow.com/questions/tagged/react-native).
|
||||
- If you have a question regarding future plans, check out the [roadmap](https://github.com/facebook/react-native/wiki/Roadmap).
|
||||
- Have a feature request that is not covered in the roadmap? [Add it here](https://react-native.canny.io/feature-requests).
|
||||
|
||||
When [opening a new issue](https://github.com/facebook/react-native/issues/new), always make sure to fill out the [issue template](https://raw.githubusercontent.com/facebook/react-native/master/.github/ISSUE_TEMPLATE.md). **This step is very important!** Not doing so may result in your issue getting closed. Don't take this personally if this happens, and feel free to open a new issue once you've gathered all the information required by the template.
|
||||
#### Reporting New Issues
|
||||
|
||||
* **One issue, one bug:** Please report a single bug per issue.
|
||||
* **Provide a Snack:** The best way to get attention on your issue is to provide a reduced test case. You can use [Snack](https://snack.expo.io/) to demonstrate the issue.
|
||||
* **Provide reproduction steps:** List all the steps necessary to reproduce the issue. Provide a Snack or upload a sample project to GitHub. The person reading your bug report should be able to follow these steps to reproduce your issue with minimal effort.
|
||||
* **Try out the latest version:** Verify that the issue can be reproduced locally by updating your project to use [React Native from `master`](https://facebook.github.io/react-native/versions.html). The bug may have already been fixed!
|
||||
The best way to get your bug fixed is to provide a reduced test case. Please provide either a [Sketch](https://sketch.expo.io/) or a public repository with a runnable example.
|
||||
|
||||
We're not able to provide support through GitHub Issues. If you're looking for help with your code, consider asking on [Stack Overflow](https://stackoverflow.com/questions/tagged/react-native) or reaching out to the community through [other channels](https://facebook.github.io/react-native/support.html).
|
||||
Please report a single bug per issue. Always provide reproduction steps. You can use Snack in many cases to demonstrate an issue: https://snack.expo.io/. If the bug cannot be reproduced using Snack, verify that the issue can be reproduced locally by targeting the latest release candidate. Ideally, check if the issue is present in master as well.
|
||||
|
||||
### Security bugs
|
||||
Do not forget to include sample code that reproduces the issue. Only open issues for bugs affecting either the latest stable release, or the current release candidate, or master (see http://facebook.github.io/react-native/versions.html). If it is not clear from your report that the issue can be reproduced in one of these releases, your issue will be closed.
|
||||
|
||||
We're not able to provide support through GitHub Issues. If you're looking for help with your code, consider asking on Stack Overflow: http://stackoverflow.com/questions/tagged/react-native
|
||||
|
||||
#### Security Bugs
|
||||
|
||||
Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe disclosure of security bugs. With that in mind, please do not file public issues; go through the process outlined on that page.
|
||||
|
||||
## Pull requests
|
||||
## How to Get in Touch
|
||||
|
||||
### Your first pull request
|
||||
Many React Native users are active on [Stack Overflow](http://stackoverflow.com/questions/tagged/react-native).
|
||||
|
||||
So you have decided to contribute code back to upstream by opening a pull request. You've invested a good chunk of time, and we appreciate it. We will do our best to work with you and get the PR looked at.
|
||||
If you want to get a general sense of what React Native folks talk about, check out the [React Native Community](https://www.facebook.com/groups/react.native.community) Facebook group.
|
||||
|
||||
Working on your first Pull Request? You can learn how from this free video series:
|
||||
There is also [an active community of React and React Native users on the Discord chat platform](https://discord.gg/0ZcbPKXt5bZjGY5n) in case you need help.
|
||||
|
||||
[**How to Contribute to an Open Source Project on GitHub**](https://egghead.io/courses/how-to-contribute-to-an-open-source-project-on-github)
|
||||
The React Native team sends out periodical updates through the following channels:
|
||||
|
||||
We have a list of [beginner friendly issues](https://github.com/facebook/react-native/labels/Good%20First%20Task) to help you get your feet wet in the React Native codebase and familiar with our contribution process. This is a great place to get started.
|
||||
* [Blog](https://facebook.github.io/react-native/blog/)
|
||||
* [Twitter](https://www.twitter.com/reactnative)
|
||||
|
||||
### Proposing a change
|
||||
Core contributors to React Native meet monthly and post their meeting notes on the React Native blog. You can also find ad hoc discussions in the [React Native Core Contributors](https://www.facebook.com/groups/reactnativeoss/) Facebook group.
|
||||
|
||||
If you would like to request a new feature or enhancement but are not yet thinking about opening a pull request, we have a [place to track feature requests](https://react-native.canny.io/feature-requests).
|
||||
## Proposing a Change
|
||||
|
||||
If you intend to change the public API, or make any non-trivial changes to the implementation, we recommend [filing an issue](https://github.com/facebook/react-native/issues/new?title=%5BProposal%5D) that includes `[Proposal]` in the title. This lets us reach an agreement on your proposal before you put significant effort into it. These types of issues should be rare. If you have been contributing to the project long enough, you will probably already have access to the [React Native Core Contributors](https://www.facebook.com/groups/reactnativeoss/) Facebook Group, where this sort of discussion is usually held.
|
||||
If you intend to change the public API, or make any non-trivial changes to the implementation, we recommend [filing an issue](https://github.com/facebook/react-native/issues/new). This lets us reach an agreement on your proposal before you put significant effort into it.
|
||||
|
||||
If you're only fixing a bug, it's fine to submit a pull request right away but we still recommend to file an issue detailing what you're fixing. This is helpful in case we don't accept that specific fix but want to keep track of the issue.
|
||||
|
||||
### Sending a pull request
|
||||
## Pull Requests
|
||||
|
||||
If you send a pull request, please do it against the master branch. We maintain stable branches for stable releases separately but we don't accept pull requests to them directly. Instead, we cherry-pick non-breaking changes from master to the latest stable version.
|
||||
|
||||
The core team will be monitoring for pull requests. When we get one, we'll run some Facebook-specific integration tests on it first. From here, we'll need to get another person to sign off on the changes and then merge the pull request. For API changes we may need to fix internal uses, which could cause some delay. We'll do our best to provide updates and feedback throughout the process.
|
||||
|
||||
Small pull requests are much easier to review and more likely to get merged. Make sure the PR does only one thing, otherwise please split it.
|
||||
|
||||
Please make sure the following is done when submitting a pull request:
|
||||
*Before* submitting a pull request, please make sure the following is done…
|
||||
|
||||
1. Fork [the repository](https://github.com/facebook/react-native) and create your branch from `master`.
|
||||
1. Fork the repo and create your branch from `master`.
|
||||
2. Add the copyright notice to the top of any new files you've added.
|
||||
3. Describe your [**test plan**](https://facebook.github.io/react-native/docs/contributing.html#test-plan) in your pull request description. Make sure to [test your changes](https://facebook.github.io/react-native/docs/testing.html)!
|
||||
4. Make sure your code lints (`npm run lint`).
|
||||
5. If you haven't already, [sign the CLA](https://code.facebook.com/cla).
|
||||
|
||||
All pull requests should be opened against the `master` branch. After opening your pull request, ensure [**all tests pass**](https://facebook.github.io/react-native/docs/contributing.html#contrinuous-integration-tests) on Circle CI. If a test fails and you believe it is unrelated to your change, leave a comment on the pull request explaining why.
|
||||
3. Describe your **test plan** in your commit.
|
||||
4. Ensure **tests pass** on Travis and Circle CI.
|
||||
5. Make sure your code lints (`node linter.js <files touched>`).
|
||||
6. If you haven't already, sign the CLA: https://code.facebook.com/cla
|
||||
7. Squash your commits (`git rebase -i`).
|
||||
One intent alongside one commit makes it clearer for people to review and easier to understand your intention.
|
||||
|
||||
> **Note:** It is not necessary to keep clicking `Merge master to your branch` on the PR page. You would want to merge master if there are conflicts or tests are failing. The Facebook-GitHub-Bot ultimately squashes all commits to a single one before merging your PR.
|
||||
|
||||
#### Test plan
|
||||
|
||||
A good test plan has the exact commands you ran and their output, provides screenshots or videos if the pull request changes UI.
|
||||
A good test plan has the exact commands you ran and their output, provides screenshots or videos if the pull request changes UI or updates the website.
|
||||
|
||||
* If you've added code that should be tested, add tests!
|
||||
* If you've changed APIs, update the documentation.
|
||||
- If you've added code that should be tested, add tests!
|
||||
- If you've changed APIs, update the documentation.
|
||||
- If you've updated the docs, verify the website locally and submit screenshots if applicable (see `website/README.md`)
|
||||
|
||||
See [What is a Test Plan?](https://medium.com/@martinkonicek/what-is-a-test-plan-8bfc840ec171#.y9lcuqqi9) to learn more.
|
||||
See "What is a Test Plan?" to learn more:
|
||||
https://medium.com/@martinkonicek/what-is-a-test-plan-8bfc840ec171#.y9lcuqqi9
|
||||
|
||||
#### Continuous integration tests
|
||||
|
||||
Make sure all **tests pass** on [Circle CI][circle]. PRs that break tests are unlikely to be merged. Learn more about [testing your changes here](https://facebook.github.io/react-native/docs/testing.html).
|
||||
Make sure all **tests pass** on both [Travis][travis] and [Circle CI][circle]. PRs that break tests are unlikely to be merged.
|
||||
|
||||
[circle]: https://circleci.com/gh/facebook/react-native
|
||||
You can learn more about running tests and contributing to React Native here: https://facebook.github.io/react-native/docs/testing.html
|
||||
|
||||
[travis]: https://travis-ci.org/facebook/react-native
|
||||
[circle]: http://circleci.com/gh/facebook/react-native
|
||||
|
||||
#### Breaking changes
|
||||
|
||||
@@ -126,10 +111,10 @@ When adding a new breaking change, follow this template in your pull request:
|
||||
```
|
||||
### New breaking change here
|
||||
|
||||
* **Who does this affect**:
|
||||
* **How to migrate**:
|
||||
* **Why make this breaking change**:
|
||||
* **Severity (number of people affected x effort)**:
|
||||
- **Who does this affect**:
|
||||
- **How to migrate**:
|
||||
- **Why make this breaking change**:
|
||||
- **Severity (number of people affected x effort)**:
|
||||
```
|
||||
|
||||
If your pull request is merged, a core contributor will update the [list of breaking changes](https://github.com/facebook/react-native/wiki/Breaking-Changes) which is then used to populate the release notes.
|
||||
@@ -151,17 +136,15 @@ Copy and paste this to the top of your new file(s):
|
||||
|
||||
If you've added a new module, add a `@providesModule <moduleName>` at the end of the comment. This will allow the haste package manager to find it.
|
||||
|
||||
#### Contributor License Agreement (CLA)
|
||||
### Contributor License Agreement (CLA)
|
||||
|
||||
In order to accept your pull request, we need you to submit a CLA. You only need to do this once, so if you've done this for another Facebook open source project, you're good to go. If you are submitting a pull request for the first time, the Facebook GitHub Bot will reply with a link to the CLA form. You may also [complete your CLA here](https://code.facebook.com/cla).
|
||||
In order to accept your pull request, we need you to submit a CLA. You only need to do this once, so if you've done this for another Facebook open source project, you're good to go. If you are submitting a pull request for the first time, just let us know that you have completed the CLA and we can cross-check with your GitHub username.
|
||||
|
||||
### What happens next?
|
||||
|
||||
The core team will be monitoring for pull requests. Read [what to expect from maintainers](https://facebook.github.io/react-native/docs/maintainers.html#handling-pull-requests) to understand what may happen after you open a pull request.
|
||||
Complete your CLA here: https://code.facebook.com/cla
|
||||
|
||||
## Style Guide
|
||||
|
||||
Our linter will catch most styling issues that may exist in your code. You can check the status of your code styling by simply running `npm run lint`.
|
||||
Our linter will catch most styling issues that may exist in your code. You can check the status of your code styling by simply running `node linter.js <files touched>`.
|
||||
|
||||
However, there are still some styles that the linter cannot pick up.
|
||||
|
||||
@@ -177,7 +160,7 @@ However, there are still some styles that the linter cannot pick up.
|
||||
#### JavaScript
|
||||
|
||||
* Use semicolons;
|
||||
* ES6 standards
|
||||
* `'use strict';`
|
||||
* Prefer `'` over `"`
|
||||
* Do not use the optional parameters of `setTimeout` and `setInterval`
|
||||
* 80 character line length
|
||||
@@ -211,4 +194,3 @@ However, there are still some styles that the linter cannot pick up.
|
||||
## License
|
||||
|
||||
By contributing to React Native, you agree that your contributions will be licensed under its BSD license.
|
||||
<!-- generated_contributing_end -->
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
FROM hramos/android-base:latest
|
||||
FROM containership/android-base:latest
|
||||
|
||||
# set default environment variables
|
||||
ENV GRADLE_OPTS="-Dorg.gradle.daemon=false -Dorg.gradle.jvmargs=\"-Xmx512m -XX:+HeapDumpOnOutOfMemoryError\""
|
||||
ENV GRADLE_OPTS="-Dorg.gradle.jvmargs=\"-Xmx512m -XX:+HeapDumpOnOutOfMemoryError\""
|
||||
ENV JAVA_TOOL_OPTIONS="-Dfile.encoding=UTF8"
|
||||
|
||||
# add ReactAndroid directory
|
||||
@@ -46,5 +46,9 @@ RUN cd $(npm root -g)/npm && npm install fs-extra && sed -i -e s/graceful-fs/fs-
|
||||
|
||||
# build node dependencies
|
||||
RUN npm install
|
||||
RUN npm install github@0.2.4
|
||||
|
||||
WORKDIR /app/website
|
||||
RUN npm install
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
FROM library/ubuntu:16.04
|
||||
|
||||
# set default build arguments
|
||||
ARG ANDROID_TOOLS_VERSION=25.2.5
|
||||
ARG BUCK_VERSION=v2017.11.16.01
|
||||
ARG ANDROID_VERSION=25.2.3
|
||||
ARG BUCK_VERSION=f3452a6a7ab15a60e94c962e686293acbe677473
|
||||
ARG NDK_VERSION=10e
|
||||
ARG NODE_VERSION=6.2.0
|
||||
ARG WATCHMAN_VERSION=4.7.0
|
||||
@@ -12,7 +12,7 @@ ENV ADB_INSTALL_TIMEOUT=10
|
||||
ENV PATH=${PATH}:/opt/buck/bin/
|
||||
ENV ANDROID_HOME=/opt/android
|
||||
ENV ANDROID_SDK_HOME=${ANDROID_HOME}
|
||||
ENV PATH=${PATH}:${ANDROID_HOME}/tools:${ANDROID_HOME}/tools/bin:${ANDROID_HOME}/platform-tools
|
||||
ENV PATH=${PATH}:${ANDROID_HOME}/tools:${ANDROID_HOME}/platform-tools
|
||||
ENV ANDROID_NDK=/opt/ndk/android-ndk-r$NDK_VERSION
|
||||
ENV PATH=${PATH}:${ANDROID_NDK}
|
||||
|
||||
@@ -28,8 +28,9 @@ RUN npm install n -g
|
||||
RUN n $NODE_VERSION
|
||||
|
||||
# download buck
|
||||
RUN git clone https://github.com/facebook/buck.git /opt/buck --branch $BUCK_VERSION --depth=1
|
||||
RUN git clone https://github.com/facebook/buck.git /opt/buck
|
||||
WORKDIR /opt/buck
|
||||
RUN git checkout $BUCK_VERSION
|
||||
|
||||
# build buck
|
||||
RUN ant
|
||||
@@ -45,11 +46,10 @@ RUN ./configure
|
||||
RUN make
|
||||
RUN make install
|
||||
|
||||
# Full reference at https://dl.google.com/android/repository/repository2-1.xml
|
||||
# download and unpack android
|
||||
RUN mkdir /opt/android
|
||||
WORKDIR /opt/android
|
||||
RUN curl --silent https://dl.google.com/android/repository/tools_r$ANDROID_TOOLS_VERSION-linux.zip > android.zip
|
||||
RUN curl --silent https://dl.google.com/android/repository/tools_r$ANDROID_VERSION-linux.zip > android.zip
|
||||
RUN unzip android.zip
|
||||
RUN rm android.zip
|
||||
|
||||
@@ -63,15 +63,39 @@ RUN unzip ndk.zip
|
||||
RUN rm ndk.zip
|
||||
|
||||
# Add android SDK tools
|
||||
RUN echo "y" | sdkmanager "system-images;android-19;google_apis;armeabi-v7a"
|
||||
RUN echo "y" | sdkmanager "platforms;android-23"
|
||||
RUN echo "y" | sdkmanager "platforms;android-19"
|
||||
RUN echo "y" | sdkmanager "build-tools;23.0.1"
|
||||
RUN echo "y" | sdkmanager "add-ons;addon-google_apis-google-23"
|
||||
RUN echo "y" | sdkmanager "extras;android;m2repository"
|
||||
|
||||
# Android SDK Platform-tools, revision 25.0.4
|
||||
RUN echo "y" | android update sdk -u -a -t $(android list sdk -a | grep "Android SDK Platform-tools, revision 25.0.4" | awk '{ print $1 }' | sed 's/.$//')
|
||||
|
||||
# Android SDK Build-tools, revision 23.0.1
|
||||
RUN echo "y" | android update sdk -u -a -t $(android list sdk -a | grep "Android SDK Build-tools, revision 23.0.1" | awk '{ print $1 }' | sed 's/.$//')
|
||||
|
||||
# SDK Platform Android 6.0, API 23, revision 3
|
||||
RUN echo "y" | android update sdk -u -a -t $(android list sdk -a | grep "SDK Platform Android 6.0, API 23" | awk '{ print $1 }' | sed 's/.$//')
|
||||
|
||||
# SDK Platform Android 4.4.2, API 19, revision 4
|
||||
RUN echo "y" | android update sdk -u -a -t $(android list sdk -a | grep "SDK Platform Android 4.4.2, API 19, revision 4" | awk '{ print $1 }' | sed 's/.$//')
|
||||
|
||||
# ARM EABI v7a System Image, Android API 19, revision 5
|
||||
RUN echo "y" | android update sdk -u -a -t $(android list sdk -a | grep "ARM EABI v7a System Image, Android API 19, revision 5" | awk '{ print $1 }' | sed 's/.$//')
|
||||
|
||||
# Intel x86 Atom System Image, Android API 19, revision 5
|
||||
RUN echo "y" | android update sdk -u -a -t $(android list sdk -a | grep "Intel x86 Atom System Image, Android API 19, revision 5" | awk '{ print $1 }' | sed 's/.$//')
|
||||
|
||||
# Google APIs, Android API 23, revision 1
|
||||
RUN echo "y" | android update sdk -u -a -t $(android list sdk -a | grep "Google APIs, Android API 23, revision 1" | awk '{ print $1 }' | sed 's/.$//')
|
||||
|
||||
# Android Support Repository, revision 45
|
||||
RUN echo "y" | android update sdk -u -a -t $(android list sdk -a | grep "Android Support Repository" | awk '{ print $1 }' | sed 's/.$//')
|
||||
|
||||
# Link adb executable
|
||||
RUN ln -s /opt/android/platform-tools/adb /usr/bin/adb
|
||||
|
||||
# Install google-chrome
|
||||
RUN curl -fsSL https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \
|
||||
&& echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" > /etc/apt/sources.list.d/google-chrome.list \
|
||||
&& apt-get update \
|
||||
&& apt-get install -y google-chrome-stable
|
||||
|
||||
# clean up unnecessary directories
|
||||
RUN rm -rf /opt/android/system-images/android-19/default/x86
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
* --package - com.facebook.react.tests
|
||||
* --retries [num] - how many times to retry possible flaky commands: npm install and running tests, default 1
|
||||
*/
|
||||
/*eslint-disable no-undef */
|
||||
|
||||
const argv = require('yargs').argv;
|
||||
const async = require('async');
|
||||
@@ -44,7 +45,7 @@ const test_opts = {
|
||||
|
||||
OFFSET: argv.offset,
|
||||
COUNT: argv.count
|
||||
};
|
||||
}
|
||||
|
||||
let max_test_class_length = Number.NEGATIVE_INFINITY;
|
||||
|
||||
@@ -84,14 +85,14 @@ if (test_opts.COUNT != null && test_opts.OFFSET != null) {
|
||||
}
|
||||
|
||||
return async.mapSeries(testClasses, (clazz, callback) => {
|
||||
if (clazz.length > max_test_class_length) {
|
||||
if(clazz.length > max_test_class_length) {
|
||||
max_test_class_length = clazz.length;
|
||||
}
|
||||
|
||||
return async.retry(test_opts.RETRIES, (retryCb) => {
|
||||
const test_process = child_process.spawn('./ContainerShip/scripts/run-instrumentation-tests-via-adb-shell.sh', [test_opts.PACKAGE, clazz], {
|
||||
stdio: 'inherit'
|
||||
});
|
||||
})
|
||||
|
||||
const timeout = setTimeout(() => {
|
||||
test_process.kill();
|
||||
@@ -105,7 +106,7 @@ return async.mapSeries(testClasses, (clazz, callback) => {
|
||||
test_process.on('exit', (code) => {
|
||||
clearTimeout(timeout);
|
||||
|
||||
if (code !== 0) {
|
||||
if(code !== 0) {
|
||||
return retryCb(new Error(`Process exited with code: ${code}`));
|
||||
}
|
||||
|
||||
@@ -137,16 +138,16 @@ function print_test_suite_results(results) {
|
||||
function pad_output(num_chars) {
|
||||
let i = 0;
|
||||
|
||||
while (i < num_chars) {
|
||||
while(i < num_chars) {
|
||||
process.stdout.write(' ');
|
||||
i++;
|
||||
}
|
||||
}
|
||||
results.forEach((test) => {
|
||||
if (test.status === 'success') {
|
||||
if(test.status === 'success') {
|
||||
color = colors.GREEN;
|
||||
passing_suites++;
|
||||
} else if (test.status === 'failure') {
|
||||
} else if(test.status === 'failure') {
|
||||
color = colors.RED;
|
||||
failing_suites++;
|
||||
}
|
||||
|
||||
@@ -230,13 +230,13 @@ function e2e_suite() {
|
||||
# js tests
|
||||
if [ $RUN_JS -ne 0 ]; then
|
||||
# Check the packager produces a bundle (doesn't throw an error)
|
||||
react-native bundle --max-workers 1 --platform android --dev true --entry-file index.js --bundle-output android-bundle.js
|
||||
react-native bundle --max-workers 1 --platform android --dev true --entry-file index.android.js --bundle-output android-bundle.js
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Could not build android bundle"
|
||||
return 1
|
||||
fi
|
||||
|
||||
react-native bundle --max-workers 1 --platform ios --dev true --entry-file index.js --bundle-output ios-bundle.js
|
||||
react-native bundle --max-workers 1 --platform ios --dev true --entry-file index.ios.js --bundle-output ios-bundle.js
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Could not build iOS bundle"
|
||||
return 1
|
||||
|
||||
@@ -29,7 +29,7 @@ def update():
|
||||
# prevent CircleCI from killing the process for inactivity
|
||||
while not done:
|
||||
time.sleep(5)
|
||||
print "Running in background. Waiting for 'adb' command response..."
|
||||
print "Running in background. Waiting for 'adb' command reponse..."
|
||||
|
||||
t = threading.Thread(target=update)
|
||||
t.dameon = True
|
||||
|
||||
@@ -21,7 +21,7 @@ const {
|
||||
} = ReactNative.NativeModules;
|
||||
|
||||
|
||||
class AccessibilityManagerTest extends React.Component<{}> {
|
||||
class AccessibilityManagerTest extends React.Component {
|
||||
componentDidMount() {
|
||||
AccessibilityManager.setAccessibilityContentSizeMultipliers({
|
||||
'extraSmall': 1.0,
|
||||
@@ -42,7 +42,7 @@ class AccessibilityManagerTest extends React.Component<{}> {
|
||||
});
|
||||
}
|
||||
|
||||
render(): React.Node {
|
||||
render(): React.Element<any> {
|
||||
return <View />;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ type State = {
|
||||
elapsed?: string,
|
||||
};
|
||||
|
||||
class AppEventsTest extends React.Component<{}, State> {
|
||||
class AppEventsTest extends React.Component {
|
||||
state: State = {sent: 'none', received: 'none'};
|
||||
|
||||
componentDidMount() {
|
||||
|
||||
@@ -155,7 +155,7 @@ function testOptimizedMultiGet() {
|
||||
let keys = batch.map(([key, value]) => key);
|
||||
AsyncStorage.multiSet(batch, (err1) => {
|
||||
// yes, twice on purpose
|
||||
[1, 2].forEach((i) => {
|
||||
;[1, 2].forEach((i) => {
|
||||
expectAsyncNoError(`${i} testOptimizedMultiGet/multiSet`, err1);
|
||||
AsyncStorage.multiGet(keys, (err2, result) => {
|
||||
expectAsyncNoError(`${i} testOptimizedMultiGet/multiGet`, err2);
|
||||
@@ -168,7 +168,7 @@ function testOptimizedMultiGet() {
|
||||
}
|
||||
|
||||
|
||||
class AsyncStorageTest extends React.Component<{}, $FlowFixMeState> {
|
||||
class AsyncStorageTest extends React.Component {
|
||||
state = {
|
||||
messages: 'Initializing...',
|
||||
done: false,
|
||||
@@ -189,11 +189,7 @@ class AsyncStorageTest extends React.Component<{}, $FlowFixMeState> {
|
||||
return (
|
||||
<View style={{backgroundColor: 'white', padding: 40}}>
|
||||
<Text>
|
||||
{
|
||||
/* $FlowFixMe(>=0.54.0 site=react_native_fb,react_native_oss) This
|
||||
* comment suppresses an error found when Flow v0.54 was deployed.
|
||||
* To see the error delete this comment and run Flow. */
|
||||
this.constructor.displayName + ': '}
|
||||
{this.constructor.displayName + ': '}
|
||||
{this.state.done ? 'Done' : 'Testing...'}
|
||||
{'\n\n' + this.state.messages}
|
||||
</Text>
|
||||
|
||||
@@ -41,14 +41,14 @@ type State = {
|
||||
'force-cache'?: boolean,
|
||||
}
|
||||
|
||||
class ImageCachePolicyTest extends React.Component<Props, $FlowFixMeState> {
|
||||
class ImageCachePolicyTest extends React.Component {
|
||||
state = {}
|
||||
|
||||
shouldComponentUpdate(nextProps: Props, nextState: State) {
|
||||
const results: Array<?boolean> = TESTS.map(x => nextState[x]);
|
||||
|
||||
if (!results.includes(undefined)) {
|
||||
const result: boolean = results.reduce((x,y) => x === y === true, true);
|
||||
const result: boolean = results.reduce((x,y) => x === y === true, true)
|
||||
TestModule.markTestPassed(result);
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ class ImageCachePolicyTest extends React.Component<Props, $FlowFixMeState> {
|
||||
<Text>Hello</Text>
|
||||
<Image
|
||||
source={{
|
||||
uri: 'https://facebook.github.io/react-native/img/favicon.png?cacheBust=notinCache' + Date.now(),
|
||||
uri: 'https://facebook.github.io/react/img/logo_small_2x.png?cacheBust=notinCache' + Date.now(),
|
||||
cache: 'only-if-cached'
|
||||
}}
|
||||
onLoad={() => this.testComplete('only-if-cached', false)}
|
||||
@@ -74,7 +74,7 @@ class ImageCachePolicyTest extends React.Component<Props, $FlowFixMeState> {
|
||||
/>
|
||||
<Image
|
||||
source={{
|
||||
uri: 'https://facebook.github.io/react-native/img/favicon.png?cacheBust=notinCache' + Date.now(),
|
||||
uri: 'https://facebook.github.io/react/img/logo_small_2x.png?cacheBust=notinCache' + Date.now(),
|
||||
cache: 'default'
|
||||
}}
|
||||
onLoad={() => this.testComplete('default', true)}
|
||||
@@ -83,7 +83,7 @@ class ImageCachePolicyTest extends React.Component<Props, $FlowFixMeState> {
|
||||
/>
|
||||
<Image
|
||||
source={{
|
||||
uri: 'https://facebook.github.io/react-native/img/favicon.png?cacheBust=notinCache' + Date.now(),
|
||||
uri: 'https://facebook.github.io/react/img/logo_small_2x.png?cacheBust=notinCache' + Date.now(),
|
||||
cache: 'reload'
|
||||
}}
|
||||
onLoad={() => this.testComplete('reload', true)}
|
||||
@@ -92,7 +92,7 @@ class ImageCachePolicyTest extends React.Component<Props, $FlowFixMeState> {
|
||||
/>
|
||||
<Image
|
||||
source={{
|
||||
uri: 'https://facebook.github.io/react-native/img/favicon.png?cacheBust=notinCache' + Date.now(),
|
||||
uri: 'https://facebook.github.io/react/img/logo_small_2x.png?cacheBust=notinCache' + Date.now(),
|
||||
cache: 'force-cache'
|
||||
}}
|
||||
onLoad={() => this.testComplete('force-cache', true)}
|
||||
|
||||
@@ -19,7 +19,7 @@ var {
|
||||
} = ReactNative;
|
||||
var { TestModule } = ReactNative.NativeModules;
|
||||
|
||||
class ImageSnapshotTest extends React.Component<{}> {
|
||||
class ImageSnapshotTest extends React.Component {
|
||||
componentDidMount() {
|
||||
if (!TestModule.verifySnapshot) {
|
||||
throw new Error('TestModule.verifySnapshot not defined.');
|
||||
|
||||
@@ -11,9 +11,6 @@
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/* $FlowFixMe(>=0.54.0 site=react_native_oss) This comment suppresses an error
|
||||
* found when Flow v0.54 was deployed. To see the error delete this comment and
|
||||
* run Flow. */
|
||||
var requestAnimationFrame = require('fbjs/lib/requestAnimationFrame');
|
||||
var React = require('react');
|
||||
var PropTypes = require('prop-types');
|
||||
@@ -24,10 +21,12 @@ var {
|
||||
} = ReactNative;
|
||||
var { TestModule } = ReactNative.NativeModules;
|
||||
|
||||
class IntegrationTestHarnessTest extends React.Component<{
|
||||
shouldThrow?: boolean,
|
||||
waitOneFrame?: boolean,
|
||||
}, $FlowFixMeState> {
|
||||
class IntegrationTestHarnessTest extends React.Component {
|
||||
props: {
|
||||
shouldThrow?: boolean,
|
||||
waitOneFrame?: boolean,
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
shouldThrow: PropTypes.bool,
|
||||
waitOneFrame: PropTypes.bool,
|
||||
@@ -63,11 +62,7 @@ class IntegrationTestHarnessTest extends React.Component<{
|
||||
return (
|
||||
<View style={{backgroundColor: 'white', padding: 40}}>
|
||||
<Text>
|
||||
{
|
||||
/* $FlowFixMe(>=0.54.0 site=react_native_fb,react_native_oss) This
|
||||
* comment suppresses an error found when Flow v0.54 was deployed.
|
||||
* To see the error delete this comment and run Flow. */
|
||||
this.constructor.displayName + ': '}
|
||||
{this.constructor.displayName + ': '}
|
||||
{this.state.done ? 'Done' : 'Testing...'}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
@@ -33,16 +33,12 @@ var TESTS = [
|
||||
require('./ImageCachePolicyTest'),
|
||||
require('./ImageSnapshotTest'),
|
||||
require('./PromiseTest'),
|
||||
require('./WebViewTest'),
|
||||
require('./SyncMethodTest'),
|
||||
require('./WebSocketTest'),
|
||||
require('./AccessibilityManagerTest'),
|
||||
];
|
||||
|
||||
TESTS.forEach(
|
||||
/* $FlowFixMe(>=0.54.0 site=react_native_fb,react_native_oss) This comment
|
||||
* suppresses an error found when Flow v0.54 was deployed. To see the error
|
||||
* delete this comment and run Flow. */
|
||||
(test) => AppRegistry.registerComponent(test.displayName, () => test)
|
||||
);
|
||||
|
||||
@@ -51,7 +47,7 @@ require('LoggingTestModule');
|
||||
|
||||
type Test = any;
|
||||
|
||||
class IntegrationTestsApp extends React.Component<{}, $FlowFixMeState> {
|
||||
class IntegrationTestsApp extends React.Component {
|
||||
state = {
|
||||
test: (null: ?Test),
|
||||
};
|
||||
@@ -60,9 +56,6 @@ class IntegrationTestsApp extends React.Component<{}, $FlowFixMeState> {
|
||||
if (this.state.test) {
|
||||
return (
|
||||
<ScrollView>
|
||||
{/* $FlowFixMe(>=0.53.0 site=react_native_fb,react_native_oss) This
|
||||
* comment suppresses an error when upgrading Flow's support for
|
||||
* React. To see the error delete this comment and run Flow. */}
|
||||
<this.state.test />
|
||||
</ScrollView>
|
||||
);
|
||||
|
||||
@@ -29,7 +29,17 @@ function debug(...args) {
|
||||
// console.log.apply(null, arguments);
|
||||
}
|
||||
|
||||
import type {Layout, LayoutEvent} from 'CoreEventTypes';
|
||||
type Layout = {
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
type LayoutEvent = {
|
||||
nativeEvent: {
|
||||
layout: Layout;
|
||||
};
|
||||
};
|
||||
type Style = {
|
||||
margin?: number,
|
||||
padding?: number,
|
||||
|
||||
@@ -16,7 +16,7 @@ var ReactNative = require('react-native');
|
||||
var { View } = ReactNative;
|
||||
var { TestModule } = ReactNative.NativeModules;
|
||||
|
||||
class PromiseTest extends React.Component<{}> {
|
||||
class PromiseTest extends React.Component {
|
||||
shouldResolve = false;
|
||||
shouldReject = false;
|
||||
shouldSucceedAsync = false;
|
||||
@@ -66,7 +66,7 @@ class PromiseTest extends React.Component<{}> {
|
||||
}
|
||||
};
|
||||
|
||||
render(): React.Node {
|
||||
render(): React.Element<any> {
|
||||
return <View />;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,9 +13,6 @@
|
||||
|
||||
var React = require('react');
|
||||
var ReactNative = require('react-native');
|
||||
/* $FlowFixMe(>=0.54.0 site=react_native_oss) This comment suppresses an error
|
||||
* found when Flow v0.54 was deployed. To see the error delete this comment and
|
||||
* run Flow. */
|
||||
var requestAnimationFrame = require('fbjs/lib/requestAnimationFrame');
|
||||
|
||||
var {
|
||||
@@ -24,7 +21,7 @@ var {
|
||||
} = ReactNative;
|
||||
var { TestModule } = ReactNative.NativeModules;
|
||||
|
||||
class SimpleSnapshotTest extends React.Component<{}> {
|
||||
class SimpleSnapshotTest extends React.Component {
|
||||
componentDidMount() {
|
||||
if (!TestModule.verifySnapshot) {
|
||||
throw new Error('TestModule.verifySnapshot not defined.');
|
||||
|
||||
@@ -21,7 +21,7 @@ const {
|
||||
} = ReactNative.NativeModules;
|
||||
|
||||
|
||||
class SyncMethodTest extends React.Component<{}> {
|
||||
class SyncMethodTest extends React.Component {
|
||||
componentDidMount() {
|
||||
if (RNTesterTestModule.echoString('test string value') !== 'test string value') {
|
||||
throw new Error('Something wrong with sync method export');
|
||||
@@ -32,7 +32,7 @@ class SyncMethodTest extends React.Component<{}> {
|
||||
TestModule.markTestCompleted();
|
||||
}
|
||||
|
||||
render(): React.Node {
|
||||
render(): React.Element<any> {
|
||||
return <View />;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,9 +14,6 @@
|
||||
var React = require('react');
|
||||
var createReactClass = require('create-react-class');
|
||||
var ReactNative = require('react-native');
|
||||
/* $FlowFixMe(>=0.54.0 site=react_native_oss) This comment suppresses an error
|
||||
* found when Flow v0.54 was deployed. To see the error delete this comment and
|
||||
* run Flow. */
|
||||
var TimerMixin = require('react-timer-mixin');
|
||||
|
||||
var {
|
||||
|
||||
@@ -42,7 +42,7 @@ type State = {
|
||||
testExpectedResponse: string;
|
||||
};
|
||||
|
||||
class WebSocketTest extends React.Component<{}, State> {
|
||||
class WebSocketTest extends React.Component {
|
||||
state: State = {
|
||||
url: DEFAULT_WS_URL,
|
||||
fetchStatus: null,
|
||||
@@ -158,7 +158,7 @@ class WebSocketTest extends React.Component<{}, State> {
|
||||
});
|
||||
}
|
||||
|
||||
render(): React.Node {
|
||||
render(): React.Element<any> {
|
||||
return <View />;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule WebViewTest
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
var ReactNative = require('react-native');
|
||||
var {
|
||||
WebView,
|
||||
} = ReactNative;
|
||||
|
||||
var { TestModule } = ReactNative.NativeModules;
|
||||
|
||||
class WebViewTest extends React.Component {
|
||||
|
||||
render() {
|
||||
var firstMessageReceived = false;
|
||||
var secondMessageReceived = false;
|
||||
function processMessage(e) {
|
||||
var message = e.nativeEvent.data;
|
||||
if (message === 'First') {firstMessageReceived = true;}
|
||||
if (message === 'Second') {secondMessageReceived = true;}
|
||||
|
||||
// got both messages
|
||||
if (firstMessageReceived && secondMessageReceived) {TestModule.markTestPassed(true);}
|
||||
// wait for next message
|
||||
else if (firstMessageReceived && !secondMessageReceived) {return;}
|
||||
// first message got lost
|
||||
else if (!firstMessageReceived && secondMessageReceived) {throw new Error('First message got lost');}
|
||||
}
|
||||
var html = 'Hello world'
|
||||
+ '<script>'
|
||||
+ "window.setTimeout(function(){window.postMessage('First'); window.postMessage('Second')}, 0)"
|
||||
+ '</script>';
|
||||
|
||||
// fail if messages didn't get through;
|
||||
window.setTimeout(function() { throw new Error(firstMessageReceived ? 'Both messages got lost' : 'Second message got lost');}, 10000);
|
||||
|
||||
var source = {
|
||||
html: html,
|
||||
};
|
||||
|
||||
return (
|
||||
<WebView
|
||||
source={source}
|
||||
onMessage = {processMessage}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
WebViewTest.displayName = 'WebViewTest';
|
||||
|
||||
module.exports = WebViewTest;
|
||||
@@ -15,9 +15,6 @@
|
||||
|
||||
/* eslint-env node */
|
||||
|
||||
/* $FlowFixMe(>=0.54.0 site=react_native_oss) This comment suppresses an error
|
||||
* found when Flow v0.54 was deployed. To see the error delete this comment and
|
||||
* run Flow. */
|
||||
const WebSocket = require('ws');
|
||||
|
||||
console.log(`\
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#import "JSContextRef.h"
|
||||
|
||||
extern "C" {
|
||||
|
||||
void nativeProfilerEnableBytecode(void);
|
||||
void nativeProfilerStart(JSContextRef ctx, const char *title);
|
||||
void nativeProfilerEnd(JSContextRef ctx, const char *title, const char *filename);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,311 @@
|
||||
#include "JSCLegacyProfiler.h"
|
||||
|
||||
#include "APICast.h"
|
||||
#include "LegacyProfiler.h"
|
||||
#include "OpaqueJSString.h"
|
||||
#include "JSProfilerPrivate.h"
|
||||
#include "JSStringRef.h"
|
||||
#include "String.h"
|
||||
#include "Options.h"
|
||||
|
||||
enum json_gen_status {
|
||||
json_gen_status_ok = 0,
|
||||
json_gen_status_error = 1,
|
||||
};
|
||||
|
||||
enum json_entry {
|
||||
json_entry_key,
|
||||
json_entry_value,
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
||||
struct json_state {
|
||||
FILE *fileOut;
|
||||
bool hasFirst;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
typedef json_state *json_gen;
|
||||
|
||||
static void json_escaped_cstring_printf(json_gen gen, const char *str) {
|
||||
const char *cursor = str;
|
||||
fputc('"', gen->fileOut);
|
||||
while (*cursor) {
|
||||
const char *escape = nullptr;
|
||||
switch (*cursor) {
|
||||
case '"':
|
||||
escape = "\\\"";
|
||||
break;
|
||||
case '\b':
|
||||
escape = "\\b";
|
||||
break;
|
||||
case '\f':
|
||||
escape = "\\f";
|
||||
break;
|
||||
case '\n':
|
||||
escape = "\\n";
|
||||
break;
|
||||
case '\r':
|
||||
escape = "\\r";
|
||||
break;
|
||||
case '\t':
|
||||
escape = "\\t";
|
||||
break;
|
||||
case '\\':
|
||||
escape = "\\\\";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (escape != nullptr) {
|
||||
fwrite(escape, 1, strlen(escape), gen->fileOut);
|
||||
} else {
|
||||
fputc(*cursor, gen->fileOut);
|
||||
}
|
||||
cursor++;
|
||||
}
|
||||
fputc('"', gen->fileOut);
|
||||
}
|
||||
|
||||
static json_gen_status json_gen_key_cstring(json_gen gen, const char *buffer) {
|
||||
if (gen->fileOut == nullptr) {
|
||||
return json_gen_status_error;
|
||||
}
|
||||
|
||||
if (gen->hasFirst) {
|
||||
fprintf(gen->fileOut, ",");
|
||||
}
|
||||
gen->hasFirst = true;
|
||||
|
||||
json_escaped_cstring_printf(gen, buffer);
|
||||
return json_gen_status_ok;
|
||||
}
|
||||
|
||||
static json_gen_status json_gen_map_open(json_gen gen, json_entry entryType) {
|
||||
if (gen->fileOut == nullptr) {
|
||||
return json_gen_status_error;
|
||||
}
|
||||
|
||||
if (entryType == json_entry_value) {
|
||||
fprintf(gen->fileOut, ":");
|
||||
} else if (entryType == json_entry_key) {
|
||||
if (gen->hasFirst) {
|
||||
fprintf(gen->fileOut, ",");
|
||||
}
|
||||
}
|
||||
fprintf(gen->fileOut, "{");
|
||||
gen->hasFirst = false;
|
||||
return json_gen_status_ok;
|
||||
}
|
||||
|
||||
static json_gen_status json_gen_map_close(json_gen gen) {
|
||||
if (gen->fileOut == nullptr) {
|
||||
return json_gen_status_error;
|
||||
}
|
||||
|
||||
fprintf(gen->fileOut, "}");
|
||||
gen->hasFirst = true;
|
||||
return json_gen_status_ok;
|
||||
}
|
||||
|
||||
static json_gen_status json_gen_array_open(json_gen gen, json_entry entryType) {
|
||||
if (gen->fileOut == nullptr) {
|
||||
return json_gen_status_error;
|
||||
}
|
||||
|
||||
if (entryType == json_entry_value) {
|
||||
fprintf(gen->fileOut, ":");
|
||||
} else if (entryType == json_entry_key) {
|
||||
if (gen->hasFirst) {
|
||||
fprintf(gen->fileOut, ",");
|
||||
}
|
||||
}
|
||||
fprintf(gen->fileOut, "[");
|
||||
gen->hasFirst = false;
|
||||
return json_gen_status_ok;
|
||||
}
|
||||
|
||||
static json_gen_status json_gen_array_close(json_gen gen) {
|
||||
if (gen->fileOut == nullptr) {
|
||||
return json_gen_status_error;
|
||||
}
|
||||
|
||||
fprintf(gen->fileOut, "]");
|
||||
gen->hasFirst = true;
|
||||
return json_gen_status_ok;
|
||||
}
|
||||
|
||||
static json_gen_status json_gen_keyvalue_cstring(json_gen gen, const char *key, const char *value) {
|
||||
if (gen->fileOut == nullptr) {
|
||||
return json_gen_status_error;
|
||||
}
|
||||
|
||||
if (gen->hasFirst) {
|
||||
fprintf(gen->fileOut, ",");
|
||||
}
|
||||
gen->hasFirst = true;
|
||||
|
||||
fprintf(gen->fileOut, "\"%s\" : ", key);
|
||||
json_escaped_cstring_printf(gen, value);
|
||||
|
||||
return json_gen_status_ok;
|
||||
}
|
||||
|
||||
|
||||
static json_gen_status json_gen_keyvalue_integer(json_gen gen, const char *key, int value) {
|
||||
if (gen->fileOut == nullptr) {
|
||||
return json_gen_status_error;
|
||||
}
|
||||
|
||||
if (gen->hasFirst) {
|
||||
fprintf(gen->fileOut, ",");
|
||||
}
|
||||
gen->hasFirst = true;
|
||||
|
||||
fprintf(gen->fileOut, "\"%s\": %d", key, value);
|
||||
return json_gen_status_ok;
|
||||
}
|
||||
|
||||
static json_gen_status json_gen_keyvalue_double(json_gen gen, const char *key, double value) {
|
||||
if (gen->fileOut == nullptr) {
|
||||
return json_gen_status_error;
|
||||
}
|
||||
|
||||
if (gen->hasFirst) {
|
||||
fprintf(gen->fileOut, ",");
|
||||
}
|
||||
gen->hasFirst = true;
|
||||
|
||||
fprintf(gen->fileOut, "\"%s\": %.20g", key, value);
|
||||
return json_gen_status_ok;
|
||||
}
|
||||
|
||||
static json_gen json_gen_alloc(const char *fileName) {
|
||||
json_gen gen = (json_gen)malloc(sizeof(json_state));
|
||||
memset(gen, 0, sizeof(json_state));
|
||||
gen->fileOut = fopen(fileName, "wb");
|
||||
return gen;
|
||||
}
|
||||
|
||||
static void json_gen_free(json_gen gen) {
|
||||
if (gen->fileOut) {
|
||||
fclose(gen->fileOut);
|
||||
}
|
||||
free(gen);
|
||||
}
|
||||
|
||||
#define GEN_AND_CHECK(expr) \
|
||||
do { \
|
||||
json_gen_status GEN_AND_CHECK_status = (expr); \
|
||||
if (GEN_AND_CHECK_status != json_gen_status_ok) { \
|
||||
return GEN_AND_CHECK_status; \
|
||||
} \
|
||||
} while (false)
|
||||
|
||||
|
||||
static json_gen_status append_children_array_json(json_gen gen, const JSC::ProfileNode *node);
|
||||
static json_gen_status append_node_json(json_gen gen, const JSC::ProfileNode *node);
|
||||
|
||||
static json_gen_status append_root_json(json_gen gen, const JSC::Profile *profile) {
|
||||
GEN_AND_CHECK(json_gen_map_open(gen, json_entry_key));
|
||||
GEN_AND_CHECK(json_gen_key_cstring(gen, "rootNodes"));
|
||||
#if IOS8
|
||||
GEN_AND_CHECK(append_children_array_json(gen, profile->head()));
|
||||
#else
|
||||
GEN_AND_CHECK(append_children_array_json(gen, profile->rootNode()));
|
||||
#endif
|
||||
GEN_AND_CHECK(json_gen_map_close(gen));
|
||||
|
||||
return json_gen_status_ok;
|
||||
}
|
||||
|
||||
static json_gen_status append_children_array_json(json_gen gen, const JSC::ProfileNode *node) {
|
||||
GEN_AND_CHECK(json_gen_array_open(gen, json_entry_value));
|
||||
for (RefPtr<JSC::ProfileNode> child : node->children()) {
|
||||
GEN_AND_CHECK(append_node_json(gen, child.get()));
|
||||
}
|
||||
GEN_AND_CHECK(json_gen_array_close(gen));
|
||||
|
||||
return json_gen_status_ok;
|
||||
}
|
||||
|
||||
static json_gen_status append_node_json(json_gen gen, const JSC::ProfileNode *node) {
|
||||
GEN_AND_CHECK(json_gen_map_open(gen, json_entry_key));
|
||||
GEN_AND_CHECK(json_gen_keyvalue_integer(gen, "id", node->id()));
|
||||
|
||||
if (!node->functionName().isEmpty()) {
|
||||
GEN_AND_CHECK(json_gen_keyvalue_cstring(gen, "functionName", node->functionName().utf8().data()));
|
||||
}
|
||||
|
||||
if (!node->url().isEmpty()) {
|
||||
GEN_AND_CHECK(json_gen_keyvalue_cstring(gen, "url", node->url().utf8().data()));
|
||||
GEN_AND_CHECK(json_gen_keyvalue_integer(gen, "lineNumber", node->lineNumber()));
|
||||
GEN_AND_CHECK(json_gen_keyvalue_integer(gen, "columnNumber", node->columnNumber()));
|
||||
}
|
||||
|
||||
GEN_AND_CHECK(json_gen_key_cstring(gen, "calls"));
|
||||
GEN_AND_CHECK(json_gen_array_open(gen, json_entry_value));
|
||||
for (const JSC::ProfileNode::Call &call : node->calls()) {
|
||||
GEN_AND_CHECK(json_gen_map_open(gen, json_entry_key));
|
||||
GEN_AND_CHECK(json_gen_keyvalue_double(gen, "startTime", call.startTime()));
|
||||
#if IOS8
|
||||
GEN_AND_CHECK(json_gen_keyvalue_double(gen, "totalTime", call.totalTime()));
|
||||
#else
|
||||
GEN_AND_CHECK(json_gen_keyvalue_double(gen, "totalTime", call.elapsedTime()));
|
||||
#endif
|
||||
GEN_AND_CHECK(json_gen_map_close(gen));
|
||||
}
|
||||
GEN_AND_CHECK(json_gen_array_close(gen));
|
||||
|
||||
if (!node->children().isEmpty()) {
|
||||
GEN_AND_CHECK(json_gen_key_cstring(gen, "children"));
|
||||
GEN_AND_CHECK(append_children_array_json(gen, node));
|
||||
}
|
||||
|
||||
GEN_AND_CHECK(json_gen_map_close(gen));
|
||||
|
||||
return json_gen_status_ok;
|
||||
}
|
||||
|
||||
static void convert_to_json(const JSC::Profile *profile, const char *filename) {
|
||||
json_gen_status status;
|
||||
json_gen gen = json_gen_alloc(filename);
|
||||
|
||||
status = append_root_json(gen, profile);
|
||||
if (status != json_gen_status_ok) {
|
||||
FILE *fileOut = fopen(filename, "wb");
|
||||
if (fileOut != nullptr) {
|
||||
fprintf(fileOut, "{\"error\": %d}", (int)status);
|
||||
fclose(fileOut);
|
||||
}
|
||||
}
|
||||
json_gen_free(gen);
|
||||
}
|
||||
|
||||
// Based on JSEndProfiling, with a little extra code to return the profile as JSON.
|
||||
static void JSEndProfilingAndRender(JSContextRef ctx, const char *title, const char *filename)
|
||||
{
|
||||
JSC::ExecState *exec = toJS(ctx);
|
||||
JSC::LegacyProfiler *profiler = JSC::LegacyProfiler::profiler();
|
||||
RefPtr<JSC::Profile> rawProfile = profiler->stopProfiling(exec, WTF::String(title));
|
||||
convert_to_json(rawProfile.get(), filename);
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
void nativeProfilerEnableBytecode(void)
|
||||
{
|
||||
JSC::Options::setOption("forceProfilerBytecodeGeneration=true");
|
||||
}
|
||||
|
||||
void nativeProfilerStart(JSContextRef ctx, const char *title) {
|
||||
JSStartProfiling(ctx, JSStringCreateWithUTF8CString(title));
|
||||
}
|
||||
|
||||
void nativeProfilerEnd(JSContextRef ctx, const char *title, const char *filename) {
|
||||
JSEndProfilingAndRender(ctx, title, filename);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
ios9:
|
||||
IOS_VERSION=9 \
|
||||
JSC_VERSION=7601.1.46.3 \
|
||||
WEB_CORE_VERSION=7601.1.46.10 \
|
||||
WTF_VERSION=7601.1.46.3 \
|
||||
make -f Makefile.base
|
||||
|
||||
ios8:
|
||||
IOS_VERSION=8 \
|
||||
JSC_VERSION=7600.1.17 \
|
||||
WEB_CORE_VERSION=7600.1.25 \
|
||||
WTF_VERSION=7600.1.24 \
|
||||
make -f Makefile.base
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
-rm -rf $(wildcard *.dylib)
|
||||
-rm -rf download
|
||||
@@ -0,0 +1,80 @@
|
||||
HEADER_PATHS := `find download/JavaScriptCore/JavaScriptCore-$(JSC_VERSION) -name '*.h' | xargs -I{} dirname {} | uniq | xargs -I{} echo "-I {}"`
|
||||
|
||||
XCODE_PATH ?= $(shell xcode-select -p)
|
||||
SDK_PATH = $(XCODE_PATH)/Platforms/$1.platform/Developer/SDKs/$1.sdk
|
||||
SDK_VERSION = $(shell plutil -convert json -o - $(call SDK_PATH,iPhoneOS)/SDKSettings.plist | awk -f parseSDKVersion.awk)
|
||||
|
||||
CERT ?= iPhone Developer
|
||||
|
||||
ARCHS = x86_64 arm64 armv7 i386
|
||||
|
||||
PLATFORM = \
|
||||
if [[ "$*" = "x86_64" || "$*" = "i386" ]]; then \
|
||||
PLATFORM=iPhoneSimulator; \
|
||||
else \
|
||||
PLATFORM=iPhoneOS; \
|
||||
fi;
|
||||
|
||||
SYSROOT = -isysroot $(call SDK_PATH,$${PLATFORM})
|
||||
|
||||
IOS_LIBS = \
|
||||
download/JavaScriptCore/JavaScriptCore-$(JSC_VERSION) \
|
||||
download/WebCore/WebCore-$(WEB_CORE_VERSION) \
|
||||
download/WTF/WTF-$(WTF_VERSION) \
|
||||
download/JavaScriptCore/JavaScriptCore-$(JSC_VERSION)/Bytecodes.h
|
||||
|
||||
IOS_EXT=ios$(IOS_VERSION)
|
||||
|
||||
ifneq ($(SDK_VERSION), $(IOS_VERSION))
|
||||
|
||||
all:
|
||||
$(error "Expected to be compiled with iOS SDK version 8, found $(SDK_VERSION)")
|
||||
|
||||
else
|
||||
|
||||
all: RCTJSCProfiler.$(IOS_EXT).dylib /tmp/RCTJSCProfiler
|
||||
cp $^
|
||||
|
||||
endif
|
||||
|
||||
/tmp/RCTJSCProfiler:
|
||||
mkdir -p $@
|
||||
|
||||
RCTJSCProfiler.$(IOS_EXT).dylib: RCTJSCProfiler_unsigned.$(IOS_EXT).dylib
|
||||
cp $< $@
|
||||
codesign -f -s "${CERT}" $@
|
||||
|
||||
.PRECIOUS: RCTJSCProfiler_unsigned.$(IOS_EXT).dylib
|
||||
RCTJSCProfiler_unsigned.$(IOS_EXT).dylib: $(patsubst %,RCTJSCProfiler_%.$(IOS_EXT).dylib,$(ARCHS))
|
||||
lipo -create -output $@ $^
|
||||
|
||||
.PRECIOUS: RCTJSCProfiler_%.$(IOS_EXT).dylib
|
||||
RCTJSCProfiler_%.$(IOS_EXT).dylib: $(IOS_LIBS)
|
||||
$(PLATFORM) \
|
||||
clang -w -dynamiclib -o RCTJSCProfiler_$*.$(IOS_EXT).dylib -std=c++11 \
|
||||
-arch $* \
|
||||
-install_name RCTJSCProfiler.$(IOS_EXT).dylib \
|
||||
-include ./download/JavaScriptCore/JavaScriptCore-$(JSC_VERSION)/config.h \
|
||||
-I download \
|
||||
-I download/WebCore/WebCore-$(WEB_CORE_VERSION)/icu \
|
||||
-I download/WTF/WTF-$(WTF_VERSION) \
|
||||
-DNDEBUG=1 \
|
||||
-DIOS$(IOS_VERSION)=1 \
|
||||
-miphoneos-version-min=8.0 \
|
||||
$(SYSROOT) \
|
||||
$(HEADER_PATHS) \
|
||||
-undefined dynamic_lookup \
|
||||
JSCLegacyProfiler.mm
|
||||
|
||||
.PRECIOUS: %/Bytecodes.h
|
||||
%/Bytecodes.h:
|
||||
python $*/generate-bytecode-files --bytecodes_h $@ $*/bytecode/BytecodeList.json
|
||||
|
||||
.PRECIOUS: download/%
|
||||
download/%: download/%.tar.gz
|
||||
tar -zxvf $< -C `dirname $@` > /dev/null
|
||||
|
||||
.PRECIOUS: %.tar.gz
|
||||
%.tar.gz:
|
||||
mkdir -p `dirname $@`
|
||||
curl -o $@ http://www.opensource.apple.com/tarballs/$(patsubst download/%,%,$@)
|
||||
Executable
+254
@@ -0,0 +1,254 @@
|
||||
#!/usr/bin/env python
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import smap
|
||||
import trace_data
|
||||
import urllib
|
||||
|
||||
SECONDS_TO_NANOSECONDS = (1000*1000)
|
||||
SAMPLE_DELTA_IN_SECONDS = 0.0001
|
||||
|
||||
class Marker(object):
|
||||
def __init__(self, _name, _timestamp, _depth, _is_end, _ident, url, line, col):
|
||||
self.name = _name
|
||||
self.timestamp = _timestamp
|
||||
self.depth = _depth
|
||||
self.is_end = _is_end
|
||||
self.ident = _ident
|
||||
self.url = url
|
||||
self.line = line
|
||||
self.col = col
|
||||
|
||||
# sort markers making sure they are ordered by timestamp then depth of function call
|
||||
# and finally that markers of the same ident are sorted in the order begin then end
|
||||
def __cmp__(self, other):
|
||||
if self.timestamp < other.timestamp:
|
||||
return -1
|
||||
if self.timestamp > other.timestamp:
|
||||
return 1
|
||||
if self.depth < other.depth:
|
||||
return -1
|
||||
if self.depth > other.depth:
|
||||
return 1
|
||||
if self.ident == other.ident:
|
||||
if self.is_end:
|
||||
return 1
|
||||
return 0
|
||||
|
||||
# calculate marker name based on combination of function name and location
|
||||
def _calcname(entry):
|
||||
funcname = ""
|
||||
if "functionName" in entry:
|
||||
funcname = funcname + entry["functionName"]
|
||||
return funcname
|
||||
|
||||
def _calcurl(mapcache, entry, map_file):
|
||||
if entry.url not in mapcache:
|
||||
map_url = entry.url.replace('.bundle', '.map')
|
||||
|
||||
if map_url != entry.url:
|
||||
if map_file:
|
||||
print('Loading sourcemap from:' + map_file)
|
||||
map_url = map_file
|
||||
|
||||
try:
|
||||
url_file = urllib.urlopen(map_url)
|
||||
if url_file != None:
|
||||
entries = smap.parse(url_file)
|
||||
mapcache[entry.url] = entries
|
||||
except Exception, e:
|
||||
mapcache[entry.url] = []
|
||||
|
||||
if entry.url in mapcache:
|
||||
source_entry = smap.find(mapcache[entry.url], entry.line, entry.col)
|
||||
if source_entry:
|
||||
entry.url = 'file://' + source_entry.src
|
||||
entry.line = source_entry.src_line
|
||||
entry.col = source_entry.src_col
|
||||
|
||||
def _compute_markers(markers, call_point, depth):
|
||||
name = _calcname(call_point)
|
||||
ident = len(markers)
|
||||
url = ""
|
||||
lineNumber = -1
|
||||
columnNumber = -1
|
||||
if "url" in call_point:
|
||||
url = call_point["url"]
|
||||
if "lineNumber" in call_point:
|
||||
lineNumber = call_point["lineNumber"]
|
||||
if "columnNumber" in call_point:
|
||||
columnNumber = call_point["columnNumber"]
|
||||
|
||||
for call in call_point["calls"]:
|
||||
markers.append(Marker(name, call["startTime"], depth, 0, ident, url, lineNumber, columnNumber))
|
||||
markers.append(Marker(name, call["startTime"] + call["totalTime"], depth, 1, ident, url, lineNumber, columnNumber))
|
||||
ident = ident + 2
|
||||
if "children" in call_point:
|
||||
for child in call_point["children"]:
|
||||
_compute_markers(markers, child, depth+1);
|
||||
|
||||
def _find_child(children, name):
|
||||
for child in children:
|
||||
if child['functionName'] == name:
|
||||
return child
|
||||
return None
|
||||
|
||||
def _add_entry_cpuprofiler_program(newtime, cpuprofiler):
|
||||
curnode = _find_child(cpuprofiler['head']['children'], '(program)')
|
||||
if cpuprofiler['lastTime'] != None:
|
||||
lastTime = cpuprofiler['lastTime']
|
||||
while lastTime < newtime:
|
||||
curnode['hitCount'] += 1
|
||||
cpuprofiler['samples'].append(curnode['callUID'])
|
||||
cpuprofiler['timestamps'].append(int(lastTime*SECONDS_TO_NANOSECONDS))
|
||||
lastTime += SAMPLE_DELTA_IN_SECONDS
|
||||
cpuprofiler['lastTime'] = lastTime
|
||||
else:
|
||||
cpuprofiler['lastTime'] = newtime
|
||||
|
||||
|
||||
def _add_entry_cpuprofiler(stack, newtime, cpuprofiler):
|
||||
index = len(stack) - 1
|
||||
marker = stack[index]
|
||||
|
||||
if marker.name not in cpuprofiler['markers']:
|
||||
cpuprofiler['markers'][marker.name] = cpuprofiler['id']
|
||||
cpuprofiler['callUID'] += 1
|
||||
callUID = cpuprofiler['markers'][marker.name]
|
||||
|
||||
curnode = cpuprofiler['head']
|
||||
index = 0
|
||||
while index < len(stack):
|
||||
newnode = _find_child(curnode['children'], stack[index].name)
|
||||
if newnode == None:
|
||||
newnode = {}
|
||||
newnode['callUID'] = callUID
|
||||
newnode['url'] = marker.url
|
||||
newnode['functionName'] = stack[index].name
|
||||
newnode['hitCount'] = 0
|
||||
newnode['lineNumber'] = marker.line
|
||||
newnode['columnNumber'] = marker.col
|
||||
newnode['scriptId'] = callUID
|
||||
newnode['positionTicks'] = []
|
||||
newnode['id'] = cpuprofiler['id']
|
||||
cpuprofiler['id'] += 1
|
||||
newnode['children'] = []
|
||||
curnode['children'].append(newnode)
|
||||
curnode['deoptReason'] = ''
|
||||
curnode = newnode
|
||||
index += 1
|
||||
|
||||
if cpuprofiler['lastTime'] == None:
|
||||
cpuprofiler['lastTime'] = newtime
|
||||
|
||||
if cpuprofiler['lastTime'] != None:
|
||||
lastTime = cpuprofiler['lastTime']
|
||||
while lastTime < newtime:
|
||||
curnode['hitCount'] += 1
|
||||
if len(curnode['positionTicks']) == 0:
|
||||
ticks = {}
|
||||
ticks['line'] = curnode['callUID']
|
||||
ticks['ticks'] = 0
|
||||
curnode['positionTicks'].append(ticks)
|
||||
curnode['positionTicks'][0]['ticks'] += 1
|
||||
cpuprofiler['samples'].append(curnode['callUID'])
|
||||
cpuprofiler['timestamps'].append(int(lastTime*1000*1000))
|
||||
lastTime += 0.0001
|
||||
cpuprofiler['lastTime'] = lastTime
|
||||
|
||||
def _create_default_cpuprofiler_node(name, _id, _uid):
|
||||
return {'functionName': name,
|
||||
'scriptId':'0',
|
||||
'url':'',
|
||||
'lineNumber':0,
|
||||
'columnNumber':0,
|
||||
'positionTicks':[],
|
||||
'id':_id,
|
||||
'callUID':_uid,
|
||||
'children': [],
|
||||
'hitCount': 0,
|
||||
'deoptReason':''}
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Converts JSON profile format to fbsystrace text output")
|
||||
|
||||
parser.add_argument(
|
||||
"-o",
|
||||
dest = "output_file",
|
||||
default = None,
|
||||
help = "Output file for trace data")
|
||||
parser.add_argument(
|
||||
"-cpuprofiler",
|
||||
dest = "output_cpuprofiler",
|
||||
default = None,
|
||||
help = "Output file for cpuprofiler data")
|
||||
parser.add_argument(
|
||||
"-map",
|
||||
dest = "map_file",
|
||||
default = None,
|
||||
help = "Map file for symbolicating")
|
||||
parser.add_argument( "file", help = "JSON trace input_file")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
markers = []
|
||||
with open(args.file, "r") as trace_file:
|
||||
trace = json.load(trace_file)
|
||||
for root_entry in trace["rootNodes"]:
|
||||
_compute_markers(markers, root_entry, 0)
|
||||
|
||||
mapcache = {}
|
||||
for m in markers:
|
||||
_calcurl(mapcache, m, args.map_file)
|
||||
|
||||
sorted_markers = list(sorted(markers));
|
||||
|
||||
if args.output_cpuprofiler != None:
|
||||
cpuprofiler = {}
|
||||
cpuprofiler['startTime'] = None
|
||||
cpuprofiler['endTime'] = None
|
||||
cpuprofiler['lastTime'] = None
|
||||
cpuprofiler['id'] = 4
|
||||
cpuprofiler['callUID'] = 4
|
||||
cpuprofiler['samples'] = []
|
||||
cpuprofiler['timestamps'] = []
|
||||
cpuprofiler['markers'] = {}
|
||||
cpuprofiler['head'] = _create_default_cpuprofiler_node('(root)', 1, 1)
|
||||
cpuprofiler['head']['children'].append(_create_default_cpuprofiler_node('(root)', 2, 2))
|
||||
cpuprofiler['head']['children'].append(_create_default_cpuprofiler_node('(program)', 3, 3))
|
||||
marker_stack = []
|
||||
with open(args.output_cpuprofiler, 'w') as file_out:
|
||||
for marker in sorted_markers:
|
||||
if len(marker_stack):
|
||||
_add_entry_cpuprofiler(marker_stack, marker.timestamp, cpuprofiler)
|
||||
else:
|
||||
_add_entry_cpuprofiler_program(marker.timestamp, cpuprofiler)
|
||||
if marker.is_end:
|
||||
marker_stack.pop()
|
||||
else:
|
||||
marker_stack.append(marker)
|
||||
cpuprofiler['startTime'] = cpuprofiler['timestamps'][0] / 1000000.0
|
||||
cpuprofiler['endTime'] = cpuprofiler['timestamps'][len(cpuprofiler['timestamps']) - 1] / 1000000.0
|
||||
json.dump(cpuprofiler, file_out, sort_keys=False, indent=4, separators=(',', ': '))
|
||||
|
||||
|
||||
if args.output_file != None:
|
||||
with open(args.output_file,"w") as trace_file:
|
||||
for marker in sorted_markers:
|
||||
start_or_end = None
|
||||
if marker.is_end:
|
||||
start_or_end = "E"
|
||||
else:
|
||||
start_or_end = "B"
|
||||
#output with timestamp at high level of precision
|
||||
trace_file.write("json-0 [000] .... {0:.12f}: tracing_mark_write: {1}|0|{2}\n".format(
|
||||
marker.timestamp,
|
||||
start_or_end,
|
||||
marker.name))
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,10 @@
|
||||
BEGIN {
|
||||
FS = ":"
|
||||
RS = ","
|
||||
}
|
||||
|
||||
/"Version"/ {
|
||||
version = substr($2, 2, length($2) - 2)
|
||||
print int(version)
|
||||
exit 0
|
||||
}
|
||||
@@ -0,0 +1,343 @@
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
||||
"""
|
||||
adapted from https://github.com/martine/python-sourcemap into a reuasable module
|
||||
"""
|
||||
|
||||
"""
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2010
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
"""
|
||||
|
||||
"""A module for parsing source maps, as output by the Closure and
|
||||
CoffeeScript compilers and consumed by browsers. See
|
||||
http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/
|
||||
"""
|
||||
|
||||
import collections
|
||||
import json
|
||||
import sys
|
||||
import bisect
|
||||
|
||||
class entry(object):
|
||||
def __init__(self, dst_line, dst_col, src, src_line, src_col):
|
||||
self.dst_line = dst_line
|
||||
self.dst_col = dst_col
|
||||
self.src = src
|
||||
self.src_line = src_line
|
||||
self.src_col = src_col
|
||||
|
||||
def __cmp__(self, other):
|
||||
#print(self)
|
||||
#print(other)
|
||||
if self.dst_line < other.dst_line:
|
||||
return -1
|
||||
if self.dst_line > other.dst_line:
|
||||
return 1
|
||||
if self.dst_col < other.dst_col:
|
||||
return -1
|
||||
if self.dst_col > other.dst_col:
|
||||
return 1
|
||||
return 0
|
||||
|
||||
SmapState = collections.namedtuple(
|
||||
'SmapState', ['dst_line', 'dst_col',
|
||||
'src', 'src_line', 'src_col',
|
||||
'name'])
|
||||
|
||||
# Mapping of base64 letter -> integer value.
|
||||
B64 = dict((c, i) for i, c in
|
||||
enumerate('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
|
||||
'0123456789+/'))
|
||||
|
||||
|
||||
def _parse_vlq(segment):
|
||||
"""Parse a string of VLQ-encoded data.
|
||||
|
||||
Returns:
|
||||
a list of integers.
|
||||
"""
|
||||
|
||||
values = []
|
||||
|
||||
cur, shift = 0, 0
|
||||
for c in segment:
|
||||
val = B64[c]
|
||||
# Each character is 6 bits:
|
||||
# 5 of value and the high bit is the continuation.
|
||||
val, cont = val & 0b11111, val >> 5
|
||||
cur += val << shift
|
||||
shift += 5
|
||||
|
||||
if not cont:
|
||||
# The low bit of the unpacked value is the sign.
|
||||
cur, sign = cur >> 1, cur & 1
|
||||
if sign:
|
||||
cur = -cur
|
||||
values.append(cur)
|
||||
cur, shift = 0, 0
|
||||
|
||||
if cur or shift:
|
||||
raise Exception('leftover cur/shift in vlq decode')
|
||||
|
||||
return values
|
||||
|
||||
|
||||
def _parse_smap(file):
|
||||
"""Given a file-like object, yield SmapState()s as they are read from it."""
|
||||
|
||||
smap = json.load(file)
|
||||
sources = smap['sources']
|
||||
names = smap['names']
|
||||
mappings = smap['mappings']
|
||||
lines = mappings.split(';')
|
||||
|
||||
dst_col, src_id, src_line, src_col, name_id = 0, 0, 0, 0, 0
|
||||
for dst_line, line in enumerate(lines):
|
||||
segments = line.split(',')
|
||||
dst_col = 0
|
||||
for segment in segments:
|
||||
if not segment:
|
||||
continue
|
||||
parsed = _parse_vlq(segment)
|
||||
dst_col += parsed[0]
|
||||
|
||||
src = None
|
||||
name = None
|
||||
if len(parsed) > 1:
|
||||
src_id += parsed[1]
|
||||
src = sources[src_id]
|
||||
src_line += parsed[2]
|
||||
src_col += parsed[3]
|
||||
|
||||
if len(parsed) > 4:
|
||||
name_id += parsed[4]
|
||||
name = names[name_id]
|
||||
|
||||
assert dst_line >= 0
|
||||
assert dst_col >= 0
|
||||
assert src_line >= 0
|
||||
assert src_col >= 0
|
||||
|
||||
yield SmapState(dst_line, dst_col, src, src_line, src_col, name)
|
||||
|
||||
def find(entries, line, col):
|
||||
test = entry(line, col, '', 0, 0)
|
||||
index = bisect.bisect_right(entries, test)
|
||||
if index == 0:
|
||||
return None
|
||||
return entries[index - 1]
|
||||
|
||||
def parse(file):
|
||||
# Simple demo that shows files that most contribute to total size.
|
||||
lookup = []
|
||||
for state in _parse_smap(file):
|
||||
lookup.append(entry(state.dst_line, state.dst_col, state.src, state.src_line, state.src_col))
|
||||
|
||||
sorted_lookup = list(sorted(lookup))
|
||||
return sorted_lookup
|
||||
@@ -0,0 +1,244 @@
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import re
|
||||
import unittest
|
||||
|
||||
"""
|
||||
# _-----=> irqs-off
|
||||
# / _----=> need-resched
|
||||
# | / _---=> hardirq/softirq
|
||||
# || / _--=> preempt-depth
|
||||
# ||| / delay
|
||||
# TASK-PID CPU# |||| TIMESTAMP FUNCTION
|
||||
# | | | |||| | |
|
||||
<idle>-0 [001] ...2 3269.291072: sched_switch: prev_comm=swapper/1 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=mmcqd/0 next_pid=120 next_prio=120
|
||||
"""
|
||||
TRACE_LINE_PATTERN = re.compile(
|
||||
r'^\s*(?P<task>.+)-(?P<pid>\d+)\s+(?:\((?P<tgid>.+)\)\s+)?\[(?P<cpu>\d+)\]\s+(?:(?P<flags>\S{4})\s+)?(?P<timestamp>[0-9.]+):\s+(?P<function>.+)$')
|
||||
|
||||
"""
|
||||
Example lines from custom app traces:
|
||||
0: B|27295|providerRemove
|
||||
0: E
|
||||
tracing_mark_write: S|27311|NNFColdStart<D-7744962>|1112249168
|
||||
"""
|
||||
APP_TRACE_LINE_PATTERN = re.compile(
|
||||
r'^(?P<type>.+?): (?P<args>.+)$')
|
||||
|
||||
"""
|
||||
Example section names:
|
||||
NNFColdStart
|
||||
NNFColdStart<0><T7744962>
|
||||
NNFColdStart<X>
|
||||
NNFColdStart<T7744962>
|
||||
"""
|
||||
DECORATED_SECTION_NAME_PATTERN = re.compile(r'^(?P<section_name>.*?)(?:<0>)?(?:<(?P<command>.)(?P<argument>.*?)>)?$')
|
||||
|
||||
SYSTRACE_LINE_TYPES = set(['0', 'tracing_mark_write'])
|
||||
|
||||
class TraceLine(object):
|
||||
def __init__(self, task, pid, tgid, cpu, flags, timestamp, function):
|
||||
self.task = task
|
||||
self.pid = pid
|
||||
self.tgid = tgid
|
||||
self.cpu = cpu
|
||||
self.flags = flags
|
||||
self.timestamp = timestamp
|
||||
self.function = function
|
||||
self.canceled = False
|
||||
|
||||
@property
|
||||
def is_app_trace_line(self):
|
||||
return isinstance(self.function, AppTraceFunction)
|
||||
|
||||
def cancel(self):
|
||||
self.canceled = True
|
||||
|
||||
def __str__(self):
|
||||
if self.canceled:
|
||||
return ""
|
||||
elif self.tgid:
|
||||
return "{task:>16s}-{pid:<5d} ({tgid:5s}) [{cpu:03d}] {flags:4s} {timestamp:12f}: {function}\n".format(**vars(self))
|
||||
elif self.flags:
|
||||
return "{task:>16s}-{pid:<5d} [{cpu:03d}] {flags:4s} {timestamp:12f}: {function}\n".format(**vars(self))
|
||||
else:
|
||||
return "{task:>16s}-{pid:<5d} [{cpu:03d}] {timestamp:12.6f}: {function}\n".format(**vars(self))
|
||||
|
||||
|
||||
class AppTraceFunction(object):
|
||||
def __init__(self, type, args):
|
||||
self.type = type
|
||||
self.args = args
|
||||
self.operation = args[0]
|
||||
|
||||
if len(args) >= 2 and args[1]:
|
||||
self.pid = int(args[1])
|
||||
if len(args) >= 3:
|
||||
self._section_name, self.command, self.argument = _parse_section_name(args[2])
|
||||
args[2] = self._section_name
|
||||
else:
|
||||
self._section_name = None
|
||||
self.command = None
|
||||
self.argument = None
|
||||
self.cookie = None
|
||||
|
||||
@property
|
||||
def section_name(self):
|
||||
return self._section_name
|
||||
|
||||
@section_name.setter
|
||||
def section_name(self, value):
|
||||
self._section_name = value
|
||||
self.args[2] = value
|
||||
|
||||
def __str__(self):
|
||||
return "{type}: {args}".format(type=self.type, args='|'.join(self.args))
|
||||
|
||||
|
||||
class AsyncTraceFunction(AppTraceFunction):
|
||||
def __init__(self, type, args):
|
||||
super(AsyncTraceFunction, self).__init__(type, args)
|
||||
|
||||
self.cookie = int(args[3])
|
||||
|
||||
|
||||
TRACE_TYPE_MAP = {
|
||||
'S': AsyncTraceFunction,
|
||||
'T': AsyncTraceFunction,
|
||||
'F': AsyncTraceFunction,
|
||||
}
|
||||
|
||||
def parse_line(line):
|
||||
match = TRACE_LINE_PATTERN.match(line.strip())
|
||||
if not match:
|
||||
return None
|
||||
|
||||
task = match.group("task")
|
||||
pid = int(match.group("pid"))
|
||||
tgid = match.group("tgid")
|
||||
cpu = int(match.group("cpu"))
|
||||
flags = match.group("flags")
|
||||
timestamp = float(match.group("timestamp"))
|
||||
function = match.group("function")
|
||||
|
||||
app_trace = _parse_function(function)
|
||||
if app_trace:
|
||||
function = app_trace
|
||||
|
||||
return TraceLine(task, pid, tgid, cpu, flags, timestamp, function)
|
||||
|
||||
def parse_dextr_line(line):
|
||||
task = line["name"]
|
||||
pid = line["pid"]
|
||||
tgid = line["tid"]
|
||||
cpu = None
|
||||
flags = None
|
||||
timestamp = line["ts"]
|
||||
function = AppTraceFunction("DextrTrace", [line["ph"], line["pid"], line["name"]])
|
||||
|
||||
return TraceLine(task, pid, tgid, cpu, flags, timestamp, function)
|
||||
|
||||
|
||||
def _parse_function(function):
|
||||
line_match = APP_TRACE_LINE_PATTERN.match(function)
|
||||
if not line_match:
|
||||
return None
|
||||
|
||||
type = line_match.group("type")
|
||||
if not type in SYSTRACE_LINE_TYPES:
|
||||
return None
|
||||
|
||||
args = line_match.group("args").split('|')
|
||||
if len(args) == 1 and len(args[0]) == 0:
|
||||
args = None
|
||||
|
||||
constructor = TRACE_TYPE_MAP.get(args[0], AppTraceFunction)
|
||||
return constructor(type, args)
|
||||
|
||||
|
||||
def _parse_section_name(section_name):
|
||||
if section_name is None:
|
||||
return section_name, None, None
|
||||
|
||||
section_name_match = DECORATED_SECTION_NAME_PATTERN.match(section_name)
|
||||
section_name = section_name_match.group("section_name")
|
||||
command = section_name_match.group("command")
|
||||
argument = section_name_match.group("argument")
|
||||
return section_name, command, argument
|
||||
|
||||
|
||||
def _format_section_name(section_name, command, argument):
|
||||
if not command:
|
||||
return section_name
|
||||
|
||||
return "{section_name}<{command}{argument}>".format(**vars())
|
||||
|
||||
|
||||
class RoundTripFormattingTests(unittest.TestCase):
|
||||
def testPlainSectionName(self):
|
||||
section_name = "SectionName12345-5562342fas"
|
||||
|
||||
self.assertEqual(section_name, _format_section_name(*_parse_section_name(section_name)))
|
||||
|
||||
def testDecoratedSectionName(self):
|
||||
section_name = "SectionName12345-5562342fas<D-123456>"
|
||||
|
||||
self.assertEqual(section_name, _format_section_name(*_parse_section_name(section_name)))
|
||||
|
||||
def testSimpleFunction(self):
|
||||
function = "0: E"
|
||||
|
||||
self.assertEqual(function, str(_parse_function(function)))
|
||||
|
||||
def testFunctionWithoutCookie(self):
|
||||
function = "0: B|27295|providerRemove"
|
||||
|
||||
self.assertEqual(function, str(_parse_function(function)))
|
||||
|
||||
def testFunctionWithCookie(self):
|
||||
function = "0: S|27311|NNFColdStart|1112249168"
|
||||
|
||||
self.assertEqual(function, str(_parse_function(function)))
|
||||
|
||||
def testFunctionWithCookieAndArgs(self):
|
||||
function = "0: T|27311|NNFColdStart|1122|Start"
|
||||
|
||||
self.assertEqual(function, str(_parse_function(function)))
|
||||
|
||||
def testFunctionWithArgsButNoPid(self):
|
||||
function = "0: E|||foo=bar"
|
||||
|
||||
self.assertEqual(function, str(_parse_function(function)))
|
||||
|
||||
def testKitKatFunction(self):
|
||||
function = "tracing_mark_write: B|14127|Looper.dispatchMessage|arg=>>>>> Dispatching to Handler (android.os.Handler) {422ae980} null: 0|Java"
|
||||
|
||||
self.assertEqual(function, str(_parse_function(function)))
|
||||
|
||||
def testNonSysTraceFunctionIgnored(self):
|
||||
function = "sched_switch: prev_comm=swapper/1 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=mmcqd/0 next_pid=120 next_prio=120"
|
||||
|
||||
self.assertEqual(None, _parse_function(function))
|
||||
|
||||
def testLineWithFlagsAndTGID(self):
|
||||
line = " <idle>-0 ( 550) [000] d..2 7953.258473: cpu_idle: state=1 cpu_id=0\n"
|
||||
|
||||
self.assertEqual(line, str(parse_line(line)))
|
||||
|
||||
def testLineWithFlagsAndNoTGID(self):
|
||||
line = " <idle>-0 (-----) [000] d..2 7953.258473: cpu_idle: state=1 cpu_id=0\n"
|
||||
|
||||
self.assertEqual(line, str(parse_line(line)))
|
||||
|
||||
def testLineWithFlags(self):
|
||||
line = " <idle>-0 [001] ...2 3269.291072: sched_switch: prev_comm=swapper/1 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=mmcqd/0 next_pid=120 next_prio=120\n"
|
||||
|
||||
self.assertEqual(line, str(parse_line(line)))
|
||||
|
||||
def testLineWithoutFlags(self):
|
||||
line = " <idle>-0 [001] 3269.291072: sched_switch: prev_comm=swapper/1 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=mmcqd/0 next_pid=120 next_prio=120\n"
|
||||
|
||||
self.assertEqual(line, str(parse_line(line)))
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"rules": {
|
||||
// This folder currently runs through babel and doesn't need to be
|
||||
// compatible with node 4
|
||||
"comma-dangle": 0
|
||||
}
|
||||
}
|
||||
@@ -152,10 +152,7 @@
|
||||
0CF68AF71AF0549300FF9E5C /* RCTConvert+ART.m */,
|
||||
0CF68AC21AF0540F00FF9E5C /* Products */,
|
||||
);
|
||||
indentWidth = 2;
|
||||
sourceTree = "<group>";
|
||||
tabWidth = 2;
|
||||
usesTabs = 0;
|
||||
};
|
||||
0CF68AC21AF0540F00FF9E5C /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
|
||||
@@ -15,15 +15,6 @@
|
||||
|
||||
@implementation ARTSurfaceView
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame
|
||||
{
|
||||
if (self = [super initWithFrame:frame]) {
|
||||
self.opaque = NO;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)insertReactSubview:(UIView *)subview atIndex:(NSInteger)atIndex
|
||||
{
|
||||
[super insertReactSubview:subview atIndex:atIndex];
|
||||
@@ -55,4 +46,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (void)reactSetInheritedBackgroundColor:(UIColor *)inheritedBackgroundColor
|
||||
{
|
||||
self.backgroundColor = inheritedBackgroundColor;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
CGPathAddArc(path, NULL, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE == 0);
|
||||
break;
|
||||
default:
|
||||
RCTLogError(@"Invalid CGPath type %llu at element %llu of %@", (unsigned long long)type, (unsigned long long)i, arr);
|
||||
RCTLogError(@"Invalid CGPath type %zd at element %zd of %@", type, i, arr);
|
||||
CGPathRelease(path);
|
||||
return NULL;
|
||||
}
|
||||
@@ -158,7 +158,7 @@ RCT_ENUM_CONVERTER(CTTextAlignment, (@{
|
||||
case 3: // pattern
|
||||
return [[ARTPattern alloc] initWithArray:arr];
|
||||
default:
|
||||
RCTLogError(@"Unknown brush type: %llu", (unsigned long long)type);
|
||||
RCTLogError(@"Unknown brush type: %zd", type);
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
@@ -167,7 +167,7 @@ RCT_ENUM_CONVERTER(CTTextAlignment, (@{
|
||||
{
|
||||
NSArray *arr = [self NSArray:json];
|
||||
if (arr.count < offset + 2) {
|
||||
RCTLogError(@"Too few elements in array (expected at least %llu): %@", (unsigned long long)(2 + offset), arr);
|
||||
RCTLogError(@"Too few elements in array (expected at least %zd): %@", 2 + offset, arr);
|
||||
return CGPointZero;
|
||||
}
|
||||
return (CGPoint){
|
||||
@@ -180,7 +180,7 @@ RCT_ENUM_CONVERTER(CTTextAlignment, (@{
|
||||
{
|
||||
NSArray *arr = [self NSArray:json];
|
||||
if (arr.count < offset + 4) {
|
||||
RCTLogError(@"Too few elements in array (expected at least %llu): %@", (unsigned long long)(4 + offset), arr);
|
||||
RCTLogError(@"Too few elements in array (expected at least %zd): %@", 4 + offset, arr);
|
||||
return CGRectZero;
|
||||
}
|
||||
return (CGRect){
|
||||
@@ -193,7 +193,7 @@ RCT_ENUM_CONVERTER(CTTextAlignment, (@{
|
||||
{
|
||||
NSArray *arr = [self NSArray:json];
|
||||
if (arr.count < offset + 4) {
|
||||
RCTLogError(@"Too few elements in array (expected at least %llu): %@", (unsigned long long)(4 + offset), arr);
|
||||
RCTLogError(@"Too few elements in array (expected at least %zd): %@", 4 + offset, arr);
|
||||
return NULL;
|
||||
}
|
||||
return [self CGColor:[arr subarrayWithRange:(NSRange){offset, 4}]];
|
||||
@@ -203,7 +203,7 @@ RCT_ENUM_CONVERTER(CTTextAlignment, (@{
|
||||
{
|
||||
NSArray *arr = [self NSArray:json];
|
||||
if (arr.count < offset) {
|
||||
RCTLogError(@"Too few elements in array (expected at least %llu): %@", (unsigned long long)offset, arr);
|
||||
RCTLogError(@"Too few elements in array (expected at least %zd): %@", offset, arr);
|
||||
return NULL;
|
||||
}
|
||||
arr = [arr subarrayWithRange:(NSRange){offset, arr.count - offset}];
|
||||
|
||||
@@ -101,29 +101,25 @@ var TextAttributes = merge(RenderableAttributes, {
|
||||
|
||||
// Native Components
|
||||
|
||||
var NativeSurfaceView = createReactNativeComponentClass('ARTSurfaceView',
|
||||
() => ({
|
||||
validAttributes: SurfaceViewAttributes,
|
||||
uiViewClassName: 'ARTSurfaceView',
|
||||
}));
|
||||
var NativeSurfaceView = createReactNativeComponentClass({
|
||||
validAttributes: SurfaceViewAttributes,
|
||||
uiViewClassName: 'ARTSurfaceView',
|
||||
});
|
||||
|
||||
var NativeGroup = createReactNativeComponentClass('ARTGroup',
|
||||
() => ({
|
||||
validAttributes: GroupAttributes,
|
||||
uiViewClassName: 'ARTGroup',
|
||||
}));
|
||||
var NativeGroup = createReactNativeComponentClass({
|
||||
validAttributes: GroupAttributes,
|
||||
uiViewClassName: 'ARTGroup',
|
||||
});
|
||||
|
||||
var NativeShape = createReactNativeComponentClass('ARTShape',
|
||||
() => ({
|
||||
validAttributes: ShapeAttributes,
|
||||
uiViewClassName: 'ARTShape',
|
||||
}));
|
||||
var NativeShape = createReactNativeComponentClass({
|
||||
validAttributes: ShapeAttributes,
|
||||
uiViewClassName: 'ARTShape',
|
||||
});
|
||||
|
||||
var NativeText = createReactNativeComponentClass('ARTText',
|
||||
() => ({
|
||||
validAttributes: TextAttributes,
|
||||
uiViewClassName: 'ARTText',
|
||||
}));
|
||||
var NativeText = createReactNativeComponentClass({
|
||||
validAttributes: TextAttributes,
|
||||
uiViewClassName: 'ARTText',
|
||||
});
|
||||
|
||||
// Utilities
|
||||
|
||||
|
||||
@@ -8,58 +8,37 @@
|
||||
*
|
||||
* @providesModule ActionSheetIOS
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const RCTActionSheetManager = require('NativeModules').ActionSheetManager;
|
||||
var RCTActionSheetManager = require('NativeModules').ActionSheetManager;
|
||||
|
||||
const invariant = require('fbjs/lib/invariant');
|
||||
const processColor = require('processColor');
|
||||
var invariant = require('fbjs/lib/invariant');
|
||||
var processColor = require('processColor');
|
||||
|
||||
/**
|
||||
* Display action sheets and share sheets on iOS.
|
||||
*
|
||||
* See http://facebook.github.io/react-native/docs/actionsheetios.html
|
||||
*/
|
||||
const ActionSheetIOS = {
|
||||
var ActionSheetIOS = {
|
||||
/**
|
||||
* Display an iOS action sheet.
|
||||
*
|
||||
* The `options` object must contain one or more of:
|
||||
* Display an iOS action sheet. The `options` object must contain one or more
|
||||
* of:
|
||||
*
|
||||
* - `options` (array of strings) - a list of button titles (required)
|
||||
* - `cancelButtonIndex` (int) - index of cancel button in `options`
|
||||
* - `destructiveButtonIndex` (int) - index of destructive button in `options`
|
||||
* - `title` (string) - a title to show above the action sheet
|
||||
* - `message` (string) - a message to show below the title
|
||||
*
|
||||
* The 'callback' function takes one parameter, the zero-based index
|
||||
* of the selected item.
|
||||
*
|
||||
* See http://facebook.github.io/react-native/docs/actionsheetios.html#showactionsheetwithoptions
|
||||
*/
|
||||
showActionSheetWithOptions(
|
||||
options: {|
|
||||
+title?: ?string,
|
||||
+message?: ?string,
|
||||
+options: Array<string>,
|
||||
+destructiveButtonIndex?: ?number,
|
||||
+cancelButtonIndex?: ?number,
|
||||
+anchor?: ?number,
|
||||
+tintColor?: number | string,
|
||||
|},
|
||||
callback: (buttonIndex: number) => void,
|
||||
) {
|
||||
showActionSheetWithOptions(options: Object, callback: Function) {
|
||||
invariant(
|
||||
typeof options === 'object' && options !== null,
|
||||
'Options must be a valid object',
|
||||
'Options must be a valid object'
|
||||
);
|
||||
invariant(
|
||||
typeof callback === 'function',
|
||||
'Must provide a valid callback'
|
||||
);
|
||||
invariant(typeof callback === 'function', 'Must provide a valid callback');
|
||||
|
||||
RCTActionSheetManager.showActionSheetWithOptions(
|
||||
{...options, tintColor: processColor(options.tintColor)},
|
||||
callback,
|
||||
callback
|
||||
);
|
||||
},
|
||||
|
||||
@@ -71,44 +50,35 @@ const ActionSheetIOS = {
|
||||
* - `url` (string) - a URL to share
|
||||
* - `message` (string) - a message to share
|
||||
* - `subject` (string) - a subject for the message
|
||||
* - `excludedActivityTypes` (array) - the activities to exclude from
|
||||
* the ActionSheet
|
||||
* - `tintColor` (color) - tint color of the buttons
|
||||
* - `excludedActivityTypes` (array) - the activities to exclude from the ActionSheet
|
||||
*
|
||||
* The 'failureCallback' function takes one parameter, an error object.
|
||||
* The only property defined on this object is an optional `stack` property
|
||||
* of type `string`.
|
||||
*
|
||||
* The 'successCallback' function takes two parameters:
|
||||
*
|
||||
* - a boolean value signifying success or failure
|
||||
* - a string that, in the case of success, indicates the method of sharing
|
||||
*
|
||||
* See http://facebook.github.io/react-native/docs/actionsheetios.html#showshareactionsheetwithoptions
|
||||
* NOTE: if `url` points to a local file, or is a base64-encoded
|
||||
* uri, the file it points to will be loaded and shared directly.
|
||||
* In this way, you can share images, videos, PDF files, etc.
|
||||
*/
|
||||
showShareActionSheetWithOptions(
|
||||
options: Object,
|
||||
failureCallback: Function,
|
||||
successCallback: Function,
|
||||
successCallback: Function
|
||||
) {
|
||||
invariant(
|
||||
typeof options === 'object' && options !== null,
|
||||
'Options must be a valid object',
|
||||
'Options must be a valid object'
|
||||
);
|
||||
invariant(
|
||||
typeof failureCallback === 'function',
|
||||
'Must provide a valid failureCallback',
|
||||
'Must provide a valid failureCallback'
|
||||
);
|
||||
invariant(
|
||||
typeof successCallback === 'function',
|
||||
'Must provide a valid successCallback',
|
||||
'Must provide a valid successCallback'
|
||||
);
|
||||
RCTActionSheetManager.showShareActionSheetWithOptions(
|
||||
{...options, tintColor: processColor(options.tintColor)},
|
||||
failureCallback,
|
||||
successCallback,
|
||||
successCallback
|
||||
);
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = ActionSheetIOS;
|
||||
|
||||
@@ -35,7 +35,6 @@
|
||||
indentWidth = 2;
|
||||
sourceTree = "<group>";
|
||||
tabWidth = 2;
|
||||
usesTabs = 0;
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule AdSupportIOS
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var AdSupport = require('NativeModules').AdSupport;
|
||||
|
||||
/**
|
||||
* `AdSupport` provides access to the "advertising identifier". If you link this library
|
||||
* in your project, you may need to justify your use for this identifier when submitting
|
||||
* your application to the App Store.
|
||||
*
|
||||
* In order to use `AdSupport` in your project, you must link the `RCTAdSupport` library.
|
||||
* In Xcode, you can manually add the `RCTAdSupport.m` and `RCTAdSupport.h` files from
|
||||
* `node_modules/react-native/Libraries/AdSupport/` to the `Libraries/React/Base/` folder
|
||||
* of your current project.
|
||||
*
|
||||
* You can refer to [Linking](docs/linking-libraries-ios.html) for help.
|
||||
*
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
getAdvertisingId: function(onSuccess: Function, onFailure: Function) {
|
||||
AdSupport.getAdvertisingId(onSuccess, onFailure);
|
||||
},
|
||||
|
||||
getAdvertisingTrackingEnabled: function(onSuccess: Function, onFailure: Function) {
|
||||
AdSupport.getAdvertisingTrackingEnabled(onSuccess, onFailure);
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import <React/RCTBridgeModule.h>
|
||||
|
||||
@interface RCTAdSupport : NSObject <RCTBridgeModule>
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import "RCTAdSupport.h"
|
||||
|
||||
#import <AdSupport/ASIdentifierManager.h>
|
||||
|
||||
#import <React/RCTUtils.h>
|
||||
|
||||
@implementation RCTAdSupport
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
RCT_EXPORT_METHOD(getAdvertisingId:(RCTResponseSenderBlock)callback
|
||||
withErrorCallback:(RCTResponseErrorBlock)errorCallback)
|
||||
{
|
||||
NSUUID *advertisingIdentifier = [ASIdentifierManager sharedManager].advertisingIdentifier;
|
||||
if (advertisingIdentifier) {
|
||||
callback(@[advertisingIdentifier.UUIDString]);
|
||||
} else {
|
||||
errorCallback(RCTErrorWithMessage(@"Advertising identifier is unavailable."));
|
||||
}
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(getAdvertisingTrackingEnabled:(RCTResponseSenderBlock)callback
|
||||
withErrorCallback:(__unused RCTResponseSenderBlock)errorCallback)
|
||||
{
|
||||
callback(@[@([ASIdentifierManager sharedManager].advertisingTrackingEnabled)]);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,257 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 46;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
832C819C1AAF6E1A007FA2F7 /* RCTAdSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 832C819B1AAF6E1A007FA2F7 /* RCTAdSupport.m */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
832C817E1AAF6DEF007FA2F7 /* CopyFiles */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "include/$(PRODUCT_NAME)";
|
||||
dstSubfolderSpec = 16;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
832C81801AAF6DEF007FA2F7 /* libRCTAdSupport.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTAdSupport.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
832C819A1AAF6E1A007FA2F7 /* RCTAdSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = RCTAdSupport.h; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; };
|
||||
832C819B1AAF6E1A007FA2F7 /* RCTAdSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTAdSupport.m; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
832C817D1AAF6DEF007FA2F7 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
832C81771AAF6DEF007FA2F7 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
832C819A1AAF6E1A007FA2F7 /* RCTAdSupport.h */,
|
||||
832C819B1AAF6E1A007FA2F7 /* RCTAdSupport.m */,
|
||||
832C81811AAF6DEF007FA2F7 /* Products */,
|
||||
);
|
||||
indentWidth = 2;
|
||||
sourceTree = "<group>";
|
||||
tabWidth = 2;
|
||||
};
|
||||
832C81811AAF6DEF007FA2F7 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
832C81801AAF6DEF007FA2F7 /* libRCTAdSupport.a */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
832C817F1AAF6DEF007FA2F7 /* RCTAdSupport */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 832C81941AAF6DF0007FA2F7 /* Build configuration list for PBXNativeTarget "RCTAdSupport" */;
|
||||
buildPhases = (
|
||||
832C817C1AAF6DEF007FA2F7 /* Sources */,
|
||||
832C817D1AAF6DEF007FA2F7 /* Frameworks */,
|
||||
832C817E1AAF6DEF007FA2F7 /* CopyFiles */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = RCTAdSupport;
|
||||
productName = RCTAdSupport;
|
||||
productReference = 832C81801AAF6DEF007FA2F7 /* libRCTAdSupport.a */;
|
||||
productType = "com.apple.product-type.library.static";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
832C81781AAF6DEF007FA2F7 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastUpgradeCheck = 0620;
|
||||
ORGANIZATIONNAME = Facebook;
|
||||
TargetAttributes = {
|
||||
832C817F1AAF6DEF007FA2F7 = {
|
||||
CreatedOnToolsVersion = 6.2;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 832C817B1AAF6DEF007FA2F7 /* Build configuration list for PBXProject "RCTAdSupport" */;
|
||||
compatibilityVersion = "Xcode 3.2";
|
||||
developmentRegion = English;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
);
|
||||
mainGroup = 832C81771AAF6DEF007FA2F7;
|
||||
productRefGroup = 832C81811AAF6DEF007FA2F7 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
832C817F1AAF6DEF007FA2F7 /* RCTAdSupport */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
832C817C1AAF6DEF007FA2F7 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
832C819C1AAF6E1A007FA2F7 /* RCTAdSupport.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
832C81921AAF6DF0007FA2F7 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES;
|
||||
GCC_WARN_SHADOW = YES;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
SKIP_INSTALL = YES;
|
||||
WARNING_CFLAGS = (
|
||||
"-Werror",
|
||||
"-Wall",
|
||||
);
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
832C81931AAF6DF0007FA2F7 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES;
|
||||
GCC_WARN_SHADOW = YES;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SKIP_INSTALL = YES;
|
||||
VALIDATE_PRODUCT = YES;
|
||||
WARNING_CFLAGS = (
|
||||
"-Werror",
|
||||
"-Wall",
|
||||
);
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
832C81951AAF6DF0007FA2F7 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_STATIC_ANALYZER_MODE = deep;
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
RUN_CLANG_STATIC_ANALYZER = YES;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
832C81961AAF6DF0007FA2F7 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_STATIC_ANALYZER_MODE = deep;
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
832C817B1AAF6DEF007FA2F7 /* Build configuration list for PBXProject "RCTAdSupport" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
832C81921AAF6DF0007FA2F7 /* Debug */,
|
||||
832C81931AAF6DF0007FA2F7 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
832C81941AAF6DF0007FA2F7 /* Build configuration list for PBXNativeTarget "RCTAdSupport" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
832C81951AAF6DF0007FA2F7 /* Debug */,
|
||||
832C81961AAF6DF0007FA2F7 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 832C81781AAF6DEF007FA2F7 /* Project object */;
|
||||
}
|
||||
@@ -17,7 +17,7 @@ const Platform = require('Platform');
|
||||
|
||||
import type { AlertType, AlertButtonStyle } from 'AlertIOS';
|
||||
|
||||
export type Buttons = Array<{
|
||||
type Buttons = Array<{
|
||||
text?: string,
|
||||
onPress?: ?Function,
|
||||
style?: AlertButtonStyle,
|
||||
@@ -30,16 +30,54 @@ type Options = {
|
||||
|
||||
/**
|
||||
* Launches an alert dialog with the specified title and message.
|
||||
*
|
||||
* See http://facebook.github.io/react-native/docs/alert.html
|
||||
*
|
||||
* Optionally provide a list of buttons. Tapping any button will fire the
|
||||
* respective onPress callback and dismiss the alert. By default, the only
|
||||
* button will be an 'OK' button.
|
||||
*
|
||||
* This is an API that works both on iOS and Android and can show static
|
||||
* alerts. To show an alert that prompts the user to enter some information,
|
||||
* see `AlertIOS`; entering text in an alert is common on iOS only.
|
||||
*
|
||||
* ## iOS
|
||||
*
|
||||
* On iOS you can specify any number of buttons. Each button can optionally
|
||||
* specify a style, which is one of 'default', 'cancel' or 'destructive'.
|
||||
*
|
||||
* ## Android
|
||||
*
|
||||
* On Android at most three buttons can be specified. Android has a concept
|
||||
* of a neutral, negative and a positive button:
|
||||
*
|
||||
* - If you specify one button, it will be the 'positive' one (such as 'OK')
|
||||
* - Two buttons mean 'negative', 'positive' (such as 'Cancel', 'OK')
|
||||
* - Three buttons mean 'neutral', 'negative', 'positive' (such as 'Later', 'Cancel', 'OK')
|
||||
*
|
||||
* By default alerts on Android can be dismissed by tapping outside of the alert
|
||||
* box. This event can be handled by providing an optional `options` parameter,
|
||||
* with an `onDismiss` callback property `{ onDismiss: () => {} }`.
|
||||
*
|
||||
* Alternatively, the dismissing behavior can be disabled altogether by providing
|
||||
* an optional `options` parameter with the `cancelable` property set to `false`
|
||||
* i.e. `{ cancelable: false }`
|
||||
*
|
||||
* Example usage:
|
||||
* ```
|
||||
* // Works on both iOS and Android
|
||||
* Alert.alert(
|
||||
* 'Alert Title',
|
||||
* 'My Alert Msg',
|
||||
* [
|
||||
* {text: 'Ask me later', onPress: () => console.log('Ask me later pressed')},
|
||||
* {text: 'Cancel', onPress: () => console.log('Cancel Pressed'), style: 'cancel'},
|
||||
* {text: 'OK', onPress: () => console.log('OK Pressed')},
|
||||
* ],
|
||||
* { cancelable: false }
|
||||
* )
|
||||
* ```
|
||||
*/
|
||||
class Alert {
|
||||
|
||||
/**
|
||||
* Launches an alert dialog with the specified title and message.
|
||||
*
|
||||
* See http://facebook.github.io/react-native/docs/alert.html#alert
|
||||
*/
|
||||
static alert(
|
||||
title: ?string,
|
||||
message?: ?string,
|
||||
|
||||
@@ -77,15 +77,60 @@ export type ButtonsArray = Array<{
|
||||
}>;
|
||||
|
||||
/**
|
||||
* Use `AlertIOS` to display an alert dialog with a message or to create a prompt for user input on iOS. If you don't need to prompt for user input, we recommend using `Alert.alert() for cross-platform support.
|
||||
* @description
|
||||
* `AlertIOS` provides functionality to create an iOS alert dialog with a
|
||||
* message or create a prompt for user input.
|
||||
*
|
||||
* Creating an iOS alert:
|
||||
*
|
||||
* ```
|
||||
* AlertIOS.alert(
|
||||
* 'Sync Complete',
|
||||
* 'All your data are belong to us.'
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* Creating an iOS prompt:
|
||||
*
|
||||
* ```
|
||||
* AlertIOS.prompt(
|
||||
* 'Enter a value',
|
||||
* null,
|
||||
* text => console.log("You entered "+text)
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* We recommend using the [`Alert.alert`](docs/alert.html) method for
|
||||
* cross-platform support if you don't need to create iOS-only prompts.
|
||||
*
|
||||
* See http://facebook.github.io/react-native/docs/alertios.html
|
||||
*/
|
||||
class AlertIOS {
|
||||
/**
|
||||
* Create and display a popup alert.
|
||||
* @static
|
||||
* @method alert
|
||||
* @param title The dialog's title.
|
||||
* @param message An optional message that appears below
|
||||
* the dialog's title.
|
||||
* @param callbackOrButtons This optional argument should
|
||||
* be either a single-argument function or an array of buttons. If passed
|
||||
* a function, it will be called when the user taps 'OK'.
|
||||
*
|
||||
* See http://facebook.github.io/react-native/docs/alertios.html#alert
|
||||
* If passed an array of button configurations, each button should include
|
||||
* a `text` key, as well as optional `onPress` and `style` keys. `style`
|
||||
* should be one of 'default', 'cancel' or 'destructive'.
|
||||
* @param type Deprecated, do not use.
|
||||
*
|
||||
* @example <caption>Example with custom buttons</caption>
|
||||
*
|
||||
* AlertIOS.alert(
|
||||
* 'Update available',
|
||||
* 'Keep your app up to date to enjoy the latest features',
|
||||
* [
|
||||
* {text: 'Cancel', onPress: () => console.log('Cancel Pressed'), style: 'cancel'},
|
||||
* {text: 'Install', onPress: () => console.log('Install Pressed')},
|
||||
* ],
|
||||
* );
|
||||
*/
|
||||
static alert(
|
||||
title: ?string,
|
||||
@@ -103,8 +148,48 @@ class AlertIOS {
|
||||
|
||||
/**
|
||||
* Create and display a prompt to enter some text.
|
||||
*
|
||||
* See http://facebook.github.io/react-native/docs/alertios.html#prompt
|
||||
* @static
|
||||
* @method prompt
|
||||
* @param title The dialog's title.
|
||||
* @param message An optional message that appears above the text
|
||||
* input.
|
||||
* @param callbackOrButtons This optional argument should
|
||||
* be either a single-argument function or an array of buttons. If passed
|
||||
* a function, it will be called with the prompt's value when the user
|
||||
* taps 'OK'.
|
||||
*
|
||||
* If passed an array of button configurations, each button should include
|
||||
* a `text` key, as well as optional `onPress` and `style` keys (see
|
||||
* example). `style` should be one of 'default', 'cancel' or 'destructive'.
|
||||
* @param type This configures the text input. One of 'plain-text',
|
||||
* 'secure-text' or 'login-password'.
|
||||
* @param defaultValue The default text in text input.
|
||||
* @param keyboardType The keyboard type of first text field(if exists).
|
||||
* One of 'default', 'email-address', 'numeric', 'phone-pad',
|
||||
* 'ascii-capable', 'numbers-and-punctuation', 'url', 'number-pad',
|
||||
* 'name-phone-pad', 'decimal-pad', 'twitter' or 'web-search'.
|
||||
*
|
||||
* @example <caption>Example with custom buttons</caption>
|
||||
*
|
||||
* AlertIOS.prompt(
|
||||
* 'Enter password',
|
||||
* 'Enter your password to claim your $1.5B in lottery winnings',
|
||||
* [
|
||||
* {text: 'Cancel', onPress: () => console.log('Cancel Pressed'), style: 'cancel'},
|
||||
* {text: 'OK', onPress: password => console.log('OK Pressed, password: ' + password)},
|
||||
* ],
|
||||
* 'secure-text'
|
||||
* );
|
||||
*
|
||||
* @example <caption>Example with the default button and a custom callback</caption>
|
||||
*
|
||||
* AlertIOS.prompt(
|
||||
* 'Update username',
|
||||
* null,
|
||||
* text => console.log("Your username is "+text),
|
||||
* null,
|
||||
* 'default'
|
||||
* );
|
||||
*/
|
||||
static prompt(
|
||||
title: ?string,
|
||||
@@ -124,7 +209,7 @@ class AlertIOS {
|
||||
var callback = type;
|
||||
var defaultValue = message;
|
||||
RCTAlertManager.alertWithArgs({
|
||||
title: title || '',
|
||||
title: title || undefined,
|
||||
type: 'plain-text',
|
||||
defaultValue,
|
||||
}, (id, value) => {
|
||||
@@ -157,7 +242,7 @@ class AlertIOS {
|
||||
}
|
||||
|
||||
RCTAlertManager.alertWithArgs({
|
||||
title: title || '',
|
||||
title: title || undefined,
|
||||
message: message || undefined,
|
||||
buttons,
|
||||
type: type || undefined,
|
||||
|
||||
@@ -1,196 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule AnimatedEvent
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const AnimatedValue = require('./nodes/AnimatedValue');
|
||||
const NativeAnimatedHelper = require('./NativeAnimatedHelper');
|
||||
const ReactNative = require('ReactNative');
|
||||
|
||||
const invariant = require('fbjs/lib/invariant');
|
||||
const {shouldUseNativeDriver} = require('./NativeAnimatedHelper');
|
||||
|
||||
export type Mapping = {[key: string]: Mapping} | AnimatedValue;
|
||||
export type EventConfig = {
|
||||
listener?: ?Function,
|
||||
useNativeDriver?: boolean,
|
||||
};
|
||||
|
||||
function attachNativeEvent(
|
||||
viewRef: any,
|
||||
eventName: string,
|
||||
argMapping: Array<?Mapping>,
|
||||
) {
|
||||
// Find animated values in `argMapping` and create an array representing their
|
||||
// key path inside the `nativeEvent` object. Ex.: ['contentOffset', 'x'].
|
||||
const eventMappings = [];
|
||||
|
||||
const traverse = (value, path) => {
|
||||
if (value instanceof AnimatedValue) {
|
||||
value.__makeNative();
|
||||
|
||||
eventMappings.push({
|
||||
nativeEventPath: path,
|
||||
animatedValueTag: value.__getNativeTag(),
|
||||
});
|
||||
} else if (typeof value === 'object') {
|
||||
for (const key in value) {
|
||||
traverse(value[key], path.concat(key));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
invariant(
|
||||
argMapping[0] && argMapping[0].nativeEvent,
|
||||
'Native driven events only support animated values contained inside `nativeEvent`.',
|
||||
);
|
||||
|
||||
// Assume that the event containing `nativeEvent` is always the first argument.
|
||||
traverse(argMapping[0].nativeEvent, []);
|
||||
|
||||
const viewTag = ReactNative.findNodeHandle(viewRef);
|
||||
|
||||
eventMappings.forEach(mapping => {
|
||||
NativeAnimatedHelper.API.addAnimatedEventToView(
|
||||
viewTag,
|
||||
eventName,
|
||||
mapping,
|
||||
);
|
||||
});
|
||||
|
||||
return {
|
||||
detach() {
|
||||
eventMappings.forEach(mapping => {
|
||||
NativeAnimatedHelper.API.removeAnimatedEventFromView(
|
||||
viewTag,
|
||||
eventName,
|
||||
mapping.animatedValueTag,
|
||||
);
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
class AnimatedEvent {
|
||||
_argMapping: Array<?Mapping>;
|
||||
_listeners: Array<Function> = [];
|
||||
_callListeners: Function;
|
||||
_attachedEvent: ?{
|
||||
detach: () => void,
|
||||
};
|
||||
__isNative: boolean;
|
||||
|
||||
constructor(argMapping: Array<?Mapping>, config?: EventConfig = {}) {
|
||||
this._argMapping = argMapping;
|
||||
if (config.listener) {
|
||||
this.__addListener(config.listener);
|
||||
}
|
||||
this._callListeners = this._callListeners.bind(this);
|
||||
this._attachedEvent = null;
|
||||
this.__isNative = shouldUseNativeDriver(config);
|
||||
|
||||
if (__DEV__) {
|
||||
this._validateMapping();
|
||||
}
|
||||
}
|
||||
|
||||
__addListener(callback: Function): void {
|
||||
this._listeners.push(callback);
|
||||
}
|
||||
|
||||
__removeListener(callback: Function): void {
|
||||
this._listeners = this._listeners.filter(listener => listener !== callback);
|
||||
}
|
||||
|
||||
__attach(viewRef: any, eventName: string) {
|
||||
invariant(
|
||||
this.__isNative,
|
||||
'Only native driven events need to be attached.',
|
||||
);
|
||||
|
||||
this._attachedEvent = attachNativeEvent(
|
||||
viewRef,
|
||||
eventName,
|
||||
this._argMapping,
|
||||
);
|
||||
}
|
||||
|
||||
__detach(viewTag: any, eventName: string) {
|
||||
invariant(
|
||||
this.__isNative,
|
||||
'Only native driven events need to be detached.',
|
||||
);
|
||||
|
||||
this._attachedEvent && this._attachedEvent.detach();
|
||||
}
|
||||
|
||||
__getHandler() {
|
||||
if (this.__isNative) {
|
||||
return this._callListeners;
|
||||
}
|
||||
|
||||
return (...args: any) => {
|
||||
const traverse = (recMapping, recEvt, key) => {
|
||||
if (typeof recEvt === 'number' && recMapping instanceof AnimatedValue) {
|
||||
recMapping.setValue(recEvt);
|
||||
} else if (typeof recMapping === 'object') {
|
||||
for (const mappingKey in recMapping) {
|
||||
/* $FlowFixMe(>=0.53.0 site=react_native_fb,react_native_oss) This
|
||||
* comment suppresses an error when upgrading Flow's support for
|
||||
* React. To see the error delete this comment and run Flow. */
|
||||
traverse(recMapping[mappingKey], recEvt[mappingKey], mappingKey);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (!this.__isNative) {
|
||||
this._argMapping.forEach((mapping, idx) => {
|
||||
traverse(mapping, args[idx], 'arg' + idx);
|
||||
});
|
||||
}
|
||||
this._callListeners(...args);
|
||||
};
|
||||
}
|
||||
|
||||
_callListeners(...args) {
|
||||
this._listeners.forEach(listener => listener(...args));
|
||||
}
|
||||
|
||||
_validateMapping() {
|
||||
const traverse = (recMapping, recEvt, key) => {
|
||||
if (typeof recEvt === 'number') {
|
||||
invariant(
|
||||
recMapping instanceof AnimatedValue,
|
||||
'Bad mapping of type ' +
|
||||
typeof recMapping +
|
||||
' for key ' +
|
||||
key +
|
||||
', event value must map to AnimatedValue',
|
||||
);
|
||||
return;
|
||||
}
|
||||
invariant(
|
||||
typeof recMapping === 'object',
|
||||
'Bad mapping of type ' + typeof recMapping + ' for key ' + key,
|
||||
);
|
||||
invariant(
|
||||
typeof recEvt === 'object',
|
||||
'Bad event of type ' + typeof recEvt + ' for key ' + key,
|
||||
);
|
||||
for (const mappingKey in recMapping) {
|
||||
traverse(recMapping[mappingKey], recEvt[mappingKey], mappingKey);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {AnimatedEvent, attachNativeEvent};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -62,14 +62,14 @@ class Easing {
|
||||
/**
|
||||
* A stepping function, returns 1 for any positive value of `n`.
|
||||
*/
|
||||
static step0(n: number) {
|
||||
static step0(n) {
|
||||
return n > 0 ? 1 : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* A stepping function, returns 1 if `n` is greater than or equal to 1.
|
||||
*/
|
||||
static step1(n: number) {
|
||||
static step1(n) {
|
||||
return n >= 1 ? 1 : 0;
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ class Easing {
|
||||
*
|
||||
* http://cubic-bezier.com/#0,0,1,1
|
||||
*/
|
||||
static linear(t: number) {
|
||||
static linear(t) {
|
||||
return t;
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ class Easing {
|
||||
*
|
||||
* http://easings.net/#easeInQuad
|
||||
*/
|
||||
static quad(t: number) {
|
||||
static quad(t) {
|
||||
return t * t;
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@ class Easing {
|
||||
*
|
||||
* http://easings.net/#easeInCubic
|
||||
*/
|
||||
static cubic(t: number) {
|
||||
static cubic(t) {
|
||||
return t * t * t;
|
||||
}
|
||||
|
||||
@@ -122,8 +122,8 @@ class Easing {
|
||||
* n = 4: http://easings.net/#easeInQuart
|
||||
* n = 5: http://easings.net/#easeInQuint
|
||||
*/
|
||||
static poly(n: number) {
|
||||
return (t: number) => Math.pow(t, n);
|
||||
static poly(n) {
|
||||
return (t) => Math.pow(t, n);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -131,7 +131,7 @@ class Easing {
|
||||
*
|
||||
* http://easings.net/#easeInSine
|
||||
*/
|
||||
static sin(t: number) {
|
||||
static sin(t) {
|
||||
return 1 - Math.cos(t * Math.PI / 2);
|
||||
}
|
||||
|
||||
@@ -140,7 +140,7 @@ class Easing {
|
||||
*
|
||||
* http://easings.net/#easeInCirc
|
||||
*/
|
||||
static circle(t: number) {
|
||||
static circle(t) {
|
||||
return 1 - Math.sqrt(1 - t * t);
|
||||
}
|
||||
|
||||
@@ -149,7 +149,7 @@ class Easing {
|
||||
*
|
||||
* http://easings.net/#easeInExpo
|
||||
*/
|
||||
static exp(t: number) {
|
||||
static exp(t) {
|
||||
return Math.pow(2, 10 * (t - 1));
|
||||
}
|
||||
|
||||
@@ -162,6 +162,11 @@ class Easing {
|
||||
* times.
|
||||
*
|
||||
* http://easings.net/#easeInElastic
|
||||
*
|
||||
* Wolfram Plots:
|
||||
*
|
||||
* - http://tiny.cc/elastic_b_1 (bounciness = 1, default)
|
||||
* - http://tiny.cc/elastic_b_3 (bounciness = 3)
|
||||
*/
|
||||
static elastic(bounciness: number = 1): (t: number) => number {
|
||||
const p = bounciness * Math.PI;
|
||||
|
||||
+79
-168
@@ -6,19 +6,14 @@
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule AnimatedInterpolation
|
||||
* @providesModule Interpolation
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
/* eslint no-bitwise: 0 */
|
||||
'use strict';
|
||||
|
||||
const AnimatedNode = require('./AnimatedNode');
|
||||
const AnimatedWithChildren = require('./AnimatedWithChildren');
|
||||
const NativeAnimatedHelper = require('../NativeAnimatedHelper');
|
||||
|
||||
const invariant = require('fbjs/lib/invariant');
|
||||
const normalizeColor = require('normalizeColor');
|
||||
var invariant = require('fbjs/lib/invariant');
|
||||
var normalizeColor = require('normalizeColor');
|
||||
|
||||
type ExtrapolateType = 'extend' | 'identity' | 'clamp';
|
||||
|
||||
@@ -28,76 +23,74 @@ export type InterpolationConfigType = {
|
||||
* detected during the deployment of v0.38.0. To see the error, remove this
|
||||
* comment and run flow
|
||||
*/
|
||||
outputRange: Array<number> | Array<string>,
|
||||
easing?: (input: number) => number,
|
||||
outputRange: (Array<number> | Array<string>),
|
||||
easing?: ((input: number) => number),
|
||||
extrapolate?: ExtrapolateType,
|
||||
extrapolateLeft?: ExtrapolateType,
|
||||
extrapolateRight?: ExtrapolateType,
|
||||
};
|
||||
|
||||
const linear = t => t;
|
||||
var linear = (t) => t;
|
||||
|
||||
/**
|
||||
* Very handy helper to map input ranges to output ranges with an easing
|
||||
* function and custom behavior outside of the ranges.
|
||||
*/
|
||||
function createInterpolation(
|
||||
config: InterpolationConfigType,
|
||||
): (input: number) => number | string {
|
||||
if (config.outputRange && typeof config.outputRange[0] === 'string') {
|
||||
return createInterpolationFromStringOutputRange(config);
|
||||
}
|
||||
class Interpolation {
|
||||
static create(config: InterpolationConfigType): (input: number) => number | string {
|
||||
|
||||
const outputRange: Array<number> = (config.outputRange: any);
|
||||
checkInfiniteRange('outputRange', outputRange);
|
||||
if (config.outputRange && typeof config.outputRange[0] === 'string') {
|
||||
return createInterpolationFromStringOutputRange(config);
|
||||
}
|
||||
|
||||
const inputRange = config.inputRange;
|
||||
checkInfiniteRange('inputRange', inputRange);
|
||||
checkValidInputRange(inputRange);
|
||||
var outputRange: Array<number> = (config.outputRange: any);
|
||||
checkInfiniteRange('outputRange', outputRange);
|
||||
|
||||
invariant(
|
||||
inputRange.length === outputRange.length,
|
||||
'inputRange (' +
|
||||
inputRange.length +
|
||||
') and outputRange (' +
|
||||
outputRange.length +
|
||||
') must have the same length',
|
||||
);
|
||||
var inputRange = config.inputRange;
|
||||
checkInfiniteRange('inputRange', inputRange);
|
||||
checkValidInputRange(inputRange);
|
||||
|
||||
const easing = config.easing || linear;
|
||||
|
||||
let extrapolateLeft: ExtrapolateType = 'extend';
|
||||
if (config.extrapolateLeft !== undefined) {
|
||||
extrapolateLeft = config.extrapolateLeft;
|
||||
} else if (config.extrapolate !== undefined) {
|
||||
extrapolateLeft = config.extrapolate;
|
||||
}
|
||||
|
||||
let extrapolateRight: ExtrapolateType = 'extend';
|
||||
if (config.extrapolateRight !== undefined) {
|
||||
extrapolateRight = config.extrapolateRight;
|
||||
} else if (config.extrapolate !== undefined) {
|
||||
extrapolateRight = config.extrapolate;
|
||||
}
|
||||
|
||||
return input => {
|
||||
invariant(
|
||||
typeof input === 'number',
|
||||
'Cannot interpolation an input which is not a number',
|
||||
inputRange.length === outputRange.length,
|
||||
'inputRange (' + inputRange.length + ') and outputRange (' +
|
||||
outputRange.length + ') must have the same length'
|
||||
);
|
||||
|
||||
const range = findRange(input, inputRange);
|
||||
return interpolate(
|
||||
input,
|
||||
inputRange[range],
|
||||
inputRange[range + 1],
|
||||
outputRange[range],
|
||||
outputRange[range + 1],
|
||||
easing,
|
||||
extrapolateLeft,
|
||||
extrapolateRight,
|
||||
);
|
||||
};
|
||||
var easing = config.easing || linear;
|
||||
|
||||
var extrapolateLeft: ExtrapolateType = 'extend';
|
||||
if (config.extrapolateLeft !== undefined) {
|
||||
extrapolateLeft = config.extrapolateLeft;
|
||||
} else if (config.extrapolate !== undefined) {
|
||||
extrapolateLeft = config.extrapolate;
|
||||
}
|
||||
|
||||
var extrapolateRight: ExtrapolateType = 'extend';
|
||||
if (config.extrapolateRight !== undefined) {
|
||||
extrapolateRight = config.extrapolateRight;
|
||||
} else if (config.extrapolate !== undefined) {
|
||||
extrapolateRight = config.extrapolate;
|
||||
}
|
||||
|
||||
return (input) => {
|
||||
invariant(
|
||||
typeof input === 'number',
|
||||
'Cannot interpolation an input which is not a number'
|
||||
);
|
||||
|
||||
var range = findRange(input, inputRange);
|
||||
return interpolate(
|
||||
input,
|
||||
inputRange[range],
|
||||
inputRange[range + 1],
|
||||
outputRange[range],
|
||||
outputRange[range + 1],
|
||||
easing,
|
||||
extrapolateLeft,
|
||||
extrapolateRight,
|
||||
);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function interpolate(
|
||||
@@ -106,11 +99,11 @@ function interpolate(
|
||||
inputMax: number,
|
||||
outputMin: number,
|
||||
outputMax: number,
|
||||
easing: (input: number) => number,
|
||||
easing: ((input: number) => number),
|
||||
extrapolateLeft: ExtrapolateType,
|
||||
extrapolateRight: ExtrapolateType,
|
||||
) {
|
||||
let result = input;
|
||||
var result = input;
|
||||
|
||||
// Extrapolate
|
||||
if (result < inputMin) {
|
||||
@@ -169,22 +162,22 @@ function interpolate(
|
||||
}
|
||||
|
||||
function colorToRgba(input: string): string {
|
||||
let int32Color = normalizeColor(input);
|
||||
var int32Color = normalizeColor(input);
|
||||
if (int32Color === null) {
|
||||
return input;
|
||||
}
|
||||
|
||||
int32Color = int32Color || 0;
|
||||
|
||||
const r = (int32Color & 0xff000000) >>> 24;
|
||||
const g = (int32Color & 0x00ff0000) >>> 16;
|
||||
const b = (int32Color & 0x0000ff00) >>> 8;
|
||||
const a = (int32Color & 0x000000ff) / 255;
|
||||
var r = (int32Color & 0xff000000) >>> 24;
|
||||
var g = (int32Color & 0x00ff0000) >>> 16;
|
||||
var b = (int32Color & 0x0000ff00) >>> 8;
|
||||
var a = (int32Color & 0x000000ff) / 255;
|
||||
|
||||
return `rgba(${r}, ${g}, ${b}, ${a})`;
|
||||
}
|
||||
|
||||
const stringShapeRegex = /[0-9\.-]+/g;
|
||||
var stringShapeRegex = /[0-9\.-]+/g;
|
||||
|
||||
/**
|
||||
* Supports string shapes by extracting numbers so new values can be computed,
|
||||
@@ -197,7 +190,7 @@ const stringShapeRegex = /[0-9\.-]+/g;
|
||||
function createInterpolationFromStringOutputRange(
|
||||
config: InterpolationConfigType,
|
||||
): (input: number) => string {
|
||||
let outputRange: Array<string> = (config.outputRange: any);
|
||||
var outputRange: Array<string> = (config.outputRange: any);
|
||||
invariant(outputRange.length >= 2, 'Bad output range');
|
||||
outputRange = outputRange.map(colorToRgba);
|
||||
checkPattern(outputRange);
|
||||
@@ -213,7 +206,7 @@ function createInterpolationFromStringOutputRange(
|
||||
/* $FlowFixMe(>=0.18.0): `outputRange[0].match()` can return `null`. Need to
|
||||
* guard against this possibility.
|
||||
*/
|
||||
const outputRanges = outputRange[0].match(stringShapeRegex).map(() => []);
|
||||
var outputRanges = outputRange[0].match(stringShapeRegex).map(() => []);
|
||||
outputRange.forEach(value => {
|
||||
/* $FlowFixMe(>=0.18.0): `value.match()` can return `null`. Need to guard
|
||||
* against this possibility.
|
||||
@@ -226,28 +219,25 @@ function createInterpolationFromStringOutputRange(
|
||||
/* $FlowFixMe(>=0.18.0): `outputRange[0].match()` can return `null`. Need to
|
||||
* guard against this possibility.
|
||||
*/
|
||||
const interpolations = outputRange[0]
|
||||
.match(stringShapeRegex)
|
||||
.map((value, i) => {
|
||||
return createInterpolation({
|
||||
...config,
|
||||
outputRange: outputRanges[i],
|
||||
});
|
||||
var interpolations = outputRange[0].match(stringShapeRegex).map((value, i) => {
|
||||
return Interpolation.create({
|
||||
...config,
|
||||
outputRange: outputRanges[i],
|
||||
});
|
||||
});
|
||||
|
||||
// rgba requires that the r,g,b are integers.... so we want to round them, but we *dont* want to
|
||||
// round the opacity (4th column).
|
||||
const shouldRound = isRgbOrRgba(outputRange[0]);
|
||||
|
||||
return input => {
|
||||
let i = 0;
|
||||
return (input) => {
|
||||
var i = 0;
|
||||
// 'rgba(0, 100, 200, 0)'
|
||||
// ->
|
||||
// 'rgba(${interpolations[0](input)}, ${interpolations[1](input)}, ...'
|
||||
return outputRange[0].replace(stringShapeRegex, () => {
|
||||
const val = +interpolations[i++](input);
|
||||
const rounded =
|
||||
shouldRound && i < 4 ? Math.round(val) : Math.round(val * 1000) / 1000;
|
||||
const rounded = shouldRound && i < 4 ? Math.round(val) : Math.round(val * 1000) / 1000;
|
||||
return String(rounded);
|
||||
});
|
||||
};
|
||||
@@ -258,8 +248,8 @@ function isRgbOrRgba(range) {
|
||||
}
|
||||
|
||||
function checkPattern(arr: Array<string>) {
|
||||
const pattern = arr[0].replace(stringShapeRegex, '');
|
||||
for (let i = 1; i < arr.length; ++i) {
|
||||
var pattern = arr[0].replace(stringShapeRegex, '');
|
||||
for (var i = 1; i < arr.length; ++i) {
|
||||
invariant(
|
||||
pattern === arr[i].replace(stringShapeRegex, ''),
|
||||
'invalid pattern ' + arr[0] + ' and ' + arr[i],
|
||||
@@ -268,8 +258,7 @@ function checkPattern(arr: Array<string>) {
|
||||
}
|
||||
|
||||
function findRange(input: number, inputRange: Array<number>) {
|
||||
let i;
|
||||
for (i = 1; i < inputRange.length - 1; ++i) {
|
||||
for (var i = 1; i < inputRange.length - 1; ++i) {
|
||||
if (inputRange[i] >= input) {
|
||||
break;
|
||||
}
|
||||
@@ -279,7 +268,7 @@ function findRange(input: number, inputRange: Array<number>) {
|
||||
|
||||
function checkValidInputRange(arr: Array<number>) {
|
||||
invariant(arr.length >= 2, 'inputRange must have at least 2 elements');
|
||||
for (let i = 1; i < arr.length; ++i) {
|
||||
for (var i = 1; i < arr.length; ++i) {
|
||||
invariant(
|
||||
arr[i] >= arr[i - 1],
|
||||
/* $FlowFixMe(>=0.13.0) - In the addition expression below this comment,
|
||||
@@ -288,7 +277,7 @@ function checkValidInputRange(arr: Array<number>) {
|
||||
* mean this implicit string conversion, you can do something like
|
||||
* String(myThing)
|
||||
*/
|
||||
'inputRange must be monotonically increasing ' + arr,
|
||||
'inputRange must be monotonically increasing ' + arr
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -303,86 +292,8 @@ function checkInfiniteRange(name: string, arr: Array<number>) {
|
||||
* this implicit string conversion, you can do something like
|
||||
* String(myThing)
|
||||
*/
|
||||
name + 'cannot be ]-infinity;+infinity[ ' + arr,
|
||||
name + 'cannot be ]-infinity;+infinity[ ' + arr
|
||||
);
|
||||
}
|
||||
|
||||
class AnimatedInterpolation extends AnimatedWithChildren {
|
||||
// Export for testing.
|
||||
static __createInterpolation = createInterpolation;
|
||||
|
||||
_parent: AnimatedNode;
|
||||
_config: InterpolationConfigType;
|
||||
_interpolation: (input: number) => number | string;
|
||||
|
||||
constructor(parent: AnimatedNode, config: InterpolationConfigType) {
|
||||
super();
|
||||
this._parent = parent;
|
||||
this._config = config;
|
||||
this._interpolation = createInterpolation(config);
|
||||
}
|
||||
|
||||
__makeNative() {
|
||||
this._parent.__makeNative();
|
||||
super.__makeNative();
|
||||
}
|
||||
|
||||
__getValue(): number | string {
|
||||
const parentValue: number = this._parent.__getValue();
|
||||
invariant(
|
||||
typeof parentValue === 'number',
|
||||
'Cannot interpolate an input which is not a number.',
|
||||
);
|
||||
return this._interpolation(parentValue);
|
||||
}
|
||||
|
||||
interpolate(config: InterpolationConfigType): AnimatedInterpolation {
|
||||
return new AnimatedInterpolation(this, config);
|
||||
}
|
||||
|
||||
__attach(): void {
|
||||
this._parent.__addChild(this);
|
||||
}
|
||||
|
||||
__detach(): void {
|
||||
this._parent.__removeChild(this);
|
||||
super.__detach();
|
||||
}
|
||||
|
||||
__transformDataType(range: Array<any>) {
|
||||
// Change the string array type to number array
|
||||
// So we can reuse the same logic in iOS and Android platform
|
||||
return range.map(function(value) {
|
||||
if (typeof value !== 'string') {
|
||||
return value;
|
||||
}
|
||||
if (/deg$/.test(value)) {
|
||||
const degrees = parseFloat(value) || 0;
|
||||
const radians = degrees * Math.PI / 180.0;
|
||||
return radians;
|
||||
} else {
|
||||
// Assume radians
|
||||
return parseFloat(value) || 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
__getNativeConfig(): any {
|
||||
if (__DEV__) {
|
||||
NativeAnimatedHelper.validateInterpolation(this._config);
|
||||
}
|
||||
|
||||
return {
|
||||
inputRange: this._config.inputRange,
|
||||
// Only the `outputRange` can contain strings so we don't need to tranform `inputRange` here
|
||||
outputRange: this.__transformDataType(this._config.outputRange),
|
||||
extrapolateLeft:
|
||||
this._config.extrapolateLeft || this._config.extrapolate || 'extend',
|
||||
extrapolateRight:
|
||||
this._config.extrapolateRight || this._config.extrapolate || 'extend',
|
||||
type: 'interpolation',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AnimatedInterpolation;
|
||||
module.exports = Interpolation;
|
||||
@@ -8,7 +8,6 @@
|
||||
*
|
||||
* @providesModule NativeAnimatedHelper
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
@@ -17,9 +16,6 @@ const NativeEventEmitter = require('NativeEventEmitter');
|
||||
|
||||
const invariant = require('fbjs/lib/invariant');
|
||||
|
||||
import type {AnimationConfig} from './animations/Animation';
|
||||
import type {EventConfig} from './AnimatedEvent';
|
||||
|
||||
let __nativeAnimatedNodeTagCount = 1; /* used for animated nodes */
|
||||
let __nativeAnimationIdCount = 1; /* used for started animations */
|
||||
|
||||
@@ -53,26 +49,13 @@ const API = {
|
||||
assertNativeAnimatedModule();
|
||||
NativeAnimatedModule.connectAnimatedNodes(parentTag, childTag);
|
||||
},
|
||||
disconnectAnimatedNodes: function(
|
||||
parentTag: ?number,
|
||||
childTag: ?number,
|
||||
): void {
|
||||
disconnectAnimatedNodes: function(parentTag: ?number, childTag: ?number): void {
|
||||
assertNativeAnimatedModule();
|
||||
NativeAnimatedModule.disconnectAnimatedNodes(parentTag, childTag);
|
||||
},
|
||||
startAnimatingNode: function(
|
||||
animationId: ?number,
|
||||
nodeTag: ?number,
|
||||
config: Object,
|
||||
endCallback: EndCallback,
|
||||
): void {
|
||||
startAnimatingNode: function(animationId: ?number, nodeTag: ?number, config: Object, endCallback: EndCallback): void {
|
||||
assertNativeAnimatedModule();
|
||||
NativeAnimatedModule.startAnimatingNode(
|
||||
animationId,
|
||||
nodeTag,
|
||||
config,
|
||||
endCallback,
|
||||
);
|
||||
NativeAnimatedModule.startAnimatingNode(animationId, nodeTag, config, endCallback);
|
||||
},
|
||||
stopAnimation: function(animationId: ?number) {
|
||||
assertNativeAnimatedModule();
|
||||
@@ -94,17 +77,11 @@ const API = {
|
||||
assertNativeAnimatedModule();
|
||||
NativeAnimatedModule.extractAnimatedNodeOffset(nodeTag);
|
||||
},
|
||||
connectAnimatedNodeToView: function(
|
||||
nodeTag: ?number,
|
||||
viewTag: ?number,
|
||||
): void {
|
||||
connectAnimatedNodeToView: function(nodeTag: ?number, viewTag: ?number): void {
|
||||
assertNativeAnimatedModule();
|
||||
NativeAnimatedModule.connectAnimatedNodeToView(nodeTag, viewTag);
|
||||
},
|
||||
disconnectAnimatedNodeFromView: function(
|
||||
nodeTag: ?number,
|
||||
viewTag: ?number,
|
||||
): void {
|
||||
disconnectAnimatedNodeFromView: function(nodeTag: ?number, viewTag: ?number): void {
|
||||
assertNativeAnimatedModule();
|
||||
NativeAnimatedModule.disconnectAnimatedNodeFromView(nodeTag, viewTag);
|
||||
},
|
||||
@@ -112,30 +89,14 @@ const API = {
|
||||
assertNativeAnimatedModule();
|
||||
NativeAnimatedModule.dropAnimatedNode(tag);
|
||||
},
|
||||
addAnimatedEventToView: function(
|
||||
viewTag: ?number,
|
||||
eventName: string,
|
||||
eventMapping: EventMapping,
|
||||
) {
|
||||
addAnimatedEventToView: function(viewTag: ?number, eventName: string, eventMapping: EventMapping) {
|
||||
assertNativeAnimatedModule();
|
||||
NativeAnimatedModule.addAnimatedEventToView(
|
||||
viewTag,
|
||||
eventName,
|
||||
eventMapping,
|
||||
);
|
||||
NativeAnimatedModule.addAnimatedEventToView(viewTag, eventName, eventMapping);
|
||||
},
|
||||
removeAnimatedEventFromView(
|
||||
viewTag: ?number,
|
||||
eventName: string,
|
||||
animatedNodeTag: ?number,
|
||||
) {
|
||||
removeAnimatedEventFromView(viewTag: ?number, eventName: string, animatedNodeTag: ?number) {
|
||||
assertNativeAnimatedModule();
|
||||
NativeAnimatedModule.removeAnimatedEventFromView(
|
||||
viewTag,
|
||||
eventName,
|
||||
animatedNodeTag,
|
||||
);
|
||||
},
|
||||
NativeAnimatedModule.removeAnimatedEventFromView(viewTag, eventName, animatedNodeTag);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -147,9 +108,6 @@ const API = {
|
||||
const STYLES_WHITELIST = {
|
||||
opacity: true,
|
||||
transform: true,
|
||||
/* ios styles */
|
||||
shadowOpacity: true,
|
||||
shadowRadius: true,
|
||||
/* legacy android transform properties */
|
||||
scaleX: true,
|
||||
scaleY: true,
|
||||
@@ -169,34 +127,10 @@ const TRANSFORM_WHITELIST = {
|
||||
perspective: true,
|
||||
};
|
||||
|
||||
const SUPPORTED_INTERPOLATION_PARAMS = {
|
||||
inputRange: true,
|
||||
outputRange: true,
|
||||
extrapolate: true,
|
||||
extrapolateRight: true,
|
||||
extrapolateLeft: true,
|
||||
};
|
||||
|
||||
function addWhitelistedStyleProp(prop: string): void {
|
||||
STYLES_WHITELIST[prop] = true;
|
||||
}
|
||||
|
||||
function addWhitelistedTransformProp(prop: string): void {
|
||||
TRANSFORM_WHITELIST[prop] = true;
|
||||
}
|
||||
|
||||
function addWhitelistedInterpolationParam(param: string): void {
|
||||
SUPPORTED_INTERPOLATION_PARAMS[param] = true;
|
||||
}
|
||||
|
||||
function validateTransform(configs: Array<Object>): void {
|
||||
configs.forEach(config => {
|
||||
configs.forEach((config) => {
|
||||
if (!TRANSFORM_WHITELIST.hasOwnProperty(config.property)) {
|
||||
throw new Error(
|
||||
`Property '${
|
||||
config.property
|
||||
}' is not supported by native animated module`,
|
||||
);
|
||||
throw new Error(`Property '${config.property}' is not supported by native animated module`);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -204,19 +138,22 @@ function validateTransform(configs: Array<Object>): void {
|
||||
function validateStyles(styles: Object): void {
|
||||
for (var key in styles) {
|
||||
if (!STYLES_WHITELIST.hasOwnProperty(key)) {
|
||||
throw new Error(
|
||||
`Style property '${key}' is not supported by native animated module`,
|
||||
);
|
||||
throw new Error(`Style property '${key}' is not supported by native animated module`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function validateInterpolation(config: Object): void {
|
||||
var SUPPORTED_INTERPOLATION_PARAMS = {
|
||||
inputRange: true,
|
||||
outputRange: true,
|
||||
extrapolate: true,
|
||||
extrapolateRight: true,
|
||||
extrapolateLeft: true,
|
||||
};
|
||||
for (var key in config) {
|
||||
if (!SUPPORTED_INTERPOLATION_PARAMS.hasOwnProperty(key)) {
|
||||
throw new Error(
|
||||
`Interpolation property '${key}' is not supported by native animated module`,
|
||||
);
|
||||
throw new Error(`Interpolation property '${key}' is not supported by native animated module`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -233,38 +170,19 @@ function assertNativeAnimatedModule(): void {
|
||||
invariant(NativeAnimatedModule, 'Native animated module is not available');
|
||||
}
|
||||
|
||||
let _warnedMissingNativeAnimated = false;
|
||||
|
||||
function shouldUseNativeDriver(config: AnimationConfig | EventConfig): boolean {
|
||||
if (config.useNativeDriver && !NativeAnimatedModule) {
|
||||
if (!_warnedMissingNativeAnimated) {
|
||||
console.warn(
|
||||
'Animated: `useNativeDriver` is not supported because the native ' +
|
||||
'animated module is missing. Falling back to JS-based animation. To ' +
|
||||
'resolve this, add `RCTAnimation` module to this app, or remove ' +
|
||||
'`useNativeDriver`. ' +
|
||||
'More info: https://github.com/facebook/react-native/issues/11094#issuecomment-263240420',
|
||||
);
|
||||
_warnedMissingNativeAnimated = true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return config.useNativeDriver || false;
|
||||
function isNativeAnimatedAvailable(): boolean {
|
||||
return !!NativeAnimatedModule;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
API,
|
||||
addWhitelistedStyleProp,
|
||||
addWhitelistedTransformProp,
|
||||
addWhitelistedInterpolationParam,
|
||||
validateStyles,
|
||||
validateTransform,
|
||||
validateInterpolation,
|
||||
generateNewNodeTag,
|
||||
generateNewAnimationId,
|
||||
assertNativeAnimatedModule,
|
||||
shouldUseNativeDriver,
|
||||
isNativeAnimatedAvailable,
|
||||
get nativeEventEmitter() {
|
||||
if (!nativeEventEmitter) {
|
||||
nativeEventEmitter = new NativeEventEmitter(NativeAnimatedModule);
|
||||
|
||||
@@ -13,15 +13,15 @@
|
||||
'use strict';
|
||||
|
||||
type SpringConfigType = {
|
||||
stiffness: number,
|
||||
damping: number,
|
||||
tension: number,
|
||||
friction: number,
|
||||
};
|
||||
|
||||
function stiffnessFromOrigamiValue(oValue) {
|
||||
function tensionFromOrigamiValue(oValue) {
|
||||
return (oValue - 30) * 3.62 + 194;
|
||||
}
|
||||
|
||||
function dampingFromOrigamiValue(oValue) {
|
||||
function frictionFromOrigamiValue(oValue) {
|
||||
return (oValue - 8) * 3 + 25;
|
||||
}
|
||||
|
||||
@@ -30,8 +30,8 @@ function fromOrigamiTensionAndFriction(
|
||||
friction: number,
|
||||
): SpringConfigType {
|
||||
return {
|
||||
stiffness: stiffnessFromOrigamiValue(tension),
|
||||
damping: dampingFromOrigamiValue(friction),
|
||||
tension: tensionFromOrigamiValue(tension),
|
||||
friction: frictionFromOrigamiValue(friction)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -91,8 +91,8 @@ function fromBouncinessAndSpeed(
|
||||
);
|
||||
|
||||
return {
|
||||
stiffness: stiffnessFromOrigamiValue(bouncyTension),
|
||||
damping: dampingFromOrigamiValue(bouncyFriction),
|
||||
tension: tensionFromOrigamiValue(bouncyTension),
|
||||
friction: frictionFromOrigamiValue(bouncyFriction)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -5,8 +5,6 @@
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @emails oncall+react_native
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
@@ -137,7 +135,7 @@ describe('Animated tests', () => {
|
||||
expect(callback).toBeCalled();
|
||||
});
|
||||
|
||||
it('send toValue when an underdamped spring stops', () => {
|
||||
it('send toValue when a spring stops', () => {
|
||||
var anim = new Animated.Value(0);
|
||||
var listener = jest.fn();
|
||||
anim.addListener(listener);
|
||||
@@ -149,18 +147,6 @@ describe('Animated tests', () => {
|
||||
expect(anim.__getValue()).toBe(15);
|
||||
});
|
||||
|
||||
it('send toValue when a critically damped spring stops', () => {
|
||||
var anim = new Animated.Value(0);
|
||||
var listener = jest.fn();
|
||||
anim.addListener(listener);
|
||||
Animated.spring(anim, {stiffness: 8000, damping: 2000, toValue: 15}).start();
|
||||
jest.runAllTimers();
|
||||
var lastValue = listener.mock.calls[listener.mock.calls.length - 2][0].value;
|
||||
expect(lastValue).not.toBe(15);
|
||||
expect(lastValue).toBeCloseTo(15);
|
||||
expect(anim.__getValue()).toBe(15);
|
||||
});
|
||||
|
||||
it('convert to JSON', () => {
|
||||
expect(JSON.stringify(new Animated.Value(10))).toBe('10');
|
||||
});
|
||||
@@ -371,7 +357,7 @@ describe('Animated tests', () => {
|
||||
loop.start(cb);
|
||||
|
||||
expect(animation.start).not.toBeCalled();
|
||||
expect(cb).toBeCalledWith({finished: true});
|
||||
expect(cb).toBeCalledWith({ finished: true });
|
||||
});
|
||||
|
||||
it('supports interrupting an indefinite loop', () => {
|
||||
|
||||
@@ -5,13 +5,10 @@
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @emails oncall+react_native
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
jest
|
||||
.clearAllMocks()
|
||||
.setMock('Text', {})
|
||||
.setMock('View', {})
|
||||
.setMock('Image', {})
|
||||
@@ -80,7 +77,7 @@ describe('Native Animated', () => {
|
||||
|
||||
anim.setValue(0.5);
|
||||
|
||||
expect(nativeAnimatedModule.setAnimatedNodeValue).toBeCalledWith(expect.any(Number), 0.5);
|
||||
expect(nativeAnimatedModule.setAnimatedNodeValue).toBeCalledWith(jasmine.any(Number), 0.5);
|
||||
expect(c.refs.node.setNativeProps).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -95,12 +92,12 @@ describe('Native Animated', () => {
|
||||
});
|
||||
|
||||
expect(nativeAnimatedModule.createAnimatedNode).toBeCalledWith(
|
||||
expect.any(Number),
|
||||
jasmine.any(Number),
|
||||
{type: 'value', value: 0, offset: 10},
|
||||
);
|
||||
anim.setOffset(20);
|
||||
expect(nativeAnimatedModule.setAnimatedNodeOffset)
|
||||
.toBeCalledWith(expect.any(Number), 20);
|
||||
.toBeCalledWith(jasmine.any(Number), 20);
|
||||
});
|
||||
|
||||
it('should flatten offset', () => {
|
||||
@@ -113,12 +110,12 @@ describe('Native Animated', () => {
|
||||
});
|
||||
|
||||
expect(nativeAnimatedModule.createAnimatedNode).toBeCalledWith(
|
||||
expect.any(Number),
|
||||
jasmine.any(Number),
|
||||
{type: 'value', value: 0, offset: 0},
|
||||
);
|
||||
anim.flattenOffset();
|
||||
expect(nativeAnimatedModule.flattenAnimatedNodeOffset)
|
||||
.toBeCalledWith(expect.any(Number));
|
||||
.toBeCalledWith(jasmine.any(Number));
|
||||
});
|
||||
|
||||
it('should extract offset', () => {
|
||||
@@ -131,12 +128,12 @@ describe('Native Animated', () => {
|
||||
});
|
||||
|
||||
expect(nativeAnimatedModule.createAnimatedNode).toBeCalledWith(
|
||||
expect.any(Number),
|
||||
jasmine.any(Number),
|
||||
{type: 'value', value: 0, offset: 0},
|
||||
);
|
||||
anim.extractOffset();
|
||||
expect(nativeAnimatedModule.extractAnimatedNodeOffset)
|
||||
.toBeCalledWith(expect.any(Number));
|
||||
.toBeCalledWith(jasmine.any(Number));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -214,14 +211,14 @@ describe('Native Animated', () => {
|
||||
);
|
||||
const c = createAndMountComponent(Animated.View, {onTouchMove: event});
|
||||
expect(nativeAnimatedModule.addAnimatedEventToView).toBeCalledWith(
|
||||
expect.any(Number),
|
||||
jasmine.any(Number),
|
||||
'onTouchMove',
|
||||
{nativeEventPath: ['state', 'foo'], animatedValueTag: value.__getNativeTag()},
|
||||
);
|
||||
|
||||
c.componentWillUnmount();
|
||||
expect(nativeAnimatedModule.removeAnimatedEventFromView).toBeCalledWith(
|
||||
expect.any(Number),
|
||||
jasmine.any(Number),
|
||||
'onTouchMove',
|
||||
value.__getNativeTag(),
|
||||
);
|
||||
@@ -271,10 +268,10 @@ describe('Native Animated', () => {
|
||||
expect(nativeAnimatedModule.connectAnimatedNodes).toHaveBeenCalledTimes(2);
|
||||
|
||||
expect(nativeAnimatedModule.startAnimatingNode).toBeCalledWith(
|
||||
expect.any(Number),
|
||||
expect.any(Number),
|
||||
{type: 'frames', frames: expect.any(Array), toValue: expect.any(Number), iterations: 1},
|
||||
expect.any(Function)
|
||||
jasmine.any(Number),
|
||||
jasmine.any(Number),
|
||||
{type: 'frames', frames: jasmine.any(Array), toValue: jasmine.any(Number), iterations: 1},
|
||||
jasmine.any(Function)
|
||||
);
|
||||
|
||||
expect(nativeAnimatedModule.disconnectAnimatedNodes).toHaveBeenCalledTimes(2);
|
||||
@@ -292,11 +289,11 @@ describe('Native Animated', () => {
|
||||
Animated.timing(anim, {toValue: 10, duration: 1000, useNativeDriver: true}).start();
|
||||
|
||||
expect(nativeAnimatedModule.createAnimatedNode)
|
||||
.toBeCalledWith(expect.any(Number), {type: 'value', value: 0, offset: 0});
|
||||
.toBeCalledWith(jasmine.any(Number), {type: 'value', value: 0, offset: 0});
|
||||
expect(nativeAnimatedModule.createAnimatedNode)
|
||||
.toBeCalledWith(expect.any(Number), {type: 'style', style: {opacity: expect.any(Number)}});
|
||||
.toBeCalledWith(jasmine.any(Number), {type: 'style', style: {opacity: jasmine.any(Number)}});
|
||||
expect(nativeAnimatedModule.createAnimatedNode)
|
||||
.toBeCalledWith(expect.any(Number), {type: 'props', props: {style: expect.any(Number)}});
|
||||
.toBeCalledWith(jasmine.any(Number), {type: 'props', props: {style: jasmine.any(Number)}});
|
||||
});
|
||||
|
||||
it('sends a valid graph description for Animated.add nodes', () => {
|
||||
@@ -312,8 +309,8 @@ describe('Native Animated', () => {
|
||||
});
|
||||
|
||||
expect(nativeAnimatedModule.createAnimatedNode).toBeCalledWith(
|
||||
expect.any(Number),
|
||||
{type: 'addition', input: expect.any(Array)},
|
||||
jasmine.any(Number),
|
||||
{type: 'addition', input: jasmine.any(Array)},
|
||||
);
|
||||
const additionCalls = nativeAnimatedModule.createAnimatedNode.mock.calls.filter(
|
||||
(call) => call[1].type === 'addition'
|
||||
@@ -344,8 +341,8 @@ describe('Native Animated', () => {
|
||||
});
|
||||
|
||||
expect(nativeAnimatedModule.createAnimatedNode).toBeCalledWith(
|
||||
expect.any(Number),
|
||||
{type: 'multiplication', input: expect.any(Array)},
|
||||
jasmine.any(Number),
|
||||
{type: 'multiplication', input: jasmine.any(Array)},
|
||||
);
|
||||
const multiplicationCalls = nativeAnimatedModule.createAnimatedNode.mock.calls.filter(
|
||||
(call) => call[1].type === 'multiplication'
|
||||
@@ -376,7 +373,7 @@ describe('Native Animated', () => {
|
||||
});
|
||||
|
||||
expect(nativeAnimatedModule.createAnimatedNode)
|
||||
.toBeCalledWith(expect.any(Number), {type: 'division', input: expect.any(Array)});
|
||||
.toBeCalledWith(jasmine.any(Number), {type: 'division', input: jasmine.any(Array)});
|
||||
const divisionCalls = nativeAnimatedModule.createAnimatedNode.mock.calls.filter(
|
||||
(call) => call[1].type === 'division'
|
||||
);
|
||||
@@ -404,8 +401,8 @@ describe('Native Animated', () => {
|
||||
});
|
||||
|
||||
expect(nativeAnimatedModule.createAnimatedNode).toBeCalledWith(
|
||||
expect.any(Number),
|
||||
{type: 'modulus', modulus: 4, input: expect.any(Number)},
|
||||
jasmine.any(Number),
|
||||
{type: 'modulus', modulus: 4, input: jasmine.any(Number)},
|
||||
);
|
||||
const moduloCalls = nativeAnimatedModule.createAnimatedNode.mock.calls.filter(
|
||||
(call) => call[1].type === 'modulus'
|
||||
@@ -435,11 +432,11 @@ describe('Native Animated', () => {
|
||||
});
|
||||
|
||||
expect(nativeAnimatedModule.createAnimatedNode).toBeCalledWith(
|
||||
expect.any(Number),
|
||||
jasmine.any(Number),
|
||||
{type: 'value', value: 10, offset: 0}
|
||||
);
|
||||
expect(nativeAnimatedModule.createAnimatedNode)
|
||||
.toBeCalledWith(expect.any(Number), {
|
||||
.toBeCalledWith(jasmine.any(Number), {
|
||||
type: 'interpolation',
|
||||
inputRange: [10, 20],
|
||||
outputRange: [0, 1],
|
||||
@@ -466,11 +463,11 @@ describe('Native Animated', () => {
|
||||
});
|
||||
|
||||
expect(nativeAnimatedModule.createAnimatedNode).toBeCalledWith(
|
||||
expect.any(Number),
|
||||
jasmine.any(Number),
|
||||
{
|
||||
type: 'transform',
|
||||
transforms: [{
|
||||
nodeTag: expect.any(Number),
|
||||
nodeTag: jasmine.any(Number),
|
||||
property: 'translateX',
|
||||
type: 'animated',
|
||||
}, {
|
||||
@@ -493,8 +490,8 @@ describe('Native Animated', () => {
|
||||
});
|
||||
|
||||
expect(nativeAnimatedModule.createAnimatedNode).toBeCalledWith(
|
||||
expect.any(Number),
|
||||
{type: 'diffclamp', input: expect.any(Number), max: 20, min: 0},
|
||||
jasmine.any(Number),
|
||||
{type: 'diffclamp', input: jasmine.any(Number), max: 20, min: 0},
|
||||
);
|
||||
const diffClampCalls = nativeAnimatedModule.createAnimatedNode.mock.calls.filter(
|
||||
(call) => call[1].type === 'diffclamp'
|
||||
@@ -570,9 +567,9 @@ describe('Native Animated', () => {
|
||||
});
|
||||
|
||||
expect(nativeAnimatedModule.createAnimatedNode)
|
||||
.toBeCalledWith(expect.any(Number), { type: 'style', style: { opacity: expect.any(Number) }});
|
||||
.toBeCalledWith(jasmine.any(Number), { type: 'style', style: { opacity: jasmine.any(Number) }});
|
||||
expect(nativeAnimatedModule.createAnimatedNode)
|
||||
.toBeCalledWith(expect.any(Number), { type: 'props', props: { style: expect.any(Number) }});
|
||||
.toBeCalledWith(jasmine.any(Number), { type: 'props', props: { style: jasmine.any(Number) }});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -582,10 +579,10 @@ describe('Native Animated', () => {
|
||||
Animated.timing(anim, {toValue: 10, duration: 1000, useNativeDriver: true}).start();
|
||||
|
||||
expect(nativeAnimatedModule.startAnimatingNode).toBeCalledWith(
|
||||
expect.any(Number),
|
||||
expect.any(Number),
|
||||
{type: 'frames', frames: expect.any(Array), toValue: expect.any(Number), iterations: 1},
|
||||
expect.any(Function)
|
||||
jasmine.any(Number),
|
||||
jasmine.any(Number),
|
||||
{type: 'frames', frames: jasmine.any(Array), toValue: jasmine.any(Number), iterations: 1},
|
||||
jasmine.any(Function)
|
||||
);
|
||||
});
|
||||
|
||||
@@ -593,65 +590,38 @@ describe('Native Animated', () => {
|
||||
const anim = new Animated.Value(0);
|
||||
Animated.spring(anim, {toValue: 10, friction: 5, tension: 164, useNativeDriver: true}).start();
|
||||
expect(nativeAnimatedModule.startAnimatingNode).toBeCalledWith(
|
||||
expect.any(Number),
|
||||
expect.any(Number),
|
||||
jasmine.any(Number),
|
||||
jasmine.any(Number),
|
||||
{
|
||||
type: 'spring',
|
||||
stiffness: 679.08,
|
||||
damping: 16,
|
||||
mass: 1,
|
||||
friction: 16,
|
||||
initialVelocity: 0,
|
||||
overshootClamping: false,
|
||||
restDisplacementThreshold: 0.001,
|
||||
restSpeedThreshold: 0.001,
|
||||
tension: 679.08,
|
||||
toValue: 10,
|
||||
iterations: 1,
|
||||
},
|
||||
expect.any(Function)
|
||||
);
|
||||
|
||||
Animated.spring(anim, {
|
||||
toValue: 10,
|
||||
stiffness: 1000,
|
||||
damping: 500,
|
||||
mass: 3,
|
||||
useNativeDriver: true
|
||||
}).start();
|
||||
expect(nativeAnimatedModule.startAnimatingNode).toBeCalledWith(
|
||||
expect.any(Number),
|
||||
expect.any(Number),
|
||||
{
|
||||
type: 'spring',
|
||||
stiffness: 1000,
|
||||
damping: 500,
|
||||
mass: 3,
|
||||
initialVelocity: 0,
|
||||
overshootClamping: false,
|
||||
restDisplacementThreshold: 0.001,
|
||||
restSpeedThreshold: 0.001,
|
||||
toValue: 10,
|
||||
iterations: 1,
|
||||
},
|
||||
expect.any(Function)
|
||||
jasmine.any(Function)
|
||||
);
|
||||
|
||||
Animated.spring(anim, {toValue: 10, bounciness: 8, speed: 10, useNativeDriver: true}).start();
|
||||
expect(nativeAnimatedModule.startAnimatingNode).toBeCalledWith(
|
||||
expect.any(Number),
|
||||
expect.any(Number),
|
||||
jasmine.any(Number),
|
||||
jasmine.any(Number),
|
||||
{
|
||||
type: 'spring',
|
||||
damping: 23.05223140901191,
|
||||
friction: 23.05223140901191,
|
||||
initialVelocity: 0,
|
||||
overshootClamping: false,
|
||||
restDisplacementThreshold: 0.001,
|
||||
restSpeedThreshold: 0.001,
|
||||
stiffness: 299.61882352941177,
|
||||
mass: 1,
|
||||
tension: 299.61882352941177,
|
||||
toValue: 10,
|
||||
iterations: 1,
|
||||
},
|
||||
expect.any(Function)
|
||||
jasmine.any(Function)
|
||||
);
|
||||
});
|
||||
|
||||
@@ -660,10 +630,10 @@ describe('Native Animated', () => {
|
||||
Animated.decay(anim, {velocity: 10, deceleration: 0.1, useNativeDriver: true}).start();
|
||||
|
||||
expect(nativeAnimatedModule.startAnimatingNode).toBeCalledWith(
|
||||
expect.any(Number),
|
||||
expect.any(Number),
|
||||
jasmine.any(Number),
|
||||
jasmine.any(Number),
|
||||
{type: 'decay', deceleration: 0.1, velocity: 10, iterations: 1},
|
||||
expect.any(Function)
|
||||
jasmine.any(Function)
|
||||
);
|
||||
});
|
||||
|
||||
@@ -675,10 +645,10 @@ describe('Native Animated', () => {
|
||||
).start();
|
||||
|
||||
expect(nativeAnimatedModule.startAnimatingNode).toBeCalledWith(
|
||||
expect.any(Number),
|
||||
expect.any(Number),
|
||||
jasmine.any(Number),
|
||||
jasmine.any(Number),
|
||||
{type: 'decay', deceleration: 0.1, velocity: 10, iterations: 10},
|
||||
expect.any(Function)
|
||||
jasmine.any(Function)
|
||||
);
|
||||
});
|
||||
|
||||
@@ -688,10 +658,10 @@ describe('Native Animated', () => {
|
||||
|
||||
animation.start();
|
||||
expect(nativeAnimatedModule.startAnimatingNode).toBeCalledWith(
|
||||
expect.any(Number),
|
||||
expect.any(Number),
|
||||
{type: 'frames', frames: expect.any(Array), toValue: expect.any(Number), iterations: 1},
|
||||
expect.any(Function)
|
||||
jasmine.any(Number),
|
||||
jasmine.any(Number),
|
||||
{type: 'frames', frames: jasmine.any(Array), toValue: jasmine.any(Number), iterations: 1},
|
||||
jasmine.any(Function)
|
||||
);
|
||||
const animationId = nativeAnimatedModule.startAnimatingNode.mock.calls[0][0];
|
||||
|
||||
|
||||
@@ -5,8 +5,6 @@
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @emails oncall+react_native
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
|
||||
@@ -5,17 +5,15 @@
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @emails oncall+react_native
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var AnimatedInterpolation = require('../nodes/AnimatedInterpolation');
|
||||
var Interpolation = require('Interpolation');
|
||||
var Easing = require('Easing');
|
||||
|
||||
describe('Interpolation', () => {
|
||||
it('should work with defaults', () => {
|
||||
var interpolation = AnimatedInterpolation.__createInterpolation({
|
||||
var interpolation = Interpolation.create({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [0, 1],
|
||||
});
|
||||
@@ -27,7 +25,7 @@ describe('Interpolation', () => {
|
||||
});
|
||||
|
||||
it('should work with output range', () => {
|
||||
var interpolation = AnimatedInterpolation.__createInterpolation({
|
||||
var interpolation = Interpolation.create({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [100, 200],
|
||||
});
|
||||
@@ -39,7 +37,7 @@ describe('Interpolation', () => {
|
||||
});
|
||||
|
||||
it('should work with input range', () => {
|
||||
var interpolation = AnimatedInterpolation.__createInterpolation({
|
||||
var interpolation = Interpolation.create({
|
||||
inputRange: [100, 200],
|
||||
outputRange: [0, 1],
|
||||
});
|
||||
@@ -51,23 +49,19 @@ describe('Interpolation', () => {
|
||||
});
|
||||
|
||||
it('should throw for non monotonic input ranges', () => {
|
||||
expect(() =>
|
||||
AnimatedInterpolation.__createInterpolation({
|
||||
inputRange: [0, 2, 1],
|
||||
outputRange: [0, 1, 2],
|
||||
}),
|
||||
).toThrow();
|
||||
expect(() => Interpolation.create({
|
||||
inputRange: [0, 2, 1],
|
||||
outputRange: [0, 1, 2],
|
||||
})).toThrow();
|
||||
|
||||
expect(() =>
|
||||
AnimatedInterpolation.__createInterpolation({
|
||||
inputRange: [0, 1, 2],
|
||||
outputRange: [0, 3, 1],
|
||||
}),
|
||||
).not.toThrow();
|
||||
expect(() => Interpolation.create({
|
||||
inputRange: [0, 1, 2],
|
||||
outputRange: [0, 3, 1],
|
||||
})).not.toThrow();
|
||||
});
|
||||
|
||||
it('should work with empty input range', () => {
|
||||
var interpolation = AnimatedInterpolation.__createInterpolation({
|
||||
var interpolation = Interpolation.create({
|
||||
inputRange: [0, 10, 10],
|
||||
outputRange: [1, 2, 3],
|
||||
extrapolate: 'extend',
|
||||
@@ -81,7 +75,7 @@ describe('Interpolation', () => {
|
||||
});
|
||||
|
||||
it('should work with empty output range', () => {
|
||||
var interpolation = AnimatedInterpolation.__createInterpolation({
|
||||
var interpolation = Interpolation.create({
|
||||
inputRange: [1, 2, 3],
|
||||
outputRange: [0, 10, 10],
|
||||
extrapolate: 'extend',
|
||||
@@ -96,7 +90,7 @@ describe('Interpolation', () => {
|
||||
});
|
||||
|
||||
it('should work with easing', () => {
|
||||
var interpolation = AnimatedInterpolation.__createInterpolation({
|
||||
var interpolation = Interpolation.create({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [0, 1],
|
||||
easing: Easing.quad,
|
||||
@@ -109,7 +103,7 @@ describe('Interpolation', () => {
|
||||
});
|
||||
|
||||
it('should work with extrapolate', () => {
|
||||
var interpolation = AnimatedInterpolation.__createInterpolation({
|
||||
var interpolation = Interpolation.create({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [0, 1],
|
||||
extrapolate: 'extend',
|
||||
@@ -119,7 +113,7 @@ describe('Interpolation', () => {
|
||||
expect(interpolation(-2)).toBe(4);
|
||||
expect(interpolation(2)).toBe(4);
|
||||
|
||||
interpolation = AnimatedInterpolation.__createInterpolation({
|
||||
interpolation = Interpolation.create({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [0, 1],
|
||||
extrapolate: 'clamp',
|
||||
@@ -129,7 +123,7 @@ describe('Interpolation', () => {
|
||||
expect(interpolation(-2)).toBe(0);
|
||||
expect(interpolation(2)).toBe(1);
|
||||
|
||||
interpolation = AnimatedInterpolation.__createInterpolation({
|
||||
interpolation = Interpolation.create({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [0, 1],
|
||||
extrapolate: 'identity',
|
||||
@@ -141,7 +135,7 @@ describe('Interpolation', () => {
|
||||
});
|
||||
|
||||
it('should work with keyframes with extrapolate', () => {
|
||||
var interpolation = AnimatedInterpolation.__createInterpolation({
|
||||
var interpolation = Interpolation.create({
|
||||
inputRange: [0, 10, 100, 1000],
|
||||
outputRange: [0, 5, 50, 500],
|
||||
extrapolate: true,
|
||||
@@ -159,7 +153,7 @@ describe('Interpolation', () => {
|
||||
});
|
||||
|
||||
it('should work with keyframes without extrapolate', () => {
|
||||
var interpolation = AnimatedInterpolation.__createInterpolation({
|
||||
var interpolation = Interpolation.create({
|
||||
inputRange: [0, 1, 2],
|
||||
outputRange: [0.2, 1, 0.2],
|
||||
extrapolate: 'clamp',
|
||||
@@ -169,23 +163,19 @@ describe('Interpolation', () => {
|
||||
});
|
||||
|
||||
it('should throw for an infinite input range', () => {
|
||||
expect(() =>
|
||||
AnimatedInterpolation.__createInterpolation({
|
||||
inputRange: [-Infinity, Infinity],
|
||||
outputRange: [0, 1],
|
||||
}),
|
||||
).toThrow();
|
||||
expect(() => Interpolation.create({
|
||||
inputRange: [-Infinity, Infinity],
|
||||
outputRange: [0, 1],
|
||||
})).toThrow();
|
||||
|
||||
expect(() =>
|
||||
AnimatedInterpolation.__createInterpolation({
|
||||
inputRange: [-Infinity, 0, Infinity],
|
||||
outputRange: [1, 2, 3],
|
||||
}),
|
||||
).not.toThrow();
|
||||
expect(() => Interpolation.create({
|
||||
inputRange: [-Infinity, 0, Infinity],
|
||||
outputRange: [1, 2, 3],
|
||||
})).not.toThrow();
|
||||
});
|
||||
|
||||
it('should work with negative infinite', () => {
|
||||
var interpolation = AnimatedInterpolation.__createInterpolation({
|
||||
var interpolation = Interpolation.create({
|
||||
inputRange: [-Infinity, 0],
|
||||
outputRange: [-Infinity, 0],
|
||||
easing: Easing.quad,
|
||||
@@ -201,7 +191,7 @@ describe('Interpolation', () => {
|
||||
});
|
||||
|
||||
it('should work with positive infinite', () => {
|
||||
var interpolation = AnimatedInterpolation.__createInterpolation({
|
||||
var interpolation = Interpolation.create({
|
||||
inputRange: [5, Infinity],
|
||||
outputRange: [5, Infinity],
|
||||
easing: Easing.quad,
|
||||
@@ -214,12 +204,12 @@ describe('Interpolation', () => {
|
||||
expect(interpolation(5)).toBeCloseTo(5);
|
||||
expect(interpolation(6)).toBeCloseTo(5 + 1);
|
||||
expect(interpolation(10)).toBeCloseTo(5 + 25);
|
||||
expect(interpolation(100)).toBeCloseTo(5 + 95 * 95);
|
||||
expect(interpolation(100)).toBeCloseTo(5 + (95 * 95));
|
||||
expect(interpolation(Infinity)).toBe(Infinity);
|
||||
});
|
||||
|
||||
it('should work with output ranges as string', () => {
|
||||
var interpolation = AnimatedInterpolation.__createInterpolation({
|
||||
var interpolation = Interpolation.create({
|
||||
inputRange: [0, 1],
|
||||
outputRange: ['rgba(0, 100, 200, 0)', 'rgba(50, 150, 250, 0.4)'],
|
||||
});
|
||||
@@ -230,7 +220,7 @@ describe('Interpolation', () => {
|
||||
});
|
||||
|
||||
it('should work with output ranges as short hex string', () => {
|
||||
var interpolation = AnimatedInterpolation.__createInterpolation({
|
||||
var interpolation = Interpolation.create({
|
||||
inputRange: [0, 1],
|
||||
outputRange: ['#024', '#9BF'],
|
||||
});
|
||||
@@ -241,7 +231,7 @@ describe('Interpolation', () => {
|
||||
});
|
||||
|
||||
it('should work with output ranges as long hex string', () => {
|
||||
var interpolation = AnimatedInterpolation.__createInterpolation({
|
||||
var interpolation = Interpolation.create({
|
||||
inputRange: [0, 1],
|
||||
outputRange: ['#FF9500', '#87FC70'],
|
||||
});
|
||||
@@ -252,7 +242,7 @@ describe('Interpolation', () => {
|
||||
});
|
||||
|
||||
it('should work with output ranges with mixed hex and rgba strings', () => {
|
||||
var interpolation = AnimatedInterpolation.__createInterpolation({
|
||||
var interpolation = Interpolation.create({
|
||||
inputRange: [0, 1],
|
||||
outputRange: ['rgba(100, 120, 140, .4)', '#87FC70'],
|
||||
});
|
||||
@@ -263,7 +253,7 @@ describe('Interpolation', () => {
|
||||
});
|
||||
|
||||
it('should work with negative and decimal values in string ranges', () => {
|
||||
var interpolation = AnimatedInterpolation.__createInterpolation({
|
||||
var interpolation = Interpolation.create({
|
||||
inputRange: [0, 1],
|
||||
outputRange: ['-100.5deg', '100deg'],
|
||||
});
|
||||
@@ -274,17 +264,15 @@ describe('Interpolation', () => {
|
||||
});
|
||||
|
||||
it('should crash when chaining an interpolation that returns a string', () => {
|
||||
var interpolation = AnimatedInterpolation.__createInterpolation({
|
||||
var interpolation = Interpolation.create({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [0, 1],
|
||||
});
|
||||
expect(() => {
|
||||
interpolation('45rad');
|
||||
}).toThrow();
|
||||
expect(() => { interpolation('45rad'); }).toThrow();
|
||||
});
|
||||
|
||||
it('should support a mix of color patterns', () => {
|
||||
var interpolation = AnimatedInterpolation.__createInterpolation({
|
||||
var interpolation = Interpolation.create({
|
||||
inputRange: [0, 1, 2],
|
||||
outputRange: ['rgba(0, 100, 200, 0)', 'rgb(50, 150, 250)', 'red'],
|
||||
});
|
||||
@@ -296,16 +284,14 @@ describe('Interpolation', () => {
|
||||
});
|
||||
|
||||
it('should crash when defining output range with different pattern', () => {
|
||||
expect(() =>
|
||||
AnimatedInterpolation.__createInterpolation({
|
||||
inputRange: [0, 1],
|
||||
outputRange: ['20deg', '30rad'],
|
||||
}),
|
||||
).toThrow();
|
||||
expect(() => Interpolation.create({
|
||||
inputRange: [0, 1],
|
||||
outputRange: ['20deg', '30rad'],
|
||||
})).toThrow();
|
||||
});
|
||||
|
||||
it('should round the alpha channel of a color to the nearest thousandth', () => {
|
||||
var interpolation = AnimatedInterpolation.__createInterpolation({
|
||||
var interpolation = Interpolation.create({
|
||||
inputRange: [0, 1],
|
||||
outputRange: ['rgba(0, 0, 0, 0)', 'rgba(0, 0, 0, 1)'],
|
||||
});
|
||||
|
||||
@@ -2,135 +2,102 @@
|
||||
* BezierEasing - use bezier curve for transition easing function
|
||||
* https://github.com/gre/bezier-easing
|
||||
*
|
||||
* @copyright 2014-2015 Gaetan Renaudeau. MIT License.
|
||||
* @copyright 2014-2015 Gaëtan Renaudeau. MIT License.
|
||||
* @noflow
|
||||
* @emails oncall+react_native
|
||||
* @format
|
||||
*/
|
||||
|
||||
/* eslint-disable */
|
||||
|
||||
'use strict';
|
||||
|
||||
var bezier = require('bezier');
|
||||
|
||||
var identity = function(x) {
|
||||
return x;
|
||||
};
|
||||
var identity = function (x) { return x; };
|
||||
|
||||
function assertClose(a, b, precision = 3) {
|
||||
function assertClose (a, b, precision = 3) {
|
||||
expect(a).toBeCloseTo(b, precision);
|
||||
}
|
||||
|
||||
function makeAssertCloseWithPrecision(precision) {
|
||||
return function(a, b) {
|
||||
function makeAssertCloseWithPrecision (precision) {
|
||||
return function (a, b) {
|
||||
assertClose(a, b, precision);
|
||||
};
|
||||
}
|
||||
|
||||
function allEquals(be1, be2, samples, assertion) {
|
||||
if (!assertion) {
|
||||
assertion = assertClose;
|
||||
}
|
||||
for (var i = 0; i <= samples; ++i) {
|
||||
function allEquals (be1, be2, samples, assertion) {
|
||||
if (!assertion) assertion = assertClose;
|
||||
for (var i=0; i<=samples; ++i) {
|
||||
var x = i / samples;
|
||||
assertion(be1(x), be2(x));
|
||||
}
|
||||
}
|
||||
|
||||
function repeat(n) {
|
||||
return function(f) {
|
||||
for (var i = 0; i < n; ++i) {
|
||||
f(i);
|
||||
}
|
||||
function repeat (n) {
|
||||
return function (f) {
|
||||
for (var i=0; i<n; ++i) f(i);
|
||||
};
|
||||
}
|
||||
|
||||
describe('bezier', function() {
|
||||
it('should be a function', function() {
|
||||
describe('bezier', function(){
|
||||
it('should be a function', function(){
|
||||
expect(typeof bezier === 'function').toBe(true);
|
||||
});
|
||||
it('should creates an object', function() {
|
||||
it('should creates an object', function(){
|
||||
expect(typeof bezier(0, 0, 1, 1) === 'function').toBe(true);
|
||||
});
|
||||
it('should fail with wrong arguments', function() {
|
||||
expect(function() {
|
||||
bezier(0.5, 0.5, -5, 0.5);
|
||||
}).toThrow();
|
||||
expect(function() {
|
||||
bezier(0.5, 0.5, 5, 0.5);
|
||||
}).toThrow();
|
||||
expect(function() {
|
||||
bezier(-2, 0.5, 0.5, 0.5);
|
||||
}).toThrow();
|
||||
expect(function() {
|
||||
bezier(2, 0.5, 0.5, 0.5);
|
||||
}).toThrow();
|
||||
it('should fail with wrong arguments', function () {
|
||||
expect(function () { bezier(0.5, 0.5, -5, 0.5); }).toThrow();
|
||||
expect(function () { bezier(0.5, 0.5, 5, 0.5); }).toThrow();
|
||||
expect(function () { bezier(-2, 0.5, 0.5, 0.5); }).toThrow();
|
||||
expect(function () { bezier(2, 0.5, 0.5, 0.5); }).toThrow();
|
||||
});
|
||||
describe('linear curves', function() {
|
||||
it('should be linear', function() {
|
||||
describe('linear curves', function () {
|
||||
it('should be linear', function () {
|
||||
allEquals(bezier(0, 0, 1, 1), bezier(1, 1, 0, 0), 100);
|
||||
allEquals(bezier(0, 0, 1, 1), identity, 100);
|
||||
});
|
||||
});
|
||||
describe('common properties', function() {
|
||||
it('should be the right value at extremes', function() {
|
||||
repeat(10)(function() {
|
||||
var a = Math.random(),
|
||||
b = 2 * Math.random() - 0.5,
|
||||
c = Math.random(),
|
||||
d = 2 * Math.random() - 0.5;
|
||||
describe('common properties', function () {
|
||||
it('should be the right value at extremes', function () {
|
||||
repeat(10)(function () {
|
||||
var a = Math.random(), b = 2*Math.random()-0.5, c = Math.random(), d = 2*Math.random()-0.5;
|
||||
var easing = bezier(a, b, c, d);
|
||||
expect(easing(0)).toBe(0);
|
||||
expect(easing(1)).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
it('should approach the projected value of its x=y projected curve', function() {
|
||||
repeat(10)(function() {
|
||||
var a = Math.random(),
|
||||
b = Math.random(),
|
||||
c = Math.random(),
|
||||
d = Math.random();
|
||||
it('should approach the projected value of its x=y projected curve', function () {
|
||||
repeat(10)(function () {
|
||||
var a = Math.random(), b = Math.random(), c = Math.random(), d = Math.random();
|
||||
var easing = bezier(a, b, c, d);
|
||||
var projected = bezier(b, a, d, c);
|
||||
var composed = function(x) {
|
||||
return projected(easing(x));
|
||||
};
|
||||
var composed = function (x) { return projected(easing(x)); };
|
||||
allEquals(identity, composed, 100, makeAssertCloseWithPrecision(2));
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('two same instances', function() {
|
||||
it('should be strictly equals', function() {
|
||||
repeat(10)(function() {
|
||||
var a = Math.random(),
|
||||
b = 2 * Math.random() - 0.5,
|
||||
c = Math.random(),
|
||||
d = 2 * Math.random() - 0.5;
|
||||
describe('two same instances', function () {
|
||||
it('should be strictly equals', function () {
|
||||
repeat(10)(function () {
|
||||
var a = Math.random(), b = 2*Math.random()-0.5, c = Math.random(), d = 2*Math.random()-0.5;
|
||||
allEquals(bezier(a, b, c, d), bezier(a, b, c, d), 100, 0);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('symetric curves', function() {
|
||||
it('should have a central value y~=0.5 at x=0.5', function() {
|
||||
repeat(10)(function() {
|
||||
var a = Math.random(),
|
||||
b = 2 * Math.random() - 0.5,
|
||||
c = 1 - a,
|
||||
d = 1 - b;
|
||||
describe('symetric curves', function () {
|
||||
it('should have a central value y~=0.5 at x=0.5', function () {
|
||||
repeat(10)(function () {
|
||||
var a = Math.random(), b = 2*Math.random()-0.5, c = 1-a, d = 1-b;
|
||||
var easing = bezier(a, b, c, d);
|
||||
assertClose(easing(0.5), 0.5, 2);
|
||||
});
|
||||
});
|
||||
it('should be symetrical', function() {
|
||||
repeat(10)(function() {
|
||||
var a = Math.random(),
|
||||
b = 2 * Math.random() - 0.5,
|
||||
c = 1 - a,
|
||||
d = 1 - b;
|
||||
it('should be symetrical', function () {
|
||||
repeat(10)(function () {
|
||||
var a = Math.random(), b = 2*Math.random()-0.5, c = 1-a, d = 1-b;
|
||||
var easing = bezier(a, b, c, d);
|
||||
var sym = function(x) {
|
||||
return 1 - easing(1 - x);
|
||||
};
|
||||
var sym = function (x) { return 1 - easing(1-x); };
|
||||
allEquals(easing, sym, 100, makeAssertCloseWithPrecision(2));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule Animation
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const NativeAnimatedHelper = require('NativeAnimatedHelper');
|
||||
|
||||
import type AnimatedValue from '../nodes/AnimatedValue';
|
||||
|
||||
export type EndResult = {finished: boolean};
|
||||
export type EndCallback = (result: EndResult) => void;
|
||||
|
||||
export type AnimationConfig = {
|
||||
isInteraction?: boolean,
|
||||
useNativeDriver?: boolean,
|
||||
onComplete?: ?EndCallback,
|
||||
iterations?: number,
|
||||
};
|
||||
|
||||
// Important note: start() and stop() will only be called at most once.
|
||||
// Once an animation has been stopped or finished its course, it will
|
||||
// not be reused.
|
||||
class Animation {
|
||||
__active: boolean;
|
||||
__isInteraction: boolean;
|
||||
__nativeId: number;
|
||||
__onEnd: ?EndCallback;
|
||||
__iterations: number;
|
||||
start(
|
||||
fromValue: number,
|
||||
onUpdate: (value: number) => void,
|
||||
onEnd: ?EndCallback,
|
||||
previousAnimation: ?Animation,
|
||||
animatedValue: AnimatedValue,
|
||||
): void {}
|
||||
stop(): void {
|
||||
if (this.__nativeId) {
|
||||
NativeAnimatedHelper.API.stopAnimation(this.__nativeId);
|
||||
}
|
||||
}
|
||||
__getNativeAnimationConfig(): any {
|
||||
// Subclasses that have corresponding animation implementation done in native
|
||||
// should override this method
|
||||
throw new Error('This animation type cannot be offloaded to native');
|
||||
}
|
||||
// Helper function for subclasses to make sure onEnd is only called once.
|
||||
__debouncedOnEnd(result: EndResult): void {
|
||||
const onEnd = this.__onEnd;
|
||||
this.__onEnd = null;
|
||||
onEnd && onEnd(result);
|
||||
}
|
||||
__startNativeAnimation(animatedValue: AnimatedValue): void {
|
||||
animatedValue.__makeNative();
|
||||
this.__nativeId = NativeAnimatedHelper.generateNewAnimationId();
|
||||
NativeAnimatedHelper.API.startAnimatingNode(
|
||||
this.__nativeId,
|
||||
animatedValue.__getNativeTag(),
|
||||
this.__getNativeAnimationConfig(),
|
||||
this.__debouncedOnEnd.bind(this),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Animation;
|
||||
@@ -1,112 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule DecayAnimation
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const Animation = require('./Animation');
|
||||
|
||||
const {shouldUseNativeDriver} = require('../NativeAnimatedHelper');
|
||||
|
||||
import type {AnimationConfig, EndCallback} from './Animation';
|
||||
import type AnimatedValue from '../nodes/AnimatedValue';
|
||||
|
||||
export type DecayAnimationConfig = AnimationConfig & {
|
||||
velocity: number | {x: number, y: number},
|
||||
deceleration?: number,
|
||||
};
|
||||
|
||||
export type DecayAnimationConfigSingle = AnimationConfig & {
|
||||
velocity: number,
|
||||
deceleration?: number,
|
||||
};
|
||||
|
||||
class DecayAnimation extends Animation {
|
||||
_startTime: number;
|
||||
_lastValue: number;
|
||||
_fromValue: number;
|
||||
_deceleration: number;
|
||||
_velocity: number;
|
||||
_onUpdate: (value: number) => void;
|
||||
_animationFrame: any;
|
||||
_useNativeDriver: boolean;
|
||||
|
||||
constructor(config: DecayAnimationConfigSingle) {
|
||||
super();
|
||||
this._deceleration =
|
||||
config.deceleration !== undefined ? config.deceleration : 0.998;
|
||||
this._velocity = config.velocity;
|
||||
this._useNativeDriver = shouldUseNativeDriver(config);
|
||||
this.__isInteraction =
|
||||
config.isInteraction !== undefined ? config.isInteraction : true;
|
||||
this.__iterations = config.iterations !== undefined ? config.iterations : 1;
|
||||
}
|
||||
|
||||
__getNativeAnimationConfig() {
|
||||
return {
|
||||
type: 'decay',
|
||||
deceleration: this._deceleration,
|
||||
velocity: this._velocity,
|
||||
iterations: this.__iterations,
|
||||
};
|
||||
}
|
||||
|
||||
start(
|
||||
fromValue: number,
|
||||
onUpdate: (value: number) => void,
|
||||
onEnd: ?EndCallback,
|
||||
previousAnimation: ?Animation,
|
||||
animatedValue: AnimatedValue,
|
||||
): void {
|
||||
this.__active = true;
|
||||
this._lastValue = fromValue;
|
||||
this._fromValue = fromValue;
|
||||
this._onUpdate = onUpdate;
|
||||
this.__onEnd = onEnd;
|
||||
this._startTime = Date.now();
|
||||
if (this._useNativeDriver) {
|
||||
this.__startNativeAnimation(animatedValue);
|
||||
} else {
|
||||
this._animationFrame = requestAnimationFrame(this.onUpdate.bind(this));
|
||||
}
|
||||
}
|
||||
|
||||
onUpdate(): void {
|
||||
const now = Date.now();
|
||||
|
||||
const value =
|
||||
this._fromValue +
|
||||
this._velocity /
|
||||
(1 - this._deceleration) *
|
||||
(1 - Math.exp(-(1 - this._deceleration) * (now - this._startTime)));
|
||||
|
||||
this._onUpdate(value);
|
||||
|
||||
if (Math.abs(this._lastValue - value) < 0.1) {
|
||||
this.__debouncedOnEnd({finished: true});
|
||||
return;
|
||||
}
|
||||
|
||||
this._lastValue = value;
|
||||
if (this.__active) {
|
||||
this._animationFrame = requestAnimationFrame(this.onUpdate.bind(this));
|
||||
}
|
||||
}
|
||||
|
||||
stop(): void {
|
||||
super.stop();
|
||||
this.__active = false;
|
||||
global.cancelAnimationFrame(this._animationFrame);
|
||||
this.__debouncedOnEnd({finished: false});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = DecayAnimation;
|
||||
@@ -1,342 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule SpringAnimation
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const AnimatedValue = require('../nodes/AnimatedValue');
|
||||
const AnimatedValueXY = require('../nodes/AnimatedValueXY');
|
||||
const Animation = require('./Animation');
|
||||
const SpringConfig = require('../SpringConfig');
|
||||
|
||||
const invariant = require('fbjs/lib/invariant');
|
||||
const {shouldUseNativeDriver} = require('../NativeAnimatedHelper');
|
||||
|
||||
import type {AnimationConfig, EndCallback} from './Animation';
|
||||
|
||||
export type SpringAnimationConfig = AnimationConfig & {
|
||||
toValue: number | AnimatedValue | {x: number, y: number} | AnimatedValueXY,
|
||||
overshootClamping?: boolean,
|
||||
restDisplacementThreshold?: number,
|
||||
restSpeedThreshold?: number,
|
||||
velocity?: number | {x: number, y: number},
|
||||
bounciness?: number,
|
||||
speed?: number,
|
||||
tension?: number,
|
||||
friction?: number,
|
||||
stiffness?: number,
|
||||
damping?: number,
|
||||
mass?: number,
|
||||
delay?: number,
|
||||
};
|
||||
|
||||
export type SpringAnimationConfigSingle = AnimationConfig & {
|
||||
toValue: number | AnimatedValue,
|
||||
overshootClamping?: boolean,
|
||||
restDisplacementThreshold?: number,
|
||||
restSpeedThreshold?: number,
|
||||
velocity?: number,
|
||||
bounciness?: number,
|
||||
speed?: number,
|
||||
tension?: number,
|
||||
friction?: number,
|
||||
stiffness?: number,
|
||||
damping?: number,
|
||||
mass?: number,
|
||||
delay?: number,
|
||||
};
|
||||
|
||||
function withDefault<T>(value: ?T, defaultValue: T): T {
|
||||
if (value === undefined || value === null) {
|
||||
return defaultValue;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
class SpringAnimation extends Animation {
|
||||
_overshootClamping: boolean;
|
||||
_restDisplacementThreshold: number;
|
||||
_restSpeedThreshold: number;
|
||||
_lastVelocity: number;
|
||||
_startPosition: number;
|
||||
_lastPosition: number;
|
||||
_fromValue: number;
|
||||
_toValue: any;
|
||||
_stiffness: number;
|
||||
_damping: number;
|
||||
_mass: number;
|
||||
_initialVelocity: number;
|
||||
_delay: number;
|
||||
_timeout: any;
|
||||
_startTime: number;
|
||||
_lastTime: number;
|
||||
_frameTime: number;
|
||||
_onUpdate: (value: number) => void;
|
||||
_animationFrame: any;
|
||||
_useNativeDriver: boolean;
|
||||
|
||||
constructor(config: SpringAnimationConfigSingle) {
|
||||
super();
|
||||
|
||||
this._overshootClamping = withDefault(config.overshootClamping, false);
|
||||
this._restDisplacementThreshold = withDefault(
|
||||
config.restDisplacementThreshold,
|
||||
0.001,
|
||||
);
|
||||
this._restSpeedThreshold = withDefault(config.restSpeedThreshold, 0.001);
|
||||
this._initialVelocity = withDefault(config.velocity, 0);
|
||||
this._lastVelocity = withDefault(config.velocity, 0);
|
||||
this._toValue = config.toValue;
|
||||
this._delay = withDefault(config.delay, 0);
|
||||
this._useNativeDriver = shouldUseNativeDriver(config);
|
||||
this.__isInteraction =
|
||||
config.isInteraction !== undefined ? config.isInteraction : true;
|
||||
this.__iterations = config.iterations !== undefined ? config.iterations : 1;
|
||||
|
||||
if (
|
||||
config.stiffness !== undefined ||
|
||||
config.damping !== undefined ||
|
||||
config.mass !== undefined
|
||||
) {
|
||||
invariant(
|
||||
config.bounciness === undefined &&
|
||||
config.speed === undefined &&
|
||||
config.tension === undefined &&
|
||||
config.friction === undefined,
|
||||
'You can define one of bounciness/speed, tension/friction, or stiffness/damping/mass, but not more than one',
|
||||
);
|
||||
this._stiffness = withDefault(config.stiffness, 100);
|
||||
this._damping = withDefault(config.damping, 10);
|
||||
this._mass = withDefault(config.mass, 1);
|
||||
} else if (config.bounciness !== undefined || config.speed !== undefined) {
|
||||
// Convert the origami bounciness/speed values to stiffness/damping
|
||||
// We assume mass is 1.
|
||||
invariant(
|
||||
config.tension === undefined &&
|
||||
config.friction === undefined &&
|
||||
config.stiffness === undefined &&
|
||||
config.damping === undefined &&
|
||||
config.mass === undefined,
|
||||
'You can define one of bounciness/speed, tension/friction, or stiffness/damping/mass, but not more than one',
|
||||
);
|
||||
const springConfig = SpringConfig.fromBouncinessAndSpeed(
|
||||
withDefault(config.bounciness, 8),
|
||||
withDefault(config.speed, 12),
|
||||
);
|
||||
this._stiffness = springConfig.stiffness;
|
||||
this._damping = springConfig.damping;
|
||||
this._mass = 1;
|
||||
} else {
|
||||
// Convert the origami tension/friction values to stiffness/damping
|
||||
// We assume mass is 1.
|
||||
const springConfig = SpringConfig.fromOrigamiTensionAndFriction(
|
||||
withDefault(config.tension, 40),
|
||||
withDefault(config.friction, 7),
|
||||
);
|
||||
this._stiffness = springConfig.stiffness;
|
||||
this._damping = springConfig.damping;
|
||||
this._mass = 1;
|
||||
}
|
||||
|
||||
invariant(this._stiffness > 0, 'Stiffness value must be greater than 0');
|
||||
invariant(this._damping > 0, 'Damping value must be greater than 0');
|
||||
invariant(this._mass > 0, 'Mass value must be greater than 0');
|
||||
}
|
||||
|
||||
__getNativeAnimationConfig() {
|
||||
return {
|
||||
type: 'spring',
|
||||
overshootClamping: this._overshootClamping,
|
||||
restDisplacementThreshold: this._restDisplacementThreshold,
|
||||
restSpeedThreshold: this._restSpeedThreshold,
|
||||
stiffness: this._stiffness,
|
||||
damping: this._damping,
|
||||
mass: this._mass,
|
||||
initialVelocity: withDefault(this._initialVelocity, this._lastVelocity),
|
||||
toValue: this._toValue,
|
||||
iterations: this.__iterations,
|
||||
};
|
||||
}
|
||||
|
||||
start(
|
||||
fromValue: number,
|
||||
onUpdate: (value: number) => void,
|
||||
onEnd: ?EndCallback,
|
||||
previousAnimation: ?Animation,
|
||||
animatedValue: AnimatedValue,
|
||||
): void {
|
||||
this.__active = true;
|
||||
this._startPosition = fromValue;
|
||||
this._lastPosition = this._startPosition;
|
||||
|
||||
this._onUpdate = onUpdate;
|
||||
this.__onEnd = onEnd;
|
||||
this._lastTime = Date.now();
|
||||
this._frameTime = 0.0;
|
||||
|
||||
if (previousAnimation instanceof SpringAnimation) {
|
||||
const internalState = previousAnimation.getInternalState();
|
||||
this._lastPosition = internalState.lastPosition;
|
||||
this._lastVelocity = internalState.lastVelocity;
|
||||
// Set the initial velocity to the last velocity
|
||||
this._initialVelocity = this._lastVelocity;
|
||||
this._lastTime = internalState.lastTime;
|
||||
}
|
||||
|
||||
const start = () => {
|
||||
if (this._useNativeDriver) {
|
||||
this.__startNativeAnimation(animatedValue);
|
||||
} else {
|
||||
this.onUpdate();
|
||||
}
|
||||
};
|
||||
|
||||
// If this._delay is more than 0, we start after the timeout.
|
||||
if (this._delay) {
|
||||
this._timeout = setTimeout(start, this._delay);
|
||||
} else {
|
||||
start();
|
||||
}
|
||||
}
|
||||
|
||||
getInternalState(): Object {
|
||||
return {
|
||||
lastPosition: this._lastPosition,
|
||||
lastVelocity: this._lastVelocity,
|
||||
lastTime: this._lastTime,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* This spring model is based off of a damped harmonic oscillator
|
||||
* (https://en.wikipedia.org/wiki/Harmonic_oscillator#Damped_harmonic_oscillator).
|
||||
*
|
||||
* We use the closed form of the second order differential equation:
|
||||
*
|
||||
* x'' + (2ζ⍵_0)x' + ⍵^2x = 0
|
||||
*
|
||||
* where
|
||||
* ⍵_0 = √(k / m) (undamped angular frequency of the oscillator),
|
||||
* ζ = c / 2√mk (damping ratio),
|
||||
* c = damping constant
|
||||
* k = stiffness
|
||||
* m = mass
|
||||
*
|
||||
* The derivation of the closed form is described in detail here:
|
||||
* http://planetmath.org/sites/default/files/texpdf/39745.pdf
|
||||
*
|
||||
* This algorithm happens to match the algorithm used by CASpringAnimation,
|
||||
* a QuartzCore (iOS) API that creates spring animations.
|
||||
*/
|
||||
onUpdate(): void {
|
||||
// If for some reason we lost a lot of frames (e.g. process large payload or
|
||||
// stopped in the debugger), we only advance by 4 frames worth of
|
||||
// computation and will continue on the next frame. It's better to have it
|
||||
// running at faster speed than jumping to the end.
|
||||
const MAX_STEPS = 64;
|
||||
let now = Date.now();
|
||||
if (now > this._lastTime + MAX_STEPS) {
|
||||
now = this._lastTime + MAX_STEPS;
|
||||
}
|
||||
|
||||
const deltaTime = (now - this._lastTime) / 1000;
|
||||
this._frameTime += deltaTime;
|
||||
|
||||
const c: number = this._damping;
|
||||
const m: number = this._mass;
|
||||
const k: number = this._stiffness;
|
||||
const v0: number = -this._initialVelocity;
|
||||
|
||||
const zeta = c / (2 * Math.sqrt(k * m)); // damping ratio
|
||||
const omega0 = Math.sqrt(k / m); // undamped angular frequency of the oscillator (rad/ms)
|
||||
const omega1 = omega0 * Math.sqrt(1.0 - zeta * zeta); // exponential decay
|
||||
const x0 = this._toValue - this._startPosition; // calculate the oscillation from x0 = 1 to x = 0
|
||||
|
||||
let position = 0.0;
|
||||
let velocity = 0.0;
|
||||
const t = this._frameTime;
|
||||
if (zeta < 1) {
|
||||
// Under damped
|
||||
const envelope = Math.exp(-zeta * omega0 * t);
|
||||
position =
|
||||
this._toValue -
|
||||
envelope *
|
||||
((v0 + zeta * omega0 * x0) / omega1 * Math.sin(omega1 * t) +
|
||||
x0 * Math.cos(omega1 * t));
|
||||
// This looks crazy -- it's actually just the derivative of the
|
||||
// oscillation function
|
||||
velocity =
|
||||
zeta *
|
||||
omega0 *
|
||||
envelope *
|
||||
(Math.sin(omega1 * t) * (v0 + zeta * omega0 * x0) / omega1 +
|
||||
x0 * Math.cos(omega1 * t)) -
|
||||
envelope *
|
||||
(Math.cos(omega1 * t) * (v0 + zeta * omega0 * x0) -
|
||||
omega1 * x0 * Math.sin(omega1 * t));
|
||||
} else {
|
||||
// Critically damped
|
||||
const envelope = Math.exp(-omega0 * t);
|
||||
position = this._toValue - envelope * (x0 + (v0 + omega0 * x0) * t);
|
||||
velocity =
|
||||
envelope * (v0 * (t * omega0 - 1) + t * x0 * (omega0 * omega0));
|
||||
}
|
||||
|
||||
this._lastTime = now;
|
||||
this._lastPosition = position;
|
||||
this._lastVelocity = velocity;
|
||||
|
||||
this._onUpdate(position);
|
||||
if (!this.__active) {
|
||||
// a listener might have stopped us in _onUpdate
|
||||
return;
|
||||
}
|
||||
|
||||
// Conditions for stopping the spring animation
|
||||
let isOvershooting = false;
|
||||
if (this._overshootClamping && this._stiffness !== 0) {
|
||||
if (this._startPosition < this._toValue) {
|
||||
isOvershooting = position > this._toValue;
|
||||
} else {
|
||||
isOvershooting = position < this._toValue;
|
||||
}
|
||||
}
|
||||
const isVelocity = Math.abs(velocity) <= this._restSpeedThreshold;
|
||||
let isDisplacement = true;
|
||||
if (this._stiffness !== 0) {
|
||||
isDisplacement =
|
||||
Math.abs(this._toValue - position) <= this._restDisplacementThreshold;
|
||||
}
|
||||
|
||||
if (isOvershooting || (isVelocity && isDisplacement)) {
|
||||
if (this._stiffness !== 0) {
|
||||
// Ensure that we end up with a round value
|
||||
this._lastPosition = this._toValue;
|
||||
this._lastVelocity = 0;
|
||||
this._onUpdate(this._toValue);
|
||||
}
|
||||
|
||||
this.__debouncedOnEnd({finished: true});
|
||||
return;
|
||||
}
|
||||
this._animationFrame = requestAnimationFrame(this.onUpdate.bind(this));
|
||||
}
|
||||
|
||||
stop(): void {
|
||||
super.stop();
|
||||
this.__active = false;
|
||||
clearTimeout(this._timeout);
|
||||
global.cancelAnimationFrame(this._animationFrame);
|
||||
this.__debouncedOnEnd({finished: false});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = SpringAnimation;
|
||||
@@ -1,155 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule TimingAnimation
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const AnimatedValue = require('../nodes/AnimatedValue');
|
||||
const AnimatedValueXY = require('../nodes/AnimatedValueXY');
|
||||
const Animation = require('./Animation');
|
||||
|
||||
const {shouldUseNativeDriver} = require('../NativeAnimatedHelper');
|
||||
|
||||
import type {AnimationConfig, EndCallback} from './Animation';
|
||||
|
||||
export type TimingAnimationConfig = AnimationConfig & {
|
||||
toValue: number | AnimatedValue | {x: number, y: number} | AnimatedValueXY,
|
||||
easing?: (value: number) => number,
|
||||
duration?: number,
|
||||
delay?: number,
|
||||
};
|
||||
|
||||
export type TimingAnimationConfigSingle = AnimationConfig & {
|
||||
toValue: number | AnimatedValue,
|
||||
easing?: (value: number) => number,
|
||||
duration?: number,
|
||||
delay?: number,
|
||||
};
|
||||
|
||||
let _easeInOut;
|
||||
function easeInOut() {
|
||||
if (!_easeInOut) {
|
||||
const Easing = require('Easing');
|
||||
_easeInOut = Easing.inOut(Easing.ease);
|
||||
}
|
||||
return _easeInOut;
|
||||
}
|
||||
|
||||
class TimingAnimation extends Animation {
|
||||
_startTime: number;
|
||||
_fromValue: number;
|
||||
_toValue: any;
|
||||
_duration: number;
|
||||
_delay: number;
|
||||
_easing: (value: number) => number;
|
||||
_onUpdate: (value: number) => void;
|
||||
_animationFrame: any;
|
||||
_timeout: any;
|
||||
_useNativeDriver: boolean;
|
||||
|
||||
constructor(config: TimingAnimationConfigSingle) {
|
||||
super();
|
||||
this._toValue = config.toValue;
|
||||
this._easing = config.easing !== undefined ? config.easing : easeInOut();
|
||||
this._duration = config.duration !== undefined ? config.duration : 500;
|
||||
this._delay = config.delay !== undefined ? config.delay : 0;
|
||||
this.__iterations = config.iterations !== undefined ? config.iterations : 1;
|
||||
this.__isInteraction =
|
||||
config.isInteraction !== undefined ? config.isInteraction : true;
|
||||
this._useNativeDriver = shouldUseNativeDriver(config);
|
||||
}
|
||||
|
||||
__getNativeAnimationConfig(): any {
|
||||
const frameDuration = 1000.0 / 60.0;
|
||||
const frames = [];
|
||||
for (let dt = 0.0; dt < this._duration; dt += frameDuration) {
|
||||
frames.push(this._easing(dt / this._duration));
|
||||
}
|
||||
frames.push(this._easing(1));
|
||||
return {
|
||||
type: 'frames',
|
||||
frames,
|
||||
toValue: this._toValue,
|
||||
iterations: this.__iterations,
|
||||
};
|
||||
}
|
||||
|
||||
start(
|
||||
fromValue: number,
|
||||
onUpdate: (value: number) => void,
|
||||
onEnd: ?EndCallback,
|
||||
previousAnimation: ?Animation,
|
||||
animatedValue: AnimatedValue,
|
||||
): void {
|
||||
this.__active = true;
|
||||
this._fromValue = fromValue;
|
||||
this._onUpdate = onUpdate;
|
||||
this.__onEnd = onEnd;
|
||||
|
||||
const start = () => {
|
||||
// Animations that sometimes have 0 duration and sometimes do not
|
||||
// still need to use the native driver when duration is 0 so as to
|
||||
// not cause intermixed JS and native animations.
|
||||
if (this._duration === 0 && !this._useNativeDriver) {
|
||||
this._onUpdate(this._toValue);
|
||||
this.__debouncedOnEnd({finished: true});
|
||||
} else {
|
||||
this._startTime = Date.now();
|
||||
if (this._useNativeDriver) {
|
||||
this.__startNativeAnimation(animatedValue);
|
||||
} else {
|
||||
this._animationFrame = requestAnimationFrame(
|
||||
this.onUpdate.bind(this),
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
if (this._delay) {
|
||||
this._timeout = setTimeout(start, this._delay);
|
||||
} else {
|
||||
start();
|
||||
}
|
||||
}
|
||||
|
||||
onUpdate(): void {
|
||||
const now = Date.now();
|
||||
if (now >= this._startTime + this._duration) {
|
||||
if (this._duration === 0) {
|
||||
this._onUpdate(this._toValue);
|
||||
} else {
|
||||
this._onUpdate(
|
||||
this._fromValue + this._easing(1) * (this._toValue - this._fromValue),
|
||||
);
|
||||
}
|
||||
this.__debouncedOnEnd({finished: true});
|
||||
return;
|
||||
}
|
||||
|
||||
this._onUpdate(
|
||||
this._fromValue +
|
||||
this._easing((now - this._startTime) / this._duration) *
|
||||
(this._toValue - this._fromValue),
|
||||
);
|
||||
if (this.__active) {
|
||||
this._animationFrame = requestAnimationFrame(this.onUpdate.bind(this));
|
||||
}
|
||||
}
|
||||
|
||||
stop(): void {
|
||||
super.stop();
|
||||
this.__active = false;
|
||||
clearTimeout(this._timeout);
|
||||
global.cancelAnimationFrame(this._animationFrame);
|
||||
this.__debouncedOnEnd({finished: false});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TimingAnimation;
|
||||
@@ -1,200 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule createAnimatedComponent
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const {AnimatedEvent} = require('./AnimatedEvent');
|
||||
const AnimatedProps = require('./nodes/AnimatedProps');
|
||||
const React = require('React');
|
||||
const ViewStylePropTypes = require('ViewStylePropTypes');
|
||||
|
||||
function createAnimatedComponent(Component: any): any {
|
||||
class AnimatedComponent extends React.Component<Object> {
|
||||
_component: any;
|
||||
_invokeAnimatedPropsCallbackOnMount: boolean = false;
|
||||
_prevComponent: any;
|
||||
_propsAnimated: AnimatedProps;
|
||||
_eventDetachers: Array<Function> = [];
|
||||
_setComponentRef: Function;
|
||||
|
||||
static __skipSetNativeProps_FOR_TESTS_ONLY = false;
|
||||
|
||||
constructor(props: Object) {
|
||||
super(props);
|
||||
this._setComponentRef = this._setComponentRef.bind(this);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this._propsAnimated && this._propsAnimated.__detach();
|
||||
this._detachNativeEvents();
|
||||
}
|
||||
|
||||
setNativeProps(props) {
|
||||
this._component.setNativeProps(props);
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
this._attachProps(this.props);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (this._invokeAnimatedPropsCallbackOnMount) {
|
||||
this._invokeAnimatedPropsCallbackOnMount = false;
|
||||
this._animatedPropsCallback();
|
||||
}
|
||||
|
||||
this._propsAnimated.setNativeView(this._component);
|
||||
this._attachNativeEvents();
|
||||
}
|
||||
|
||||
_attachNativeEvents() {
|
||||
// Make sure to get the scrollable node for components that implement
|
||||
// `ScrollResponder.Mixin`.
|
||||
const scrollableNode = this._component.getScrollableNode
|
||||
? this._component.getScrollableNode()
|
||||
: this._component;
|
||||
|
||||
for (const key in this.props) {
|
||||
const prop = this.props[key];
|
||||
if (prop instanceof AnimatedEvent && prop.__isNative) {
|
||||
prop.__attach(scrollableNode, key);
|
||||
this._eventDetachers.push(() => prop.__detach(scrollableNode, key));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_detachNativeEvents() {
|
||||
this._eventDetachers.forEach(remove => remove());
|
||||
this._eventDetachers = [];
|
||||
}
|
||||
|
||||
// The system is best designed when setNativeProps is implemented. It is
|
||||
// able to avoid re-rendering and directly set the attributes that changed.
|
||||
// However, setNativeProps can only be implemented on leaf native
|
||||
// components. If you want to animate a composite component, you need to
|
||||
// re-render it. In this case, we have a fallback that uses forceUpdate.
|
||||
_animatedPropsCallback = () => {
|
||||
if (this._component == null) {
|
||||
// AnimatedProps is created in will-mount because it's used in render.
|
||||
// But this callback may be invoked before mount in async mode,
|
||||
// In which case we should defer the setNativeProps() call.
|
||||
// React may throw away uncommitted work in async mode,
|
||||
// So a deferred call won't always be invoked.
|
||||
this._invokeAnimatedPropsCallbackOnMount = true;
|
||||
} else if (
|
||||
AnimatedComponent.__skipSetNativeProps_FOR_TESTS_ONLY ||
|
||||
typeof this._component.setNativeProps !== 'function'
|
||||
) {
|
||||
this.forceUpdate();
|
||||
} else if (!this._propsAnimated.__isNative) {
|
||||
this._component.setNativeProps(
|
||||
this._propsAnimated.__getAnimatedValue(),
|
||||
);
|
||||
} else {
|
||||
throw new Error(
|
||||
'Attempting to run JS driven animation on animated ' +
|
||||
'node that has been moved to "native" earlier by starting an ' +
|
||||
'animation with `useNativeDriver: true`',
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
_attachProps(nextProps) {
|
||||
const oldPropsAnimated = this._propsAnimated;
|
||||
|
||||
this._propsAnimated = new AnimatedProps(
|
||||
nextProps,
|
||||
this._animatedPropsCallback,
|
||||
);
|
||||
|
||||
// When you call detach, it removes the element from the parent list
|
||||
// of children. If it goes to 0, then the parent also detaches itself
|
||||
// and so on.
|
||||
// An optimization is to attach the new elements and THEN detach the old
|
||||
// ones instead of detaching and THEN attaching.
|
||||
// This way the intermediate state isn't to go to 0 and trigger
|
||||
// this expensive recursive detaching to then re-attach everything on
|
||||
// the very next operation.
|
||||
oldPropsAnimated && oldPropsAnimated.__detach();
|
||||
}
|
||||
|
||||
componentWillReceiveProps(newProps) {
|
||||
this._attachProps(newProps);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (this._component !== this._prevComponent) {
|
||||
this._propsAnimated.setNativeView(this._component);
|
||||
}
|
||||
if (this._component !== this._prevComponent || prevProps !== this.props) {
|
||||
this._detachNativeEvents();
|
||||
this._attachNativeEvents();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const props = this._propsAnimated.__getValue();
|
||||
return (
|
||||
<Component
|
||||
{...props}
|
||||
ref={this._setComponentRef}
|
||||
// The native driver updates views directly through the UI thread so we
|
||||
// have to make sure the view doesn't get optimized away because it cannot
|
||||
// go through the NativeViewHierachyManager since it operates on the shadow
|
||||
// thread.
|
||||
collapsable={
|
||||
this._propsAnimated.__isNative ? false : props.collapsable
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
_setComponentRef(c) {
|
||||
this._prevComponent = this._component;
|
||||
this._component = c;
|
||||
}
|
||||
|
||||
// A third party library can use getNode()
|
||||
// to get the node reference of the decorated component
|
||||
getNode() {
|
||||
return this._component;
|
||||
}
|
||||
}
|
||||
|
||||
const propTypes = Component.propTypes;
|
||||
|
||||
AnimatedComponent.propTypes = {
|
||||
style: function(props, propName, componentName) {
|
||||
if (!propTypes) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const key in ViewStylePropTypes) {
|
||||
if (!propTypes[key] && props[key] !== undefined) {
|
||||
console.warn(
|
||||
'You are setting the style `{ ' +
|
||||
key +
|
||||
': ... }` as a prop. You ' +
|
||||
'should nest it in a style object. ' +
|
||||
'E.g. `{ style: { ' +
|
||||
key +
|
||||
': ... } }`',
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
return AnimatedComponent;
|
||||
}
|
||||
|
||||
module.exports = createAnimatedComponent;
|
||||
@@ -1,65 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule AnimatedAddition
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const AnimatedInterpolation = require('./AnimatedInterpolation');
|
||||
const AnimatedNode = require('./AnimatedNode');
|
||||
const AnimatedValue = require('./AnimatedValue');
|
||||
const AnimatedWithChildren = require('./AnimatedWithChildren');
|
||||
|
||||
import type {InterpolationConfigType} from './AnimatedInterpolation';
|
||||
|
||||
class AnimatedAddition extends AnimatedWithChildren {
|
||||
_a: AnimatedNode;
|
||||
_b: AnimatedNode;
|
||||
|
||||
constructor(a: AnimatedNode | number, b: AnimatedNode | number) {
|
||||
super();
|
||||
this._a = typeof a === 'number' ? new AnimatedValue(a) : a;
|
||||
this._b = typeof b === 'number' ? new AnimatedValue(b) : b;
|
||||
}
|
||||
|
||||
__makeNative() {
|
||||
this._a.__makeNative();
|
||||
this._b.__makeNative();
|
||||
super.__makeNative();
|
||||
}
|
||||
|
||||
__getValue(): number {
|
||||
return this._a.__getValue() + this._b.__getValue();
|
||||
}
|
||||
|
||||
interpolate(config: InterpolationConfigType): AnimatedInterpolation {
|
||||
return new AnimatedInterpolation(this, config);
|
||||
}
|
||||
|
||||
__attach(): void {
|
||||
this._a.__addChild(this);
|
||||
this._b.__addChild(this);
|
||||
}
|
||||
|
||||
__detach(): void {
|
||||
this._a.__removeChild(this);
|
||||
this._b.__removeChild(this);
|
||||
super.__detach();
|
||||
}
|
||||
|
||||
__getNativeConfig(): any {
|
||||
return {
|
||||
type: 'addition',
|
||||
input: [this._a.__getNativeTag(), this._b.__getNativeTag()],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AnimatedAddition;
|
||||
@@ -1,73 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule AnimatedDiffClamp
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const AnimatedInterpolation = require('./AnimatedInterpolation');
|
||||
const AnimatedNode = require('./AnimatedNode');
|
||||
const AnimatedWithChildren = require('./AnimatedWithChildren');
|
||||
|
||||
import type {InterpolationConfigType} from './AnimatedInterpolation';
|
||||
|
||||
class AnimatedDiffClamp extends AnimatedWithChildren {
|
||||
_a: AnimatedNode;
|
||||
_min: number;
|
||||
_max: number;
|
||||
_value: number;
|
||||
_lastValue: number;
|
||||
|
||||
constructor(a: AnimatedNode, min: number, max: number) {
|
||||
super();
|
||||
|
||||
this._a = a;
|
||||
this._min = min;
|
||||
this._max = max;
|
||||
this._value = this._lastValue = this._a.__getValue();
|
||||
}
|
||||
|
||||
__makeNative() {
|
||||
this._a.__makeNative();
|
||||
super.__makeNative();
|
||||
}
|
||||
|
||||
interpolate(config: InterpolationConfigType): AnimatedInterpolation {
|
||||
return new AnimatedInterpolation(this, config);
|
||||
}
|
||||
|
||||
__getValue(): number {
|
||||
const value = this._a.__getValue();
|
||||
const diff = value - this._lastValue;
|
||||
this._lastValue = value;
|
||||
this._value = Math.min(Math.max(this._value + diff, this._min), this._max);
|
||||
return this._value;
|
||||
}
|
||||
|
||||
__attach(): void {
|
||||
this._a.__addChild(this);
|
||||
}
|
||||
|
||||
__detach(): void {
|
||||
this._a.__removeChild(this);
|
||||
super.__detach();
|
||||
}
|
||||
|
||||
__getNativeConfig(): any {
|
||||
return {
|
||||
type: 'diffclamp',
|
||||
input: this._a.__getNativeTag(),
|
||||
min: this._min,
|
||||
max: this._max,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AnimatedDiffClamp;
|
||||
@@ -1,70 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule AnimatedDivision
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const AnimatedInterpolation = require('./AnimatedInterpolation');
|
||||
const AnimatedNode = require('./AnimatedNode');
|
||||
const AnimatedValue = require('./AnimatedValue');
|
||||
const AnimatedWithChildren = require('./AnimatedWithChildren');
|
||||
|
||||
import type {InterpolationConfigType} from './AnimatedInterpolation';
|
||||
|
||||
class AnimatedDivision extends AnimatedWithChildren {
|
||||
_a: AnimatedNode;
|
||||
_b: AnimatedNode;
|
||||
|
||||
constructor(a: AnimatedNode | number, b: AnimatedNode | number) {
|
||||
super();
|
||||
this._a = typeof a === 'number' ? new AnimatedValue(a) : a;
|
||||
this._b = typeof b === 'number' ? new AnimatedValue(b) : b;
|
||||
}
|
||||
|
||||
__makeNative() {
|
||||
this._a.__makeNative();
|
||||
this._b.__makeNative();
|
||||
super.__makeNative();
|
||||
}
|
||||
|
||||
__getValue(): number {
|
||||
const a = this._a.__getValue();
|
||||
const b = this._b.__getValue();
|
||||
if (b === 0) {
|
||||
console.error('Detected division by zero in AnimatedDivision');
|
||||
}
|
||||
return a / b;
|
||||
}
|
||||
|
||||
interpolate(config: InterpolationConfigType): AnimatedInterpolation {
|
||||
return new AnimatedInterpolation(this, config);
|
||||
}
|
||||
|
||||
__attach(): void {
|
||||
this._a.__addChild(this);
|
||||
this._b.__addChild(this);
|
||||
}
|
||||
|
||||
__detach(): void {
|
||||
this._a.__removeChild(this);
|
||||
this._b.__removeChild(this);
|
||||
super.__detach();
|
||||
}
|
||||
|
||||
__getNativeConfig(): any {
|
||||
return {
|
||||
type: 'division',
|
||||
input: [this._a.__getNativeTag(), this._b.__getNativeTag()],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AnimatedDivision;
|
||||
@@ -1,64 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule AnimatedModulo
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const AnimatedInterpolation = require('./AnimatedInterpolation');
|
||||
const AnimatedNode = require('./AnimatedNode');
|
||||
const AnimatedWithChildren = require('./AnimatedWithChildren');
|
||||
|
||||
import type {InterpolationConfigType} from './AnimatedInterpolation';
|
||||
|
||||
class AnimatedModulo extends AnimatedWithChildren {
|
||||
_a: AnimatedNode;
|
||||
_modulus: number;
|
||||
|
||||
constructor(a: AnimatedNode, modulus: number) {
|
||||
super();
|
||||
this._a = a;
|
||||
this._modulus = modulus;
|
||||
}
|
||||
|
||||
__makeNative() {
|
||||
this._a.__makeNative();
|
||||
super.__makeNative();
|
||||
}
|
||||
|
||||
__getValue(): number {
|
||||
return (
|
||||
(this._a.__getValue() % this._modulus + this._modulus) % this._modulus
|
||||
);
|
||||
}
|
||||
|
||||
interpolate(config: InterpolationConfigType): AnimatedInterpolation {
|
||||
return new AnimatedInterpolation(this, config);
|
||||
}
|
||||
|
||||
__attach(): void {
|
||||
this._a.__addChild(this);
|
||||
}
|
||||
|
||||
__detach(): void {
|
||||
this._a.__removeChild(this);
|
||||
super.__detach();
|
||||
}
|
||||
|
||||
__getNativeConfig(): any {
|
||||
return {
|
||||
type: 'modulus',
|
||||
input: this._a.__getNativeTag(),
|
||||
modulus: this._modulus,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AnimatedModulo;
|
||||
@@ -1,65 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule AnimatedMultiplication
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const AnimatedInterpolation = require('./AnimatedInterpolation');
|
||||
const AnimatedNode = require('./AnimatedNode');
|
||||
const AnimatedValue = require('./AnimatedValue');
|
||||
const AnimatedWithChildren = require('./AnimatedWithChildren');
|
||||
|
||||
import type {InterpolationConfigType} from './AnimatedInterpolation';
|
||||
|
||||
class AnimatedMultiplication extends AnimatedWithChildren {
|
||||
_a: AnimatedNode;
|
||||
_b: AnimatedNode;
|
||||
|
||||
constructor(a: AnimatedNode | number, b: AnimatedNode | number) {
|
||||
super();
|
||||
this._a = typeof a === 'number' ? new AnimatedValue(a) : a;
|
||||
this._b = typeof b === 'number' ? new AnimatedValue(b) : b;
|
||||
}
|
||||
|
||||
__makeNative() {
|
||||
this._a.__makeNative();
|
||||
this._b.__makeNative();
|
||||
super.__makeNative();
|
||||
}
|
||||
|
||||
__getValue(): number {
|
||||
return this._a.__getValue() * this._b.__getValue();
|
||||
}
|
||||
|
||||
interpolate(config: InterpolationConfigType): AnimatedInterpolation {
|
||||
return new AnimatedInterpolation(this, config);
|
||||
}
|
||||
|
||||
__attach(): void {
|
||||
this._a.__addChild(this);
|
||||
this._b.__addChild(this);
|
||||
}
|
||||
|
||||
__detach(): void {
|
||||
this._a.__removeChild(this);
|
||||
this._b.__removeChild(this);
|
||||
super.__detach();
|
||||
}
|
||||
|
||||
__getNativeConfig(): any {
|
||||
return {
|
||||
type: 'multiplication',
|
||||
input: [this._a.__getNativeTag(), this._b.__getNativeTag()],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AnimatedMultiplication;
|
||||
@@ -1,73 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule AnimatedNode
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const NativeAnimatedHelper = require('../NativeAnimatedHelper');
|
||||
|
||||
const invariant = require('fbjs/lib/invariant');
|
||||
|
||||
// Note(vjeux): this would be better as an interface but flow doesn't
|
||||
// support them yet
|
||||
class AnimatedNode {
|
||||
__attach(): void {}
|
||||
__detach(): void {
|
||||
if (this.__isNative && this.__nativeTag != null) {
|
||||
NativeAnimatedHelper.API.dropAnimatedNode(this.__nativeTag);
|
||||
this.__nativeTag = undefined;
|
||||
}
|
||||
}
|
||||
__getValue(): any {}
|
||||
__getAnimatedValue(): any {
|
||||
return this.__getValue();
|
||||
}
|
||||
__addChild(child: AnimatedNode) {}
|
||||
__removeChild(child: AnimatedNode) {}
|
||||
__getChildren(): Array<AnimatedNode> {
|
||||
return [];
|
||||
}
|
||||
|
||||
/* Methods and props used by native Animated impl */
|
||||
__isNative: boolean;
|
||||
__nativeTag: ?number;
|
||||
__makeNative() {
|
||||
if (!this.__isNative) {
|
||||
throw new Error('This node cannot be made a "native" animated node');
|
||||
}
|
||||
}
|
||||
__getNativeTag(): ?number {
|
||||
NativeAnimatedHelper.assertNativeAnimatedModule();
|
||||
invariant(
|
||||
this.__isNative,
|
||||
'Attempt to get native tag from node not marked as "native"',
|
||||
);
|
||||
if (this.__nativeTag == null) {
|
||||
const nativeTag: ?number = NativeAnimatedHelper.generateNewNodeTag();
|
||||
NativeAnimatedHelper.API.createAnimatedNode(
|
||||
nativeTag,
|
||||
this.__getNativeConfig(),
|
||||
);
|
||||
this.__nativeTag = nativeTag;
|
||||
}
|
||||
return this.__nativeTag;
|
||||
}
|
||||
__getNativeConfig(): Object {
|
||||
throw new Error(
|
||||
'This JS animated node type cannot be used as native animated node',
|
||||
);
|
||||
}
|
||||
toJSON(): any {
|
||||
return this.__getValue();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AnimatedNode;
|
||||
@@ -1,167 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule AnimatedProps
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const {AnimatedEvent} = require('../AnimatedEvent');
|
||||
const AnimatedNode = require('./AnimatedNode');
|
||||
const AnimatedStyle = require('./AnimatedStyle');
|
||||
const NativeAnimatedHelper = require('../NativeAnimatedHelper');
|
||||
const ReactNative = require('ReactNative');
|
||||
|
||||
const invariant = require('fbjs/lib/invariant');
|
||||
|
||||
class AnimatedProps extends AnimatedNode {
|
||||
_props: Object;
|
||||
_animatedView: any;
|
||||
_callback: () => void;
|
||||
|
||||
constructor(props: Object, callback: () => void) {
|
||||
super();
|
||||
if (props.style) {
|
||||
props = {
|
||||
...props,
|
||||
style: new AnimatedStyle(props.style),
|
||||
};
|
||||
}
|
||||
this._props = props;
|
||||
this._callback = callback;
|
||||
this.__attach();
|
||||
}
|
||||
|
||||
__getValue(): Object {
|
||||
const props = {};
|
||||
for (const key in this._props) {
|
||||
const value = this._props[key];
|
||||
if (value instanceof AnimatedNode) {
|
||||
if (!value.__isNative || value instanceof AnimatedStyle) {
|
||||
// We cannot use value of natively driven nodes this way as the value we have access from
|
||||
// JS may not be up to date.
|
||||
props[key] = value.__getValue();
|
||||
}
|
||||
} else if (value instanceof AnimatedEvent) {
|
||||
props[key] = value.__getHandler();
|
||||
} else {
|
||||
props[key] = value;
|
||||
}
|
||||
}
|
||||
return props;
|
||||
}
|
||||
|
||||
__getAnimatedValue(): Object {
|
||||
const props = {};
|
||||
for (const key in this._props) {
|
||||
const value = this._props[key];
|
||||
if (value instanceof AnimatedNode) {
|
||||
props[key] = value.__getAnimatedValue();
|
||||
}
|
||||
}
|
||||
return props;
|
||||
}
|
||||
|
||||
__attach(): void {
|
||||
for (const key in this._props) {
|
||||
const value = this._props[key];
|
||||
if (value instanceof AnimatedNode) {
|
||||
value.__addChild(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
__detach(): void {
|
||||
if (this.__isNative && this._animatedView) {
|
||||
this.__disconnectAnimatedView();
|
||||
}
|
||||
for (const key in this._props) {
|
||||
const value = this._props[key];
|
||||
if (value instanceof AnimatedNode) {
|
||||
value.__removeChild(this);
|
||||
}
|
||||
}
|
||||
super.__detach();
|
||||
}
|
||||
|
||||
update(): void {
|
||||
this._callback();
|
||||
}
|
||||
|
||||
__makeNative(): void {
|
||||
if (!this.__isNative) {
|
||||
this.__isNative = true;
|
||||
for (const key in this._props) {
|
||||
const value = this._props[key];
|
||||
if (value instanceof AnimatedNode) {
|
||||
value.__makeNative();
|
||||
}
|
||||
}
|
||||
if (this._animatedView) {
|
||||
this.__connectAnimatedView();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setNativeView(animatedView: any): void {
|
||||
if (this._animatedView === animatedView) {
|
||||
return;
|
||||
}
|
||||
this._animatedView = animatedView;
|
||||
if (this.__isNative) {
|
||||
this.__connectAnimatedView();
|
||||
}
|
||||
}
|
||||
|
||||
__connectAnimatedView(): void {
|
||||
invariant(this.__isNative, 'Expected node to be marked as "native"');
|
||||
const nativeViewTag: ?number = ReactNative.findNodeHandle(
|
||||
this._animatedView,
|
||||
);
|
||||
invariant(
|
||||
nativeViewTag != null,
|
||||
'Unable to locate attached view in the native tree',
|
||||
);
|
||||
NativeAnimatedHelper.API.connectAnimatedNodeToView(
|
||||
this.__getNativeTag(),
|
||||
nativeViewTag,
|
||||
);
|
||||
}
|
||||
|
||||
__disconnectAnimatedView(): void {
|
||||
invariant(this.__isNative, 'Expected node to be marked as "native"');
|
||||
const nativeViewTag: ?number = ReactNative.findNodeHandle(
|
||||
this._animatedView,
|
||||
);
|
||||
invariant(
|
||||
nativeViewTag != null,
|
||||
'Unable to locate attached view in the native tree',
|
||||
);
|
||||
NativeAnimatedHelper.API.disconnectAnimatedNodeFromView(
|
||||
this.__getNativeTag(),
|
||||
nativeViewTag,
|
||||
);
|
||||
}
|
||||
|
||||
__getNativeConfig(): Object {
|
||||
const propsConfig = {};
|
||||
for (const propKey in this._props) {
|
||||
const value = this._props[propKey];
|
||||
if (value instanceof AnimatedNode) {
|
||||
propsConfig[propKey] = value.__getNativeTag();
|
||||
}
|
||||
}
|
||||
return {
|
||||
type: 'props',
|
||||
props: propsConfig,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AnimatedProps;
|
||||
@@ -1,127 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule AnimatedStyle
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const AnimatedNode = require('./AnimatedNode');
|
||||
const AnimatedTransform = require('./AnimatedTransform');
|
||||
const AnimatedWithChildren = require('./AnimatedWithChildren');
|
||||
const NativeAnimatedHelper = require('../NativeAnimatedHelper');
|
||||
|
||||
const flattenStyle = require('flattenStyle');
|
||||
|
||||
class AnimatedStyle extends AnimatedWithChildren {
|
||||
_style: Object;
|
||||
|
||||
constructor(style: any) {
|
||||
super();
|
||||
style = flattenStyle(style) || {};
|
||||
if (style.transform) {
|
||||
style = {
|
||||
...style,
|
||||
transform: new AnimatedTransform(style.transform),
|
||||
};
|
||||
}
|
||||
this._style = style;
|
||||
}
|
||||
|
||||
// Recursively get values for nested styles (like iOS's shadowOffset)
|
||||
_walkStyleAndGetValues(style) {
|
||||
const updatedStyle = {};
|
||||
for (const key in style) {
|
||||
const value = style[key];
|
||||
if (value instanceof AnimatedNode) {
|
||||
if (!value.__isNative) {
|
||||
// We cannot use value of natively driven nodes this way as the value we have access from
|
||||
// JS may not be up to date.
|
||||
updatedStyle[key] = value.__getValue();
|
||||
}
|
||||
} else if (value && !Array.isArray(value) && typeof value === 'object') {
|
||||
// Support animating nested values (for example: shadowOffset.height)
|
||||
updatedStyle[key] = this._walkStyleAndGetValues(value);
|
||||
} else {
|
||||
updatedStyle[key] = value;
|
||||
}
|
||||
}
|
||||
return updatedStyle;
|
||||
}
|
||||
|
||||
__getValue(): Object {
|
||||
return this._walkStyleAndGetValues(this._style);
|
||||
}
|
||||
|
||||
// Recursively get animated values for nested styles (like iOS's shadowOffset)
|
||||
_walkStyleAndGetAnimatedValues(style) {
|
||||
const updatedStyle = {};
|
||||
for (const key in style) {
|
||||
const value = style[key];
|
||||
if (value instanceof AnimatedNode) {
|
||||
updatedStyle[key] = value.__getAnimatedValue();
|
||||
} else if (value && !Array.isArray(value) && typeof value === 'object') {
|
||||
// Support animating nested values (for example: shadowOffset.height)
|
||||
updatedStyle[key] = this._walkStyleAndGetAnimatedValues(value);
|
||||
}
|
||||
}
|
||||
return updatedStyle;
|
||||
}
|
||||
|
||||
__getAnimatedValue(): Object {
|
||||
return this._walkStyleAndGetAnimatedValues(this._style);
|
||||
}
|
||||
|
||||
__attach(): void {
|
||||
for (const key in this._style) {
|
||||
const value = this._style[key];
|
||||
if (value instanceof AnimatedNode) {
|
||||
value.__addChild(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
__detach(): void {
|
||||
for (const key in this._style) {
|
||||
const value = this._style[key];
|
||||
if (value instanceof AnimatedNode) {
|
||||
value.__removeChild(this);
|
||||
}
|
||||
}
|
||||
super.__detach();
|
||||
}
|
||||
|
||||
__makeNative() {
|
||||
super.__makeNative();
|
||||
for (const key in this._style) {
|
||||
const value = this._style[key];
|
||||
if (value instanceof AnimatedNode) {
|
||||
value.__makeNative();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
__getNativeConfig(): Object {
|
||||
const styleConfig = {};
|
||||
for (const styleKey in this._style) {
|
||||
if (this._style[styleKey] instanceof AnimatedNode) {
|
||||
styleConfig[styleKey] = this._style[styleKey].__getNativeTag();
|
||||
}
|
||||
// Non-animated styles are set using `setNativeProps`, no need
|
||||
// to pass those as a part of the node config
|
||||
}
|
||||
NativeAnimatedHelper.validateStyles(styleConfig);
|
||||
return {
|
||||
type: 'style',
|
||||
style: styleConfig,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AnimatedStyle;
|
||||
@@ -1,67 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule AnimatedTracking
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const AnimatedValue = require('./AnimatedValue');
|
||||
const AnimatedNode = require('./AnimatedNode');
|
||||
|
||||
import type {EndCallback} from '../animations/Animation';
|
||||
|
||||
class AnimatedTracking extends AnimatedNode {
|
||||
_value: AnimatedValue;
|
||||
_parent: AnimatedNode;
|
||||
_callback: ?EndCallback;
|
||||
_animationConfig: Object;
|
||||
_animationClass: any;
|
||||
|
||||
constructor(
|
||||
value: AnimatedValue,
|
||||
parent: AnimatedNode,
|
||||
animationClass: any,
|
||||
animationConfig: Object,
|
||||
callback?: ?EndCallback,
|
||||
) {
|
||||
super();
|
||||
this._value = value;
|
||||
this._parent = parent;
|
||||
this._animationClass = animationClass;
|
||||
this._animationConfig = animationConfig;
|
||||
this._callback = callback;
|
||||
this.__attach();
|
||||
}
|
||||
|
||||
__getValue(): Object {
|
||||
return this._parent.__getValue();
|
||||
}
|
||||
|
||||
__attach(): void {
|
||||
this._parent.__addChild(this);
|
||||
}
|
||||
|
||||
__detach(): void {
|
||||
this._parent.__removeChild(this);
|
||||
super.__detach();
|
||||
}
|
||||
|
||||
update(): void {
|
||||
this._value.animate(
|
||||
new this._animationClass({
|
||||
...this._animationConfig,
|
||||
toValue: (this._animationConfig.toValue: any).__getValue(),
|
||||
}),
|
||||
this._callback,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AnimatedTracking;
|
||||
@@ -1,123 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule AnimatedTransform
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const AnimatedNode = require('./AnimatedNode');
|
||||
const AnimatedWithChildren = require('./AnimatedWithChildren');
|
||||
const NativeAnimatedHelper = require('../NativeAnimatedHelper');
|
||||
|
||||
class AnimatedTransform extends AnimatedWithChildren {
|
||||
_transforms: Array<Object>;
|
||||
|
||||
constructor(transforms: Array<Object>) {
|
||||
super();
|
||||
this._transforms = transforms;
|
||||
}
|
||||
|
||||
__makeNative() {
|
||||
super.__makeNative();
|
||||
this._transforms.forEach(transform => {
|
||||
for (const key in transform) {
|
||||
const value = transform[key];
|
||||
if (value instanceof AnimatedNode) {
|
||||
value.__makeNative();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
__getValue(): Array<Object> {
|
||||
return this._transforms.map(transform => {
|
||||
const result = {};
|
||||
for (const key in transform) {
|
||||
const value = transform[key];
|
||||
if (value instanceof AnimatedNode) {
|
||||
result[key] = value.__getValue();
|
||||
} else {
|
||||
result[key] = value;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
__getAnimatedValue(): Array<Object> {
|
||||
return this._transforms.map(transform => {
|
||||
const result = {};
|
||||
for (const key in transform) {
|
||||
const value = transform[key];
|
||||
if (value instanceof AnimatedNode) {
|
||||
result[key] = value.__getAnimatedValue();
|
||||
} else {
|
||||
// All transform components needed to recompose matrix
|
||||
result[key] = value;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
__attach(): void {
|
||||
this._transforms.forEach(transform => {
|
||||
for (const key in transform) {
|
||||
const value = transform[key];
|
||||
if (value instanceof AnimatedNode) {
|
||||
value.__addChild(this);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
__detach(): void {
|
||||
this._transforms.forEach(transform => {
|
||||
for (const key in transform) {
|
||||
const value = transform[key];
|
||||
if (value instanceof AnimatedNode) {
|
||||
value.__removeChild(this);
|
||||
}
|
||||
}
|
||||
});
|
||||
super.__detach();
|
||||
}
|
||||
|
||||
__getNativeConfig(): any {
|
||||
const transConfigs = [];
|
||||
|
||||
this._transforms.forEach(transform => {
|
||||
for (const key in transform) {
|
||||
const value = transform[key];
|
||||
if (value instanceof AnimatedNode) {
|
||||
transConfigs.push({
|
||||
type: 'animated',
|
||||
property: key,
|
||||
nodeTag: value.__getNativeTag(),
|
||||
});
|
||||
} else {
|
||||
transConfigs.push({
|
||||
type: 'static',
|
||||
property: key,
|
||||
value,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
NativeAnimatedHelper.validateTransform(transConfigs);
|
||||
return {
|
||||
type: 'transform',
|
||||
transforms: transConfigs,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AnimatedTransform;
|
||||
@@ -1,338 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule AnimatedValue
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const AnimatedInterpolation = require('./AnimatedInterpolation');
|
||||
const AnimatedNode = require('./AnimatedNode');
|
||||
const AnimatedWithChildren = require('./AnimatedWithChildren');
|
||||
const InteractionManager = require('InteractionManager');
|
||||
const NativeAnimatedHelper = require('../NativeAnimatedHelper');
|
||||
|
||||
import type Animation, {EndCallback} from '../animations/Animation';
|
||||
import type {InterpolationConfigType} from './AnimatedInterpolation';
|
||||
|
||||
const NativeAnimatedAPI = NativeAnimatedHelper.API;
|
||||
|
||||
type ValueListenerCallback = (state: {value: number}) => void;
|
||||
|
||||
let _uniqueId = 1;
|
||||
|
||||
/**
|
||||
* Animated works by building a directed acyclic graph of dependencies
|
||||
* transparently when you render your Animated components.
|
||||
*
|
||||
* new Animated.Value(0)
|
||||
* .interpolate() .interpolate() new Animated.Value(1)
|
||||
* opacity translateY scale
|
||||
* style transform
|
||||
* View#234 style
|
||||
* View#123
|
||||
*
|
||||
* A) Top Down phase
|
||||
* When an Animated.Value is updated, we recursively go down through this
|
||||
* graph in order to find leaf nodes: the views that we flag as needing
|
||||
* an update.
|
||||
*
|
||||
* B) Bottom Up phase
|
||||
* When a view is flagged as needing an update, we recursively go back up
|
||||
* in order to build the new value that it needs. The reason why we need
|
||||
* this two-phases process is to deal with composite props such as
|
||||
* transform which can receive values from multiple parents.
|
||||
*/
|
||||
function _flush(rootNode: AnimatedValue): void {
|
||||
const animatedStyles = new Set();
|
||||
function findAnimatedStyles(node) {
|
||||
if (typeof node.update === 'function') {
|
||||
animatedStyles.add(node);
|
||||
} else {
|
||||
node.__getChildren().forEach(findAnimatedStyles);
|
||||
}
|
||||
}
|
||||
findAnimatedStyles(rootNode);
|
||||
/* $FlowFixMe */
|
||||
animatedStyles.forEach(animatedStyle => animatedStyle.update());
|
||||
}
|
||||
|
||||
/**
|
||||
* Standard value for driving animations. One `Animated.Value` can drive
|
||||
* multiple properties in a synchronized fashion, but can only be driven by one
|
||||
* mechanism at a time. Using a new mechanism (e.g. starting a new animation,
|
||||
* or calling `setValue`) will stop any previous ones.
|
||||
*
|
||||
* See http://facebook.github.io/react-native/docs/animatedvalue.html
|
||||
*/
|
||||
class AnimatedValue extends AnimatedWithChildren {
|
||||
_value: number;
|
||||
_startingValue: number;
|
||||
_offset: number;
|
||||
_animation: ?Animation;
|
||||
_tracking: ?AnimatedNode;
|
||||
_listeners: {[key: string]: ValueListenerCallback};
|
||||
__nativeAnimatedValueListener: ?any;
|
||||
|
||||
constructor(value: number) {
|
||||
super();
|
||||
this._startingValue = this._value = value;
|
||||
this._offset = 0;
|
||||
this._animation = null;
|
||||
this._listeners = {};
|
||||
}
|
||||
|
||||
__detach() {
|
||||
this.stopAnimation();
|
||||
super.__detach();
|
||||
}
|
||||
|
||||
__getValue(): number {
|
||||
return this._value + this._offset;
|
||||
}
|
||||
|
||||
__makeNative() {
|
||||
super.__makeNative();
|
||||
|
||||
if (Object.keys(this._listeners).length) {
|
||||
this._startListeningToNativeValueUpdates();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Directly set the value. This will stop any animations running on the value
|
||||
* and update all the bound properties.
|
||||
*
|
||||
* See http://facebook.github.io/react-native/docs/animatedvalue.html#setvalue
|
||||
*/
|
||||
setValue(value: number): void {
|
||||
if (this._animation) {
|
||||
this._animation.stop();
|
||||
this._animation = null;
|
||||
}
|
||||
this._updateValue(
|
||||
value,
|
||||
!this.__isNative /* don't perform a flush for natively driven values */,
|
||||
);
|
||||
if (this.__isNative) {
|
||||
NativeAnimatedAPI.setAnimatedNodeValue(this.__getNativeTag(), value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an offset that is applied on top of whatever value is set, whether via
|
||||
* `setValue`, an animation, or `Animated.event`. Useful for compensating
|
||||
* things like the start of a pan gesture.
|
||||
*
|
||||
* See http://facebook.github.io/react-native/docs/animatedvalue.html#setoffset
|
||||
*/
|
||||
setOffset(offset: number): void {
|
||||
this._offset = offset;
|
||||
if (this.__isNative) {
|
||||
NativeAnimatedAPI.setAnimatedNodeOffset(this.__getNativeTag(), offset);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges the offset value into the base value and resets the offset to zero.
|
||||
* The final output of the value is unchanged.
|
||||
*
|
||||
* See http://facebook.github.io/react-native/docs/animatedvalue.html#flattenoffset
|
||||
*/
|
||||
flattenOffset(): void {
|
||||
this._value += this._offset;
|
||||
this._offset = 0;
|
||||
if (this.__isNative) {
|
||||
NativeAnimatedAPI.flattenAnimatedNodeOffset(this.__getNativeTag());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the offset value to the base value, and resets the base value to zero.
|
||||
* The final output of the value is unchanged.
|
||||
*
|
||||
* See http://facebook.github.io/react-native/docs/animatedvalue.html#extractoffset
|
||||
*/
|
||||
extractOffset(): void {
|
||||
this._offset += this._value;
|
||||
this._value = 0;
|
||||
if (this.__isNative) {
|
||||
NativeAnimatedAPI.extractAnimatedNodeOffset(this.__getNativeTag());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an asynchronous listener to the value so you can observe updates from
|
||||
* animations. This is useful because there is no way to
|
||||
* synchronously read the value because it might be driven natively.
|
||||
*
|
||||
* See http://facebook.github.io/react-native/docs/animatedvalue.html#addlistener
|
||||
*/
|
||||
addListener(callback: ValueListenerCallback): string {
|
||||
const id = String(_uniqueId++);
|
||||
this._listeners[id] = callback;
|
||||
if (this.__isNative) {
|
||||
this._startListeningToNativeValueUpdates();
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister a listener. The `id` param shall match the identifier
|
||||
* previously returned by `addListener()`.
|
||||
*
|
||||
* See http://facebook.github.io/react-native/docs/animatedvalue.html#removelistener
|
||||
*/
|
||||
removeListener(id: string): void {
|
||||
delete this._listeners[id];
|
||||
if (this.__isNative && Object.keys(this._listeners).length === 0) {
|
||||
this._stopListeningForNativeValueUpdates();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all registered listeners.
|
||||
*
|
||||
* See http://facebook.github.io/react-native/docs/animatedvalue.html#removealllisteners
|
||||
*/
|
||||
removeAllListeners(): void {
|
||||
this._listeners = {};
|
||||
if (this.__isNative) {
|
||||
this._stopListeningForNativeValueUpdates();
|
||||
}
|
||||
}
|
||||
|
||||
_startListeningToNativeValueUpdates() {
|
||||
if (this.__nativeAnimatedValueListener) {
|
||||
return;
|
||||
}
|
||||
|
||||
NativeAnimatedAPI.startListeningToAnimatedNodeValue(this.__getNativeTag());
|
||||
this.__nativeAnimatedValueListener = NativeAnimatedHelper.nativeEventEmitter.addListener(
|
||||
'onAnimatedValueUpdate',
|
||||
data => {
|
||||
if (data.tag !== this.__getNativeTag()) {
|
||||
return;
|
||||
}
|
||||
this._updateValue(data.value, false /* flush */);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
_stopListeningForNativeValueUpdates() {
|
||||
if (!this.__nativeAnimatedValueListener) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.__nativeAnimatedValueListener.remove();
|
||||
this.__nativeAnimatedValueListener = null;
|
||||
NativeAnimatedAPI.stopListeningToAnimatedNodeValue(this.__getNativeTag());
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops any running animation or tracking. `callback` is invoked with the
|
||||
* final value after stopping the animation, which is useful for updating
|
||||
* state to match the animation position with layout.
|
||||
*
|
||||
* See http://facebook.github.io/react-native/docs/animatedvalue.html#stopanimation
|
||||
*/
|
||||
stopAnimation(callback?: ?(value: number) => void): void {
|
||||
this.stopTracking();
|
||||
this._animation && this._animation.stop();
|
||||
this._animation = null;
|
||||
callback && callback(this.__getValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops any animation and resets the value to its original.
|
||||
*
|
||||
* See http://facebook.github.io/react-native/docs/animatedvalue.html#resetanimation
|
||||
*/
|
||||
resetAnimation(callback?: ?(value: number) => void): void {
|
||||
this.stopAnimation(callback);
|
||||
this._value = this._startingValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interpolates the value before updating the property, e.g. mapping 0-1 to
|
||||
* 0-10.
|
||||
*/
|
||||
interpolate(config: InterpolationConfigType): AnimatedInterpolation {
|
||||
return new AnimatedInterpolation(this, config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Typically only used internally, but could be used by a custom Animation
|
||||
* class.
|
||||
*
|
||||
* See http://facebook.github.io/react-native/docs/animatedvalue.html#animate
|
||||
*/
|
||||
animate(animation: Animation, callback: ?EndCallback): void {
|
||||
let handle = null;
|
||||
if (animation.__isInteraction) {
|
||||
handle = InteractionManager.createInteractionHandle();
|
||||
}
|
||||
const previousAnimation = this._animation;
|
||||
this._animation && this._animation.stop();
|
||||
this._animation = animation;
|
||||
animation.start(
|
||||
this._value,
|
||||
value => {
|
||||
// Natively driven animations will never call into that callback, therefore we can always
|
||||
// pass flush = true to allow the updated value to propagate to native with setNativeProps
|
||||
this._updateValue(value, true /* flush */);
|
||||
},
|
||||
result => {
|
||||
this._animation = null;
|
||||
if (handle !== null) {
|
||||
InteractionManager.clearInteractionHandle(handle);
|
||||
}
|
||||
callback && callback(result);
|
||||
},
|
||||
previousAnimation,
|
||||
this,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Typically only used internally.
|
||||
*/
|
||||
stopTracking(): void {
|
||||
this._tracking && this._tracking.__detach();
|
||||
this._tracking = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Typically only used internally.
|
||||
*/
|
||||
track(tracking: AnimatedNode): void {
|
||||
this.stopTracking();
|
||||
this._tracking = tracking;
|
||||
}
|
||||
|
||||
_updateValue(value: number, flush: boolean): void {
|
||||
this._value = value;
|
||||
if (flush) {
|
||||
_flush(this);
|
||||
}
|
||||
for (const key in this._listeners) {
|
||||
this._listeners[key]({value: this.__getValue()});
|
||||
}
|
||||
}
|
||||
|
||||
__getNativeConfig(): Object {
|
||||
return {
|
||||
type: 'value',
|
||||
value: this._value,
|
||||
offset: this._offset,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AnimatedValue;
|
||||
@@ -1,197 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule AnimatedValueXY
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const AnimatedValue = require('./AnimatedValue');
|
||||
const AnimatedWithChildren = require('./AnimatedWithChildren');
|
||||
|
||||
const invariant = require('fbjs/lib/invariant');
|
||||
|
||||
type ValueXYListenerCallback = (value: {x: number, y: number}) => void;
|
||||
|
||||
let _uniqueId = 1;
|
||||
|
||||
/**
|
||||
* 2D Value for driving 2D animations, such as pan gestures. Almost identical
|
||||
* API to normal `Animated.Value`, but multiplexed.
|
||||
*
|
||||
* See http://facebook.github.io/react-native/docs/animatedvaluexy.html
|
||||
*/
|
||||
class AnimatedValueXY extends AnimatedWithChildren {
|
||||
x: AnimatedValue;
|
||||
y: AnimatedValue;
|
||||
_listeners: {[key: string]: {x: string, y: string}};
|
||||
|
||||
constructor(
|
||||
valueIn?: ?{x: number | AnimatedValue, y: number | AnimatedValue},
|
||||
) {
|
||||
super();
|
||||
const value: any = valueIn || {x: 0, y: 0}; // @flowfixme: shouldn't need `: any`
|
||||
if (typeof value.x === 'number' && typeof value.y === 'number') {
|
||||
this.x = new AnimatedValue(value.x);
|
||||
this.y = new AnimatedValue(value.y);
|
||||
} else {
|
||||
invariant(
|
||||
value.x instanceof AnimatedValue && value.y instanceof AnimatedValue,
|
||||
'AnimatedValueXY must be initalized with an object of numbers or ' +
|
||||
'AnimatedValues.',
|
||||
);
|
||||
this.x = value.x;
|
||||
this.y = value.y;
|
||||
}
|
||||
this._listeners = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Directly set the value. This will stop any animations running on the value
|
||||
* and update all the bound properties.
|
||||
*
|
||||
* See http://facebook.github.io/react-native/docs/animatedvaluexy.html#setvalue
|
||||
*/
|
||||
setValue(value: {x: number, y: number}) {
|
||||
this.x.setValue(value.x);
|
||||
this.y.setValue(value.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an offset that is applied on top of whatever value is set, whether
|
||||
* via `setValue`, an animation, or `Animated.event`. Useful for compensating
|
||||
* things like the start of a pan gesture.
|
||||
*
|
||||
* See http://facebook.github.io/react-native/docs/animatedvaluexy.html#setoffset
|
||||
*/
|
||||
setOffset(offset: {x: number, y: number}) {
|
||||
this.x.setOffset(offset.x);
|
||||
this.y.setOffset(offset.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges the offset value into the base value and resets the offset to zero.
|
||||
* The final output of the value is unchanged.
|
||||
*
|
||||
* See http://facebook.github.io/react-native/docs/animatedvaluexy.html#flattenoffset
|
||||
*/
|
||||
flattenOffset(): void {
|
||||
this.x.flattenOffset();
|
||||
this.y.flattenOffset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the offset value to the base value, and resets the base value to
|
||||
* zero. The final output of the value is unchanged.
|
||||
*
|
||||
* See http://facebook.github.io/react-native/docs/animatedvaluexy.html#extractoffset
|
||||
*/
|
||||
extractOffset(): void {
|
||||
this.x.extractOffset();
|
||||
this.y.extractOffset();
|
||||
}
|
||||
|
||||
__getValue(): {x: number, y: number} {
|
||||
return {
|
||||
x: this.x.__getValue(),
|
||||
y: this.y.__getValue(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops any animation and resets the value to its original.
|
||||
*
|
||||
* See http://facebook.github.io/react-native/docs/animatedvaluexy.html#resetanimation
|
||||
*/
|
||||
resetAnimation(callback?: (value: {x: number, y: number}) => void): void {
|
||||
this.x.resetAnimation();
|
||||
this.y.resetAnimation();
|
||||
callback && callback(this.__getValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops any running animation or tracking. `callback` is invoked with the
|
||||
* final value after stopping the animation, which is useful for updating
|
||||
* state to match the animation position with layout.
|
||||
*
|
||||
* See http://facebook.github.io/react-native/docs/animatedvaluexy.html#stopanimation
|
||||
*/
|
||||
stopAnimation(callback?: (value: {x: number, y: number}) => void): void {
|
||||
this.x.stopAnimation();
|
||||
this.y.stopAnimation();
|
||||
callback && callback(this.__getValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an asynchronous listener to the value so you can observe updates from
|
||||
* animations. This is useful because there is no way to synchronously read
|
||||
* the value because it might be driven natively.
|
||||
*
|
||||
* Returns a string that serves as an identifier for the listener.
|
||||
*
|
||||
* See http://facebook.github.io/react-native/docs/animatedvaluexy.html#addlistener
|
||||
*/
|
||||
addListener(callback: ValueXYListenerCallback): string {
|
||||
const id = String(_uniqueId++);
|
||||
const jointCallback = ({value: number}) => {
|
||||
callback(this.__getValue());
|
||||
};
|
||||
this._listeners[id] = {
|
||||
x: this.x.addListener(jointCallback),
|
||||
y: this.y.addListener(jointCallback),
|
||||
};
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister a listener. The `id` param shall match the identifier
|
||||
* previously returned by `addListener()`.
|
||||
*
|
||||
* See http://facebook.github.io/react-native/docs/animatedvaluexy.html#removelistener
|
||||
*/
|
||||
removeListener(id: string): void {
|
||||
this.x.removeListener(this._listeners[id].x);
|
||||
this.y.removeListener(this._listeners[id].y);
|
||||
delete this._listeners[id];
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all registered listeners.
|
||||
*
|
||||
* See http://facebook.github.io/react-native/docs/animatedvaluexy.html#removealllisteners
|
||||
*/
|
||||
removeAllListeners(): void {
|
||||
this.x.removeAllListeners();
|
||||
this.y.removeAllListeners();
|
||||
this._listeners = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts `{x, y}` into `{left, top}` for use in style.
|
||||
*
|
||||
* See http://facebook.github.io/react-native/docs/animatedvaluexy.html#getlayout
|
||||
*/
|
||||
getLayout(): {[key: string]: AnimatedValue} {
|
||||
return {
|
||||
left: this.x,
|
||||
top: this.y,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts `{x, y}` into a useable translation transform.
|
||||
*
|
||||
* See http://facebook.github.io/react-native/docs/animatedvaluexy.html#gettranslatetransform
|
||||
*/
|
||||
getTranslateTransform(): Array<{[key: string]: AnimatedValue}> {
|
||||
return [{translateX: this.x}, {translateY: this.y}];
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AnimatedValueXY;
|
||||
@@ -1,77 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule AnimatedWithChildren
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const AnimatedNode = require('./AnimatedNode');
|
||||
const NativeAnimatedHelper = require('../NativeAnimatedHelper');
|
||||
|
||||
class AnimatedWithChildren extends AnimatedNode {
|
||||
_children: Array<AnimatedNode>;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this._children = [];
|
||||
}
|
||||
|
||||
__makeNative() {
|
||||
if (!this.__isNative) {
|
||||
this.__isNative = true;
|
||||
for (const child of this._children) {
|
||||
child.__makeNative();
|
||||
NativeAnimatedHelper.API.connectAnimatedNodes(
|
||||
this.__getNativeTag(),
|
||||
child.__getNativeTag(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
__addChild(child: AnimatedNode): void {
|
||||
if (this._children.length === 0) {
|
||||
this.__attach();
|
||||
}
|
||||
this._children.push(child);
|
||||
if (this.__isNative) {
|
||||
// Only accept "native" animated nodes as children
|
||||
child.__makeNative();
|
||||
NativeAnimatedHelper.API.connectAnimatedNodes(
|
||||
this.__getNativeTag(),
|
||||
child.__getNativeTag(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
__removeChild(child: AnimatedNode): void {
|
||||
const index = this._children.indexOf(child);
|
||||
if (index === -1) {
|
||||
console.warn("Trying to remove a child that doesn't exist");
|
||||
return;
|
||||
}
|
||||
if (this.__isNative && child.__isNative) {
|
||||
NativeAnimatedHelper.API.disconnectAnimatedNodes(
|
||||
this.__getNativeTag(),
|
||||
child.__getNativeTag(),
|
||||
);
|
||||
}
|
||||
this._children.splice(index, 1);
|
||||
if (this._children.length === 0) {
|
||||
this.__detach();
|
||||
}
|
||||
}
|
||||
|
||||
__getChildren(): Array<AnimatedNode> {
|
||||
return this._children;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AnimatedWithChildren;
|
||||
@@ -23,8 +23,66 @@ const invariant = require('fbjs/lib/invariant');
|
||||
* `AppState` can tell you if the app is in the foreground or background,
|
||||
* and notify you when the state changes.
|
||||
*
|
||||
* See http://facebook.github.io/react-native/docs/appstate.html
|
||||
* AppState is frequently used to determine the intent and proper behavior when
|
||||
* handling push notifications.
|
||||
*
|
||||
* ### App States
|
||||
*
|
||||
* - `active` - The app is running in the foreground
|
||||
* - `background` - The app is running in the background. The user is either
|
||||
* in another app or on the home screen
|
||||
* - `inactive` - This is a state that occurs when transitioning between
|
||||
* foreground & background, and during periods of inactivity such as
|
||||
* entering the Multitasking view or in the event of an incoming call
|
||||
*
|
||||
* For more information, see
|
||||
* [Apple's documentation](https://developer.apple.com/library/ios/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/TheAppLifeCycle/TheAppLifeCycle.html)
|
||||
*
|
||||
* ### Basic Usage
|
||||
*
|
||||
* To see the current state, you can check `AppState.currentState`, which
|
||||
* will be kept up-to-date. However, `currentState` will be null at launch
|
||||
* while `AppState` retrieves it over the bridge.
|
||||
*
|
||||
* ```
|
||||
* import React, {Component} from 'react'
|
||||
* import {AppState, Text} from 'react-native'
|
||||
*
|
||||
* class AppStateExample extends Component {
|
||||
*
|
||||
* state = {
|
||||
* appState: AppState.currentState
|
||||
* }
|
||||
*
|
||||
* componentDidMount() {
|
||||
* AppState.addEventListener('change', this._handleAppStateChange);
|
||||
* }
|
||||
*
|
||||
* componentWillUnmount() {
|
||||
* AppState.removeEventListener('change', this._handleAppStateChange);
|
||||
* }
|
||||
*
|
||||
* _handleAppStateChange = (nextAppState) => {
|
||||
* if (this.state.appState.match(/inactive|background/) && nextAppState === 'active') {
|
||||
* console.log('App has come to the foreground!')
|
||||
* }
|
||||
* this.setState({appState: nextAppState});
|
||||
* }
|
||||
*
|
||||
* render() {
|
||||
* return (
|
||||
* <Text>Current state is: {this.state.appState}</Text>
|
||||
* );
|
||||
* }
|
||||
*
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* This example will only ever appear to say "Current state is: active" because
|
||||
* the app is only visible to the user when in the `active` state, and the null
|
||||
* state will happen only momentarily.
|
||||
*/
|
||||
|
||||
class AppState extends NativeEventEmitter {
|
||||
|
||||
_eventHandlers: Object;
|
||||
@@ -44,43 +102,37 @@ class AppState extends NativeEventEmitter {
|
||||
// the Android implementation.
|
||||
this.currentState = RCTAppState.initialAppState || 'active';
|
||||
|
||||
let eventUpdated = false;
|
||||
|
||||
// TODO: this is a terrible solution - in order to ensure `currentState`
|
||||
// prop is up to date, we have to register an observer that updates it
|
||||
// whenever the state changes, even if nobody cares. We should just
|
||||
// deprecate the `currentState` property and get rid of this.
|
||||
// TODO: this is a terrible solution - in order to ensure `currentState` prop
|
||||
// is up to date, we have to register an observer that updates it whenever
|
||||
// the state changes, even if nobody cares. We should just deprecate the
|
||||
// `currentState` property and get rid of this.
|
||||
this.addListener(
|
||||
'appStateDidChange',
|
||||
(appStateData) => {
|
||||
eventUpdated = true;
|
||||
this.currentState = appStateData.app_state;
|
||||
}
|
||||
);
|
||||
|
||||
// TODO: see above - this request just populates the value of `currentState`
|
||||
// when the module is first initialized. Would be better to get rid of the
|
||||
// prop and expose `getCurrentAppState` method directly.
|
||||
// when the module is first initialized. Would be better to get rid of the prop
|
||||
// and expose `getCurrentAppState` method directly.
|
||||
RCTAppState.getCurrentAppState(
|
||||
(appStateData) => {
|
||||
if (!eventUpdated) {
|
||||
this.currentState = appStateData.app_state;
|
||||
}
|
||||
this.currentState = appStateData.app_state;
|
||||
},
|
||||
logError
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: now that AppState is a subclass of NativeEventEmitter, we could
|
||||
// deprecate `addEventListener` and `removeEventListener` and just use
|
||||
// addListener` and `listener.remove()` directly. That will be a breaking
|
||||
// change though, as both the method and event names are different
|
||||
// (addListener events are currently required to be globally unique).
|
||||
/**
|
||||
/**
|
||||
* Add a handler to AppState changes by listening to the `change` event type
|
||||
* and providing the handler.
|
||||
*
|
||||
* See http://facebook.github.io/react-native/docs/appstate.html#addeventlistener
|
||||
* and providing the handler
|
||||
*
|
||||
* TODO: now that AppState is a subclass of NativeEventEmitter, we could deprecate
|
||||
* `addEventListener` and `removeEventListener` and just use `addListener` and
|
||||
* `listener.remove()` directly. That will be a breaking change though, as both
|
||||
* the method and event names are different (addListener events are currently
|
||||
* required to be globally unique).
|
||||
*/
|
||||
addEventListener(
|
||||
type: string,
|
||||
@@ -106,9 +158,7 @@ class AppState extends NativeEventEmitter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a handler by passing the `change` event type and the handler.
|
||||
*
|
||||
* See http://facebook.github.io/react-native/docs/appstate.html#removeeventlistener
|
||||
* Remove a handler by passing the `change` event type and the handler
|
||||
*/
|
||||
removeEventListener(
|
||||
type: string,
|
||||
@@ -145,10 +195,9 @@ if (__DEV__ && !RCTAppState) {
|
||||
}
|
||||
}
|
||||
|
||||
// This module depends on the native `RCTAppState` module. If you don't
|
||||
// include it, `AppState.isAvailable` will return `false`, and any method
|
||||
// calls will throw. We reassign the class variable to keep the autodoc
|
||||
// generator happy.
|
||||
// This module depends on the native `RCTAppState` module. If you don't include it,
|
||||
// `AppState.isAvailable` will return `false`, and any method calls will throw.
|
||||
// We reassign the class variable to keep the autodoc generator happy.
|
||||
AppState = new MissingNativeAppStateShim();
|
||||
} else {
|
||||
AppState = new AppState();
|
||||
|
||||
@@ -12,21 +12,7 @@
|
||||
'use strict';
|
||||
|
||||
const MessageQueue = require('MessageQueue');
|
||||
|
||||
// MessageQueue can install a global handler to catch all exceptions where JS users can register their own behavior
|
||||
// This handler makes all exceptions to be handled inside MessageQueue rather than by the VM at its origin
|
||||
// This makes stacktraces to be placed at MessageQueue rather than at where they were launched
|
||||
// The parameter __fbUninstallRNGlobalErrorHandler is passed to MessageQueue to prevent the handler from being installed
|
||||
//
|
||||
// __fbUninstallRNGlobalErrorHandler is conditionally set by the Inspector while the VM is paused for intialization
|
||||
// If the Inspector isn't present it defaults to undefined and the global error handler is installed
|
||||
// The Inspector can still call MessageQueue#uninstallGlobalErrorHandler to uninstalled on attach
|
||||
|
||||
const BatchedBridge = new MessageQueue(
|
||||
// $FlowFixMe
|
||||
typeof __fbUninstallRNGlobalErrorHandler !== 'undefined' &&
|
||||
__fbUninstallRNGlobalErrorHandler === true, // eslint-disable-line no-undef
|
||||
);
|
||||
const BatchedBridge = new MessageQueue();
|
||||
|
||||
// Wire up the batched bridge on the global object so that we can call into it.
|
||||
// Ideally, this would be the inverse relationship. I.e. the native environment
|
||||
|
||||
@@ -8,9 +8,10 @@
|
||||
*
|
||||
* @providesModule MessageQueue
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
|
||||
/*eslint no-bitwise: 0*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const ErrorUtils = require('ErrorUtils');
|
||||
@@ -23,9 +24,9 @@ const stringifySafe = require('stringifySafe');
|
||||
export type SpyData = {
|
||||
type: number,
|
||||
module: ?string,
|
||||
method: string | number,
|
||||
args: any[],
|
||||
};
|
||||
method: string|number,
|
||||
args: any
|
||||
}
|
||||
|
||||
const TO_JS = 0;
|
||||
const TO_NATIVE = 1;
|
||||
@@ -35,7 +36,6 @@ const METHOD_IDS = 1;
|
||||
const PARAMS = 2;
|
||||
const MIN_TIME_BETWEEN_FLUSHES_MS = 5;
|
||||
|
||||
// eslint-disable-next-line no-bitwise
|
||||
const TRACE_TAG_REACT_APPS = 1 << 17;
|
||||
|
||||
const DEBUG_INFO_LIMIT = 32;
|
||||
@@ -44,24 +44,22 @@ const DEBUG_INFO_LIMIT = 32;
|
||||
let JSTimers = null;
|
||||
|
||||
class MessageQueue {
|
||||
_lazyCallableModules: {[key: string]: (void) => Object};
|
||||
_queue: [number[], number[], any[], number];
|
||||
_successCallbacks: (?Function)[];
|
||||
_failureCallbacks: (?Function)[];
|
||||
_lazyCallableModules: {[key: string]: void => Object};
|
||||
_queue: [Array<number>, Array<number>, Array<any>, number];
|
||||
_successCallbacks: Array<?Function>;
|
||||
_failureCallbacks: Array<?Function>;
|
||||
_callID: number;
|
||||
_inCall: number;
|
||||
_lastFlush: number;
|
||||
_eventLoopStartTime: number;
|
||||
|
||||
_debugInfo: {[number]: [number, number]};
|
||||
_remoteModuleTable: {[number]: string};
|
||||
_remoteMethodTable: {[number]: string[]};
|
||||
_debugInfo: Object;
|
||||
_remoteModuleTable: Object;
|
||||
_remoteMethodTable: Object;
|
||||
|
||||
__spy: ?(data: SpyData) => void;
|
||||
|
||||
__guard: (() => void) => void;
|
||||
|
||||
constructor(shouldUninstallGlobalErrorHandler: boolean = false) {
|
||||
constructor() {
|
||||
this._lazyCallableModules = {};
|
||||
this._queue = [[], [], [], 0];
|
||||
this._successCallbacks = [];
|
||||
@@ -69,11 +67,6 @@ class MessageQueue {
|
||||
this._callID = 0;
|
||||
this._lastFlush = 0;
|
||||
this._eventLoopStartTime = new Date().getTime();
|
||||
if (shouldUninstallGlobalErrorHandler) {
|
||||
this.uninstallGlobalErrorHandler();
|
||||
} else {
|
||||
this.installGlobalErrorHandler();
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
this._debugInfo = {};
|
||||
@@ -81,30 +74,22 @@ class MessageQueue {
|
||||
this._remoteMethodTable = {};
|
||||
}
|
||||
|
||||
(this: any).callFunctionReturnFlushedQueue = this.callFunctionReturnFlushedQueue.bind(
|
||||
this,
|
||||
);
|
||||
(this: any).callFunctionReturnResultAndFlushedQueue = this.callFunctionReturnResultAndFlushedQueue.bind(
|
||||
this,
|
||||
);
|
||||
(this: any).flushedQueue = this.flushedQueue.bind(this);
|
||||
(this: any).invokeCallbackAndReturnFlushedQueue = this.invokeCallbackAndReturnFlushedQueue.bind(
|
||||
this,
|
||||
);
|
||||
(this:any).callFunctionReturnFlushedQueue = this.callFunctionReturnFlushedQueue.bind(this);
|
||||
(this:any).callFunctionReturnResultAndFlushedQueue = this.callFunctionReturnResultAndFlushedQueue.bind(this);
|
||||
(this:any).flushedQueue = this.flushedQueue.bind(this);
|
||||
(this:any).invokeCallbackAndReturnFlushedQueue = this.invokeCallbackAndReturnFlushedQueue.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Public APIs
|
||||
*/
|
||||
|
||||
static spy(spyOrToggle: boolean | ((data: SpyData) => void)) {
|
||||
if (spyOrToggle === true) {
|
||||
static spy(spyOrToggle: boolean|(data: SpyData) => void){
|
||||
if (spyOrToggle === true){
|
||||
MessageQueue.prototype.__spy = info => {
|
||||
console.log(
|
||||
`${info.type === TO_JS ? 'N->JS' : 'JS->N'} : ` +
|
||||
`${info.module ? info.module + '.' : ''}${info.method}` +
|
||||
`(${JSON.stringify(info.args)})`,
|
||||
);
|
||||
console.log(`${info.type === TO_JS ? 'N->JS' : 'JS->N'} : ` +
|
||||
`${info.module ? (info.module + '.') : ''}${info.method}` +
|
||||
`(${JSON.stringify(info.args)})`);
|
||||
};
|
||||
} else if (spyOrToggle === false) {
|
||||
MessageQueue.prototype.__spy = null;
|
||||
@@ -113,7 +98,7 @@ class MessageQueue {
|
||||
}
|
||||
}
|
||||
|
||||
callFunctionReturnFlushedQueue(module: string, method: string, args: any[]) {
|
||||
callFunctionReturnFlushedQueue(module: string, method: string, args: Array<any>) {
|
||||
this.__guard(() => {
|
||||
this.__callFunction(module, method, args);
|
||||
});
|
||||
@@ -121,11 +106,7 @@ class MessageQueue {
|
||||
return this.flushedQueue();
|
||||
}
|
||||
|
||||
callFunctionReturnResultAndFlushedQueue(
|
||||
module: string,
|
||||
method: string,
|
||||
args: any[],
|
||||
) {
|
||||
callFunctionReturnResultAndFlushedQueue(module: string, method: string, args: Array<any>) {
|
||||
let result;
|
||||
this.__guard(() => {
|
||||
result = this.__callFunction(module, method, args);
|
||||
@@ -134,7 +115,7 @@ class MessageQueue {
|
||||
return [result, this.flushedQueue()];
|
||||
}
|
||||
|
||||
invokeCallbackAndReturnFlushedQueue(cbID: number, args: any[]) {
|
||||
invokeCallbackAndReturnFlushedQueue(cbID: number, args: Array<any>) {
|
||||
this.__guard(() => {
|
||||
this.__invokeCallback(cbID, args);
|
||||
});
|
||||
@@ -162,7 +143,7 @@ class MessageQueue {
|
||||
|
||||
registerLazyCallableModule(name: string, factory: void => Object) {
|
||||
let module: Object;
|
||||
let getValue: ?(void) => Object = factory;
|
||||
let getValue: ?(void => Object) = factory;
|
||||
this._lazyCallableModules[name] = () => {
|
||||
if (getValue) {
|
||||
module = getValue();
|
||||
@@ -177,13 +158,7 @@ class MessageQueue {
|
||||
return getValue ? getValue() : null;
|
||||
}
|
||||
|
||||
enqueueNativeCall(
|
||||
moduleID: number,
|
||||
methodID: number,
|
||||
params: any[],
|
||||
onFail: ?Function,
|
||||
onSucc: ?Function,
|
||||
) {
|
||||
enqueueNativeCall(moduleID: number, methodID: number, params: Array<any>, onFail: ?Function, onSucc: ?Function) {
|
||||
if (onFail || onSucc) {
|
||||
if (__DEV__) {
|
||||
this._debugInfo[this._callID] = [moduleID, methodID];
|
||||
@@ -193,9 +168,7 @@ class MessageQueue {
|
||||
}
|
||||
// Encode callIDs into pairs of callback identifiers by shifting left and using the rightmost bit
|
||||
// to indicate fail (0) or success (1)
|
||||
// eslint-disable-next-line no-bitwise
|
||||
onFail && params.push(this._callID << 1);
|
||||
// eslint-disable-next-line no-bitwise
|
||||
onSucc && params.push((this._callID << 1) | 1);
|
||||
this._successCallbacks[this._callID] = onSucc;
|
||||
this._failureCallbacks[this._callID] = onFail;
|
||||
@@ -203,11 +176,7 @@ class MessageQueue {
|
||||
|
||||
if (__DEV__) {
|
||||
global.nativeTraceBeginAsyncFlow &&
|
||||
global.nativeTraceBeginAsyncFlow(
|
||||
TRACE_TAG_REACT_APPS,
|
||||
'native',
|
||||
this._callID,
|
||||
);
|
||||
global.nativeTraceBeginAsyncFlow(TRACE_TAG_REACT_APPS, 'native', this._callID);
|
||||
}
|
||||
this._callID++;
|
||||
|
||||
@@ -215,52 +184,18 @@ class MessageQueue {
|
||||
this._queue[METHOD_IDS].push(methodID);
|
||||
|
||||
if (__DEV__) {
|
||||
// Validate that parameters passed over the bridge are
|
||||
// folly-convertible. As a special case, if a prop value is a
|
||||
// function it is permitted here, and special-cased in the
|
||||
// conversion.
|
||||
const isValidArgument = val => {
|
||||
const t = typeof val;
|
||||
if (
|
||||
t === 'undefined' ||
|
||||
t === 'null' ||
|
||||
t === 'boolean' ||
|
||||
t === 'number' ||
|
||||
t === 'string'
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
if (t === 'function' || t !== 'object') {
|
||||
return false;
|
||||
}
|
||||
if (Array.isArray(val)) {
|
||||
return val.every(isValidArgument);
|
||||
}
|
||||
for (const k in val) {
|
||||
if (typeof val[k] !== 'function' && !isValidArgument(val[k])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
invariant(
|
||||
isValidArgument(params),
|
||||
'%s is not usable as a native method argument',
|
||||
params,
|
||||
);
|
||||
// Any params sent over the bridge should be encodable as JSON
|
||||
JSON.stringify(params);
|
||||
|
||||
// The params object should not be mutated after being queued
|
||||
deepFreezeAndThrowOnMutationInDev((params: any));
|
||||
deepFreezeAndThrowOnMutationInDev((params:any));
|
||||
}
|
||||
this._queue[PARAMS].push(params);
|
||||
|
||||
const now = new Date().getTime();
|
||||
if (
|
||||
global.nativeFlushQueueImmediate &&
|
||||
(now - this._lastFlush >= MIN_TIME_BETWEEN_FLUSHES_MS ||
|
||||
this._inCall === 0)
|
||||
) {
|
||||
if (global.nativeFlushQueueImmediate &&
|
||||
(now - this._lastFlush >= MIN_TIME_BETWEEN_FLUSHES_MS ||
|
||||
this._inCall === 0)) {
|
||||
var queue = this._queue;
|
||||
this._queue = [[], [], [], this._callID];
|
||||
this._lastFlush = now;
|
||||
@@ -268,49 +203,29 @@ class MessageQueue {
|
||||
}
|
||||
Systrace.counterEvent('pending_js_to_native_queue', this._queue[0].length);
|
||||
if (__DEV__ && this.__spy && isFinite(moduleID)) {
|
||||
this.__spy({
|
||||
type: TO_NATIVE,
|
||||
module: this._remoteModuleTable[moduleID],
|
||||
method: this._remoteMethodTable[moduleID][methodID],
|
||||
args: params,
|
||||
});
|
||||
this.__spy(
|
||||
{ type: TO_NATIVE,
|
||||
module: this._remoteModuleTable[moduleID],
|
||||
method: this._remoteMethodTable[moduleID][methodID],
|
||||
args: params }
|
||||
);
|
||||
} else if (this.__spy) {
|
||||
this.__spy({
|
||||
type: TO_NATIVE,
|
||||
module: moduleID + '',
|
||||
method: methodID,
|
||||
args: params,
|
||||
});
|
||||
this.__spy({type: TO_NATIVE, module: moduleID + '', method: methodID, args: params});
|
||||
}
|
||||
}
|
||||
|
||||
createDebugLookup(moduleID: number, name: string, methods: string[]) {
|
||||
createDebugLookup(moduleID: number, name: string, methods: Array<string>) {
|
||||
if (__DEV__) {
|
||||
this._remoteModuleTable[moduleID] = name;
|
||||
this._remoteMethodTable[moduleID] = methods;
|
||||
}
|
||||
}
|
||||
|
||||
uninstallGlobalErrorHandler() {
|
||||
this.__guard = this.__guardUnsafe;
|
||||
}
|
||||
|
||||
installGlobalErrorHandler() {
|
||||
this.__guard = this.__guardSafe;
|
||||
}
|
||||
|
||||
/**
|
||||
* Private methods
|
||||
*/
|
||||
|
||||
// Lets exceptions propagate to be handled by the VM at the origin
|
||||
__guardUnsafe(fn: () => void) {
|
||||
this._inCall++;
|
||||
fn();
|
||||
this._inCall--;
|
||||
}
|
||||
|
||||
__guardSafe(fn: () => void) {
|
||||
__guard(fn: () => void) {
|
||||
this._inCall++;
|
||||
try {
|
||||
fn();
|
||||
@@ -330,43 +245,36 @@ class MessageQueue {
|
||||
Systrace.endEvent();
|
||||
}
|
||||
|
||||
__callFunction(module: string, method: string, args: any[]): any {
|
||||
__callFunction(module: string, method: string, args: Array<any>) {
|
||||
this._lastFlush = new Date().getTime();
|
||||
this._eventLoopStartTime = this._lastFlush;
|
||||
Systrace.beginEvent(`${module}.${method}()`);
|
||||
if (this.__spy) {
|
||||
this.__spy({type: TO_JS, module, method, args});
|
||||
this.__spy({ type: TO_JS, module, method, args});
|
||||
}
|
||||
const moduleMethods = this.getCallableModule(module);
|
||||
invariant(
|
||||
!!moduleMethods,
|
||||
'Module %s is not a registered callable module (calling %s)',
|
||||
module,
|
||||
method,
|
||||
module, method
|
||||
);
|
||||
invariant(
|
||||
!!moduleMethods[method],
|
||||
'Method %s does not exist on module %s',
|
||||
method,
|
||||
module,
|
||||
method, module
|
||||
);
|
||||
const result = moduleMethods[method].apply(moduleMethods, args);
|
||||
Systrace.endEvent();
|
||||
return result;
|
||||
}
|
||||
|
||||
__invokeCallback(cbID: number, args: any[]) {
|
||||
__invokeCallback(cbID: number, args: Array<any>) {
|
||||
this._lastFlush = new Date().getTime();
|
||||
this._eventLoopStartTime = this._lastFlush;
|
||||
|
||||
// The rightmost bit of cbID indicates fail (0) or success (1), the other bits are the callID shifted left.
|
||||
// eslint-disable-next-line no-bitwise
|
||||
const callID = cbID >>> 1;
|
||||
// eslint-disable-next-line no-bitwise
|
||||
const isSuccess = cbID & 1;
|
||||
const callback = isSuccess
|
||||
? this._successCallbacks[callID]
|
||||
: this._failureCallbacks[callID];
|
||||
const callback = (cbID & 1) ? this._successCallbacks[callID] : this._failureCallbacks[callID];
|
||||
|
||||
if (__DEV__) {
|
||||
const debug = this._debugInfo[callID];
|
||||
@@ -375,21 +283,20 @@ class MessageQueue {
|
||||
if (!callback) {
|
||||
let errorMessage = `Callback with id ${cbID}: ${module}.${method}() not found`;
|
||||
if (method) {
|
||||
errorMessage =
|
||||
`The callback ${method}() exists in module ${module}, ` +
|
||||
'but only one callback may be registered to a function in a native module.';
|
||||
errorMessage = `The callback ${method}() exists in module ${module}, `
|
||||
+ 'but only one callback may be registered to a function in a native module.';
|
||||
}
|
||||
invariant(callback, errorMessage);
|
||||
invariant(
|
||||
callback,
|
||||
errorMessage
|
||||
);
|
||||
}
|
||||
const profileName = debug
|
||||
? '<callback for ' + module + '.' + method + '>'
|
||||
: cbID;
|
||||
const profileName = debug ? '<callback for ' + module + '.' + method + '>' : cbID;
|
||||
if (callback && this.__spy) {
|
||||
this.__spy({type: TO_JS, module: null, method: profileName, args});
|
||||
this.__spy({ type: TO_JS, module:null, method:profileName, args });
|
||||
}
|
||||
Systrace.beginEvent(
|
||||
`MessageQueue.invokeCallback(${profileName}, ${stringifySafe(args)})`,
|
||||
);
|
||||
`MessageQueue.invokeCallback(${profileName}, ${stringifySafe(args)})`);
|
||||
}
|
||||
|
||||
if (!callback) {
|
||||
@@ -397,7 +304,7 @@ class MessageQueue {
|
||||
}
|
||||
|
||||
this._successCallbacks[callID] = this._failureCallbacks[callID] = null;
|
||||
callback(...args);
|
||||
callback.apply(null, args);
|
||||
|
||||
if (__DEV__) {
|
||||
Systrace.endEvent();
|
||||
|
||||
@@ -6,8 +6,6 @@
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @emails oncall+react_native
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
@@ -39,12 +37,10 @@ describe('MessageQueue', function() {
|
||||
queue = new MessageQueue();
|
||||
queue.registerCallableModule(
|
||||
'MessageQueueTestModule',
|
||||
MessageQueueTestModule,
|
||||
MessageQueueTestModule
|
||||
);
|
||||
queue.createDebugLookup(0, 'MessageQueueTestModule', [
|
||||
'testHook1',
|
||||
'testHook2',
|
||||
]);
|
||||
queue.createDebugLookup(0, 'MessageQueueTestModule',
|
||||
['testHook1', 'testHook2']);
|
||||
});
|
||||
|
||||
it('should enqueue native calls', () => {
|
||||
@@ -54,10 +50,10 @@ describe('MessageQueue', function() {
|
||||
});
|
||||
|
||||
it('should call a local function with the function name', () => {
|
||||
MessageQueueTestModule.testHook2 = jest.fn();
|
||||
expect(MessageQueueTestModule.testHook2.mock.calls.length).toEqual(0);
|
||||
MessageQueueTestModule.testHook2 = jasmine.createSpy();
|
||||
expect(MessageQueueTestModule.testHook2.calls.count()).toEqual(0);
|
||||
queue.__callFunction('MessageQueueTestModule', 'testHook2', [2]);
|
||||
expect(MessageQueueTestModule.testHook2.mock.calls.length).toEqual(1);
|
||||
expect(MessageQueueTestModule.testHook2.calls.count()).toEqual(1);
|
||||
});
|
||||
|
||||
it('should store callbacks', () => {
|
||||
@@ -68,118 +64,50 @@ describe('MessageQueue', function() {
|
||||
|
||||
it('should call the stored callback', () => {
|
||||
let done = false;
|
||||
queue.enqueueNativeCall(
|
||||
0,
|
||||
1,
|
||||
[],
|
||||
() => {},
|
||||
() => {
|
||||
done = true;
|
||||
},
|
||||
);
|
||||
queue.__invokeCallback(1, []);
|
||||
queue.enqueueNativeCall(0, 1, [], () => {}, () => { done = true; });
|
||||
queue.__invokeCallback(1);
|
||||
expect(done).toEqual(true);
|
||||
});
|
||||
|
||||
it('should throw when calling the same callback twice', () => {
|
||||
queue.enqueueNativeCall(0, 1, [], () => {}, () => {});
|
||||
queue.__invokeCallback(1, []);
|
||||
expect(() => queue.__invokeCallback(1, [])).toThrow();
|
||||
queue.__invokeCallback(1);
|
||||
expect(() => queue.__invokeCallback(1)).toThrow();
|
||||
});
|
||||
|
||||
it('should throw when calling both success and failure callback', () => {
|
||||
queue.enqueueNativeCall(0, 1, [], () => {}, () => {});
|
||||
queue.__invokeCallback(1, []);
|
||||
expect(() => queue.__invokeCallback(0, [])).toThrow();
|
||||
queue.__invokeCallback(1);
|
||||
expect(() => queue.__invokeCallback(0)).toThrow();
|
||||
});
|
||||
|
||||
it('should throw when calling with unknown module', () => {
|
||||
const unknownModule = 'UnknownModule',
|
||||
unknownMethod = 'UnknownMethod';
|
||||
const unknownModule = 'UnknownModule', unknownMethod = 'UnknownMethod';
|
||||
expect(() => queue.__callFunction(unknownModule, unknownMethod)).toThrow(
|
||||
`Module ${unknownModule} is not a registered callable module (calling ${unknownMethod})`,
|
||||
);
|
||||
});
|
||||
|
||||
it('should return lazily registered module', () => {
|
||||
const dummyModule = {},
|
||||
name = 'modulesName';
|
||||
const dummyModule = {}, name = 'modulesName';
|
||||
queue.registerLazyCallableModule(name, () => dummyModule);
|
||||
|
||||
expect(queue.getCallableModule(name)).toEqual(dummyModule);
|
||||
});
|
||||
|
||||
it('should not initialize lazily registered module before it was used for the first time', () => {
|
||||
const dummyModule = {},
|
||||
name = 'modulesName';
|
||||
const dummyModule = {}, name = 'modulesName';
|
||||
const factory = jest.fn(() => dummyModule);
|
||||
queue.registerLazyCallableModule(name, factory);
|
||||
expect(factory).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should initialize lazily registered module only once', () => {
|
||||
const dummyModule = {},
|
||||
name = 'modulesName';
|
||||
const dummyModule = {}, name = 'modulesName';
|
||||
const factory = jest.fn(() => dummyModule);
|
||||
queue.registerLazyCallableModule(name, factory);
|
||||
queue.getCallableModule(name);
|
||||
queue.getCallableModule(name);
|
||||
expect(factory).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should catch all exceptions if the global error handler is installed', () => {
|
||||
const errorMessage = 'intentional error';
|
||||
const errorModule = {
|
||||
explode: function() {
|
||||
throw new Error(errorMessage);
|
||||
},
|
||||
};
|
||||
const name = 'errorModuleName';
|
||||
const factory = jest.fn(() => errorModule);
|
||||
queue.__guardSafe = jest.fn(() => {});
|
||||
queue.__guardUnsafe = jest.fn(() => {});
|
||||
queue.installGlobalErrorHandler();
|
||||
queue.registerLazyCallableModule(name, factory);
|
||||
queue.callFunctionReturnFlushedQueue(name, 'explode', []);
|
||||
expect(queue.__guardUnsafe).toHaveBeenCalledTimes(0);
|
||||
expect(queue.__guardSafe).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('should propagate exceptions if the global error handler is uninstalled', () => {
|
||||
queue.uninstallGlobalErrorHandler();
|
||||
const errorMessage = 'intentional error';
|
||||
const errorModule = {
|
||||
explode: function() {
|
||||
throw new Error(errorMessage);
|
||||
},
|
||||
};
|
||||
const name = 'errorModuleName';
|
||||
const factory = jest.fn(() => errorModule);
|
||||
queue.__guardUnsafe = jest.fn(() => {});
|
||||
queue.__guardSafe = jest.fn(() => {});
|
||||
queue.registerLazyCallableModule(name, factory);
|
||||
queue.uninstallGlobalErrorHandler();
|
||||
queue.callFunctionReturnFlushedQueue(name, 'explode');
|
||||
expect(queue.__guardUnsafe).toHaveBeenCalledTimes(2);
|
||||
expect(queue.__guardSafe).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it('should catch all exceptions if the global error handler is re-installed', () => {
|
||||
const errorMessage = 'intentional error';
|
||||
const errorModule = {
|
||||
explode: function() {
|
||||
throw new Error(errorMessage);
|
||||
},
|
||||
};
|
||||
const name = 'errorModuleName';
|
||||
const factory = jest.fn(() => errorModule);
|
||||
queue.__guardUnsafe = jest.fn(() => {});
|
||||
queue.__guardSafe = jest.fn(() => {});
|
||||
queue.registerLazyCallableModule(name, factory);
|
||||
queue.uninstallGlobalErrorHandler();
|
||||
queue.installGlobalErrorHandler();
|
||||
queue.callFunctionReturnFlushedQueue(name, 'explode');
|
||||
expect(queue.__guardUnsafe).toHaveBeenCalledTimes(0);
|
||||
expect(queue.__guardSafe).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @emails oncall+react_native
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
@@ -57,8 +56,8 @@ describe('MessageQueue', function() {
|
||||
});
|
||||
|
||||
it('should make round trip and clear memory', function() {
|
||||
const onFail = jest.fn();
|
||||
const onSucc = jest.fn();
|
||||
const onFail = jasmine.createSpy();
|
||||
const onSucc = jasmine.createSpy();
|
||||
|
||||
// Perform communication
|
||||
NativeModules.RemoteModule1.promiseMethod('paloAlto', 'menloPark', onFail, onSucc);
|
||||
@@ -98,8 +97,8 @@ describe('MessageQueue', function() {
|
||||
expect(function() {
|
||||
BatchedBridge.__invokeCallback(firstSuccCBID, ['firstSucc']);
|
||||
}).toThrow();
|
||||
expect(onFail.mock.calls.length).toBe(1);
|
||||
expect(onSucc.mock.calls.length).toBe(0);
|
||||
expect(onFail.calls.count()).toBe(1);
|
||||
expect(onSucc.calls.count()).toBe(0);
|
||||
|
||||
// Handle the second remote invocation by signaling success.
|
||||
BatchedBridge.__invokeCallback(secondSuccCBID, ['secondSucc']);
|
||||
@@ -107,7 +106,7 @@ describe('MessageQueue', function() {
|
||||
expect(function() {
|
||||
BatchedBridge.__invokeCallback(secondFailCBID, ['secondFail']);
|
||||
}).toThrow();
|
||||
expect(onFail.mock.calls.length).toBe(1);
|
||||
expect(onSucc.mock.calls.length).toBe(1);
|
||||
expect(onFail.calls.count()).toBe(1);
|
||||
expect(onSucc.calls.count()).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,156 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule Blob
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const invariant = require('fbjs/lib/invariant');
|
||||
/* $FlowFixMe(>=0.54.0 site=react_native_oss) This comment suppresses an error
|
||||
* found when Flow v0.54 was deployed. To see the error delete this comment and
|
||||
* run Flow. */
|
||||
const uuid = require('uuid');
|
||||
|
||||
const { BlobModule } = require('NativeModules');
|
||||
|
||||
import type { BlobProps } from 'BlobTypes';
|
||||
|
||||
/**
|
||||
* Opaque JS representation of some binary data in native.
|
||||
*
|
||||
* The API is modeled after the W3C Blob API, with one caveat
|
||||
* regarding explicit deallocation. Refer to the `close()`
|
||||
* method for further details.
|
||||
*
|
||||
* Example usage in a React component:
|
||||
*
|
||||
* class WebSocketImage extends React.Component {
|
||||
* state = {blob: null};
|
||||
* componentDidMount() {
|
||||
* let ws = this.ws = new WebSocket(...);
|
||||
* ws.binaryType = 'blob';
|
||||
* ws.onmessage = (event) => {
|
||||
* if (this.state.blob) {
|
||||
* this.state.blob.close();
|
||||
* }
|
||||
* this.setState({blob: event.data});
|
||||
* };
|
||||
* }
|
||||
* componentUnmount() {
|
||||
* if (this.state.blob) {
|
||||
* this.state.blob.close();
|
||||
* }
|
||||
* this.ws.close();
|
||||
* }
|
||||
* render() {
|
||||
* if (!this.state.blob) {
|
||||
* return <View />;
|
||||
* }
|
||||
* return <Image source={{uri: URL.createObjectURL(this.state.blob)}} />;
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* Reference: https://developer.mozilla.org/en-US/docs/Web/API/Blob
|
||||
*/
|
||||
class Blob {
|
||||
/**
|
||||
* Size of the data contained in the Blob object, in bytes.
|
||||
*/
|
||||
size: number;
|
||||
/*
|
||||
* String indicating the MIME type of the data contained in the Blob.
|
||||
* If the type is unknown, this string is empty.
|
||||
*/
|
||||
type: string;
|
||||
|
||||
/*
|
||||
* Unique id to identify the blob on native side (non-standard)
|
||||
*/
|
||||
blobId: string;
|
||||
/*
|
||||
* Offset to indicate part of blob, used when sliced (non-standard)
|
||||
*/
|
||||
offset: number;
|
||||
|
||||
/**
|
||||
* Construct blob instance from blob data from native.
|
||||
* Used internally by modules like XHR, WebSocket, etc.
|
||||
*/
|
||||
static create(props: BlobProps): Blob {
|
||||
return Object.assign(Object.create(Blob.prototype), props);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for JS consumers.
|
||||
* Currently we only support creating Blobs from other Blobs.
|
||||
* Reference: https://developer.mozilla.org/en-US/docs/Web/API/Blob/Blob
|
||||
*/
|
||||
constructor(parts: Array<Blob>, options: any) {
|
||||
const blobId = uuid();
|
||||
let size = 0;
|
||||
parts.forEach((part) => {
|
||||
invariant(part instanceof Blob, 'Can currently only create a Blob from other Blobs');
|
||||
size += part.size;
|
||||
});
|
||||
BlobModule.createFromParts(parts, blobId);
|
||||
return Blob.create({
|
||||
blobId,
|
||||
offset: 0,
|
||||
size,
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* This method is used to create a new Blob object containing
|
||||
* the data in the specified range of bytes of the source Blob.
|
||||
* Reference: https://developer.mozilla.org/en-US/docs/Web/API/Blob/slice
|
||||
*/
|
||||
slice(start?: number, end?: number): Blob {
|
||||
let offset = this.offset;
|
||||
let size = this.size;
|
||||
if (typeof start === 'number') {
|
||||
if (start > size) {
|
||||
start = size;
|
||||
}
|
||||
offset += start;
|
||||
size -= start;
|
||||
|
||||
if (typeof end === 'number') {
|
||||
if (end < 0) {
|
||||
end = this.size + end;
|
||||
}
|
||||
size = end - start;
|
||||
}
|
||||
}
|
||||
return Blob.create({
|
||||
blobId: this.blobId,
|
||||
offset,
|
||||
size,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is in the standard, but not actually implemented by
|
||||
* any browsers at this point. It's important for how Blobs work in
|
||||
* React Native, however, since we cannot de-allocate resources automatically,
|
||||
* so consumers need to explicitly de-allocate them.
|
||||
*
|
||||
* Note that the semantics around Blobs created via `blob.slice()`
|
||||
* and `new Blob([blob])` are different. `blob.slice()` creates a
|
||||
* new *view* onto the same binary data, so calling `close()` on any
|
||||
* of those views is enough to deallocate the data, whereas
|
||||
* `new Blob([blob, ...])` actually copies the data in memory.
|
||||
*/
|
||||
close() {
|
||||
BlobModule.release(this.blobId);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Blob;
|
||||
@@ -1,25 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule BlobTypes
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
export type BlobProps = {
|
||||
blobId: string,
|
||||
offset: number,
|
||||
size: number,
|
||||
type?: string,
|
||||
};
|
||||
|
||||
export type FileProps = BlobProps & {
|
||||
name: string,
|
||||
lastModified: number,
|
||||
};
|
||||
@@ -1,347 +0,0 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 46;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
AD0871131E215B28007D136D /* RCTBlobManager.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = AD9A43C11DFC7126008DC588 /* RCTBlobManager.h */; };
|
||||
AD0871161E215EC9007D136D /* RCTBlobManager.h in Headers */ = {isa = PBXBuildFile; fileRef = AD9A43C11DFC7126008DC588 /* RCTBlobManager.h */; };
|
||||
AD0871181E215ED1007D136D /* RCTBlobManager.h in Headers */ = {isa = PBXBuildFile; fileRef = AD9A43C11DFC7126008DC588 /* RCTBlobManager.h */; };
|
||||
AD08711A1E2162C8007D136D /* RCTBlobManager.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = AD9A43C11DFC7126008DC588 /* RCTBlobManager.h */; };
|
||||
AD9A43C31DFC7126008DC588 /* RCTBlobManager.m in Sources */ = {isa = PBXBuildFile; fileRef = AD9A43C21DFC7126008DC588 /* RCTBlobManager.m */; };
|
||||
ADD01A711E09404A00F6D226 /* RCTBlobManager.m in Sources */ = {isa = PBXBuildFile; fileRef = AD9A43C21DFC7126008DC588 /* RCTBlobManager.m */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
358F4ED51D1E81A9004DF814 /* Copy Headers */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = include/RCTBlob;
|
||||
dstSubfolderSpec = 16;
|
||||
files = (
|
||||
AD08711A1E2162C8007D136D /* RCTBlobManager.h in Copy Headers */,
|
||||
);
|
||||
name = "Copy Headers";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
AD0871121E215B16007D136D /* Copy Headers */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = include/RCTBlob;
|
||||
dstSubfolderSpec = 16;
|
||||
files = (
|
||||
AD0871131E215B28007D136D /* RCTBlobManager.h in Copy Headers */,
|
||||
);
|
||||
name = "Copy Headers";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
358F4ED71D1E81A9004DF814 /* libRCTBlob.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTBlob.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
AD9A43C11DFC7126008DC588 /* RCTBlobManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTBlobManager.h; sourceTree = "<group>"; };
|
||||
AD9A43C21DFC7126008DC588 /* RCTBlobManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTBlobManager.m; sourceTree = "<group>"; };
|
||||
ADD01A681E09402E00F6D226 /* libRCTBlob-tvOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libRCTBlob-tvOS.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
358F4ECE1D1E81A9004DF814 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
AD9A43C11DFC7126008DC588 /* RCTBlobManager.h */,
|
||||
AD9A43C21DFC7126008DC588 /* RCTBlobManager.m */,
|
||||
358F4ED81D1E81A9004DF814 /* Products */,
|
||||
);
|
||||
indentWidth = 2;
|
||||
sourceTree = "<group>";
|
||||
tabWidth = 2;
|
||||
usesTabs = 0;
|
||||
};
|
||||
358F4ED81D1E81A9004DF814 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
358F4ED71D1E81A9004DF814 /* libRCTBlob.a */,
|
||||
ADD01A681E09402E00F6D226 /* libRCTBlob-tvOS.a */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXHeadersBuildPhase section */
|
||||
AD0871151E215EB7007D136D /* Headers */ = {
|
||||
isa = PBXHeadersBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
AD0871161E215EC9007D136D /* RCTBlobManager.h in Headers */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
AD0871171E215ECC007D136D /* Headers */ = {
|
||||
isa = PBXHeadersBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
AD0871181E215ED1007D136D /* RCTBlobManager.h in Headers */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXHeadersBuildPhase section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
358F4ED61D1E81A9004DF814 /* RCTBlob */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 358F4EE01D1E81A9004DF814 /* Build configuration list for PBXNativeTarget "RCTBlob" */;
|
||||
buildPhases = (
|
||||
AD0871151E215EB7007D136D /* Headers */,
|
||||
358F4ED51D1E81A9004DF814 /* Copy Headers */,
|
||||
358F4ED31D1E81A9004DF814 /* Sources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = RCTBlob;
|
||||
productName = SLKBlobs;
|
||||
productReference = 358F4ED71D1E81A9004DF814 /* libRCTBlob.a */;
|
||||
productType = "com.apple.product-type.library.static";
|
||||
};
|
||||
ADD01A671E09402E00F6D226 /* RCTBlob-tvOS */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = ADD01A6E1E09402E00F6D226 /* Build configuration list for PBXNativeTarget "RCTBlob-tvOS" */;
|
||||
buildPhases = (
|
||||
AD0871171E215ECC007D136D /* Headers */,
|
||||
AD0871121E215B16007D136D /* Copy Headers */,
|
||||
ADD01A641E09402E00F6D226 /* Sources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = "RCTBlob-tvOS";
|
||||
productName = "RCTBlob-tvOS";
|
||||
productReference = ADD01A681E09402E00F6D226 /* libRCTBlob-tvOS.a */;
|
||||
productType = "com.apple.product-type.library.static";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
358F4ECF1D1E81A9004DF814 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastUpgradeCheck = 0730;
|
||||
ORGANIZATIONNAME = "Silk Labs";
|
||||
TargetAttributes = {
|
||||
358F4ED61D1E81A9004DF814 = {
|
||||
CreatedOnToolsVersion = 7.3;
|
||||
};
|
||||
ADD01A671E09402E00F6D226 = {
|
||||
CreatedOnToolsVersion = 8.2;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 358F4ED21D1E81A9004DF814 /* Build configuration list for PBXProject "RCTBlob" */;
|
||||
compatibilityVersion = "Xcode 3.2";
|
||||
developmentRegion = English;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
);
|
||||
mainGroup = 358F4ECE1D1E81A9004DF814;
|
||||
productRefGroup = 358F4ED81D1E81A9004DF814 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
358F4ED61D1E81A9004DF814 /* RCTBlob */,
|
||||
ADD01A671E09402E00F6D226 /* RCTBlob-tvOS */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
358F4ED31D1E81A9004DF814 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
AD9A43C31DFC7126008DC588 /* RCTBlobManager.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
ADD01A641E09402E00F6D226 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
ADD01A711E09404A00F6D226 /* RCTBlobManager.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
358F4EDE1D1E81A9004DF814 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
SKIP_INSTALL = YES;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
358F4EDF1D1E81A9004DF814 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SKIP_INSTALL = YES;
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
358F4EE11D1E81A9004DF814 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
358F4EE21D1E81A9004DF814 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
ADD01A6F1E09402E00F6D226 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = appletvos;
|
||||
SKIP_INSTALL = YES;
|
||||
TVOS_DEPLOYMENT_TARGET = 10.1;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
ADD01A701E09402E00F6D226 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = appletvos;
|
||||
SKIP_INSTALL = YES;
|
||||
TVOS_DEPLOYMENT_TARGET = 10.1;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
358F4ED21D1E81A9004DF814 /* Build configuration list for PBXProject "RCTBlob" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
358F4EDE1D1E81A9004DF814 /* Debug */,
|
||||
358F4EDF1D1E81A9004DF814 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
358F4EE01D1E81A9004DF814 /* Build configuration list for PBXNativeTarget "RCTBlob" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
358F4EE11D1E81A9004DF814 /* Debug */,
|
||||
358F4EE21D1E81A9004DF814 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
ADD01A6E1E09402E00F6D226 /* Build configuration list for PBXNativeTarget "RCTBlob-tvOS" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
ADD01A6F1E09402E00F6D226 /* Debug */,
|
||||
ADD01A701E09402E00F6D226 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 358F4ECF1D1E81A9004DF814 /* Project object */;
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import <React/RCTBridge.h>
|
||||
#import <React/RCTBridgeModule.h>
|
||||
#import <React/RCTURLRequestHandler.h>
|
||||
|
||||
@interface RCTBlobManager : NSObject <RCTBridgeModule, RCTURLRequestHandler>
|
||||
|
||||
@end
|
||||
@@ -1,218 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import "RCTBlobManager.h"
|
||||
|
||||
#import <React/RCTConvert.h>
|
||||
#import <React/RCTWebSocketModule.h>
|
||||
|
||||
static NSString *const kBlobUriScheme = @"blob";
|
||||
|
||||
@interface _RCTBlobContentHandler : NSObject <RCTWebSocketContentHandler>
|
||||
|
||||
- (instancetype)initWithBlobManager:(RCTBlobManager *)blobManager;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation RCTBlobManager
|
||||
{
|
||||
NSMutableDictionary<NSString *, NSData *> *_blobs;
|
||||
_RCTBlobContentHandler *_contentHandler;
|
||||
NSOperationQueue *_queue;
|
||||
}
|
||||
|
||||
RCT_EXPORT_MODULE(BlobModule)
|
||||
|
||||
@synthesize bridge = _bridge;
|
||||
|
||||
+ (BOOL)requiresMainQueueSetup
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (NSDictionary<NSString *, id> *)constantsToExport
|
||||
{
|
||||
return @{
|
||||
@"BLOB_URI_SCHEME": kBlobUriScheme,
|
||||
@"BLOB_URI_HOST": [NSNull null],
|
||||
};
|
||||
}
|
||||
|
||||
- (dispatch_queue_t)methodQueue
|
||||
{
|
||||
return [[_bridge webSocketModule] methodQueue];
|
||||
}
|
||||
|
||||
- (NSString *)store:(NSData *)data
|
||||
{
|
||||
NSString *blobId = [NSUUID UUID].UUIDString;
|
||||
[self store:data withId:blobId];
|
||||
return blobId;
|
||||
}
|
||||
|
||||
- (void)store:(NSData *)data withId:(NSString *)blobId
|
||||
{
|
||||
if (!_blobs) {
|
||||
_blobs = [NSMutableDictionary new];
|
||||
}
|
||||
|
||||
_blobs[blobId] = data;
|
||||
}
|
||||
|
||||
- (NSData *)resolve:(NSDictionary<NSString *, id> *)blob
|
||||
{
|
||||
NSString *blobId = [RCTConvert NSString:blob[@"blobId"]];
|
||||
NSNumber *offset = [RCTConvert NSNumber:blob[@"offset"]];
|
||||
NSNumber *size = [RCTConvert NSNumber:blob[@"size"]];
|
||||
|
||||
return [self resolve:blobId
|
||||
offset:offset ? [offset integerValue] : 0
|
||||
size:size ? [size integerValue] : -1];
|
||||
}
|
||||
|
||||
- (NSData *)resolve:(NSString *)blobId offset:(NSInteger)offset size:(NSInteger)size
|
||||
{
|
||||
NSData *data = _blobs[blobId];
|
||||
if (!data) {
|
||||
return nil;
|
||||
}
|
||||
if (offset != 0 || (size != -1 && size != data.length)) {
|
||||
data = [data subdataWithRange:NSMakeRange(offset, size)];
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(enableBlobSupport:(nonnull NSNumber *)socketID)
|
||||
{
|
||||
if (!_contentHandler) {
|
||||
_contentHandler = [[_RCTBlobContentHandler alloc] initWithBlobManager:self];
|
||||
}
|
||||
[[_bridge webSocketModule] setContentHandler:_contentHandler forSocketID:socketID];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(disableBlobSupport:(nonnull NSNumber *)socketID)
|
||||
{
|
||||
[[_bridge webSocketModule] setContentHandler:nil forSocketID:socketID];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(sendBlob:(NSDictionary *)blob socketID:(nonnull NSNumber *)socketID)
|
||||
{
|
||||
[[_bridge webSocketModule] sendData:[self resolve:blob] forSocketID:socketID];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(createFromParts:(NSArray<NSDictionary<NSString *, id> *> *)parts withId:(NSString *)blobId)
|
||||
{
|
||||
NSMutableData *data = [NSMutableData new];
|
||||
for (NSDictionary<NSString *, id> *part in parts) {
|
||||
NSData *partData = [self resolve:part];
|
||||
[data appendData:partData];
|
||||
}
|
||||
[self store:data withId:blobId];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(release:(NSString *)blobId)
|
||||
{
|
||||
[_blobs removeObjectForKey:blobId];
|
||||
}
|
||||
|
||||
#pragma mark - RCTURLRequestHandler methods
|
||||
|
||||
- (BOOL)canHandleRequest:(NSURLRequest *)request
|
||||
{
|
||||
return [request.URL.scheme caseInsensitiveCompare:kBlobUriScheme] == NSOrderedSame;
|
||||
}
|
||||
|
||||
- (id)sendRequest:(NSURLRequest *)request withDelegate:(id<RCTURLRequestDelegate>)delegate
|
||||
{
|
||||
// Lazy setup
|
||||
if (!_queue) {
|
||||
_queue = [NSOperationQueue new];
|
||||
_queue.maxConcurrentOperationCount = 2;
|
||||
}
|
||||
|
||||
__weak __block NSBlockOperation *weakOp;
|
||||
__block NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
|
||||
NSURLResponse *response = [[NSURLResponse alloc] initWithURL:request.URL
|
||||
MIMEType:nil
|
||||
expectedContentLength:-1
|
||||
textEncodingName:nil];
|
||||
|
||||
[delegate URLRequest:weakOp didReceiveResponse:response];
|
||||
|
||||
NSURLComponents *components = [[NSURLComponents alloc] initWithURL:request.URL resolvingAgainstBaseURL:NO];
|
||||
|
||||
NSString *blobId = components.path;
|
||||
NSInteger offset = 0;
|
||||
NSInteger size = -1;
|
||||
|
||||
if (components.queryItems) {
|
||||
for (NSURLQueryItem *queryItem in components.queryItems) {
|
||||
if ([queryItem.name isEqualToString:@"offset"]) {
|
||||
offset = [queryItem.value integerValue];
|
||||
}
|
||||
if ([queryItem.name isEqualToString:@"size"]) {
|
||||
size = [queryItem.value integerValue];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NSData *data;
|
||||
if (blobId) {
|
||||
data = [self resolve:blobId offset:offset size:size];
|
||||
}
|
||||
NSError *error;
|
||||
if (data) {
|
||||
[delegate URLRequest:weakOp didReceiveData:data];
|
||||
} else {
|
||||
error = [[NSError alloc] initWithDomain:NSURLErrorDomain code:NSURLErrorBadURL userInfo:nil];
|
||||
}
|
||||
[delegate URLRequest:weakOp didCompleteWithError:error];
|
||||
}];
|
||||
|
||||
weakOp = op;
|
||||
[_queue addOperation:op];
|
||||
return op;
|
||||
}
|
||||
|
||||
- (void)cancelRequest:(NSOperation *)op
|
||||
{
|
||||
[op cancel];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation _RCTBlobContentHandler {
|
||||
__weak RCTBlobManager *_blobManager;
|
||||
}
|
||||
|
||||
- (instancetype)initWithBlobManager:(RCTBlobManager *)blobManager
|
||||
{
|
||||
if (self = [super init]) {
|
||||
_blobManager = blobManager;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)processMessage:(id)message forSocketID:(NSNumber *)socketID withType:(NSString *__autoreleasing _Nonnull *)type
|
||||
{
|
||||
if (![message isKindOfClass:[NSData class]]) {
|
||||
*type = @"text";
|
||||
return message;
|
||||
}
|
||||
|
||||
*type = @"blob";
|
||||
return @{
|
||||
@"blobId": [_blobManager store:message],
|
||||
@"offset": @0,
|
||||
@"size": @(((NSData *)message).length),
|
||||
};
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,72 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule URL
|
||||
* @format
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const Blob = require('Blob');
|
||||
|
||||
const {BlobModule} = require('NativeModules');
|
||||
|
||||
let BLOB_URL_PREFIX = null;
|
||||
|
||||
if (BlobModule && typeof BlobModule.BLOB_URI_SCHEME === 'string') {
|
||||
BLOB_URL_PREFIX = BlobModule.BLOB_URI_SCHEME + ':';
|
||||
if (typeof BlobModule.BLOB_URI_HOST === 'string') {
|
||||
BLOB_URL_PREFIX += `//${BlobModule.BLOB_URI_HOST}/`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* To allow Blobs be accessed via `content://` URIs,
|
||||
* you need to register `BlobProvider` as a ContentProvider in your app's `AndroidManifest.xml`:
|
||||
*
|
||||
* ```xml
|
||||
* <manifest>
|
||||
* <application>
|
||||
* <provider
|
||||
* android:name="com.facebook.react.modules.blob.BlobProvider"
|
||||
* android:authorities="@string/blob_provider_authority"
|
||||
* android:exported="false"
|
||||
* />
|
||||
* </application>
|
||||
* </manifest>
|
||||
* ```
|
||||
* And then define the `blob_provider_authority` string in `res/values/strings.xml`.
|
||||
* Use a dotted name that's entirely unique to your app:
|
||||
*
|
||||
* ```xml
|
||||
* <resources>
|
||||
* <string name="blob_provider_authority">your.app.package.blobs</string>
|
||||
* </resources>
|
||||
* ```
|
||||
*/
|
||||
class URL {
|
||||
constructor() {
|
||||
throw new Error('Creating BlobURL objects is not supported yet.');
|
||||
}
|
||||
|
||||
static createObjectURL(blob: Blob) {
|
||||
if (BLOB_URL_PREFIX === null) {
|
||||
throw new Error('Cannot create URL for blob!');
|
||||
}
|
||||
return `${BLOB_URL_PREFIX}${blob.blobId}?offset=${blob.offset}&size=${
|
||||
blob.size
|
||||
}`;
|
||||
}
|
||||
|
||||
static revokeObjectURL(url: string) {
|
||||
// Do nothing.
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = URL;
|
||||
@@ -35,7 +35,6 @@ class BugReporting {
|
||||
static _extraSources: Map<string, SourceCallback> = new Map();
|
||||
static _fileSources: Map<string, SourceCallback> = new Map();
|
||||
static _subscription: ?EmitterSubscription = null;
|
||||
static _redboxSubscription: ?EmitterSubscription = null;
|
||||
|
||||
static _maybeInit() {
|
||||
if (!BugReporting._subscription) {
|
||||
@@ -43,11 +42,6 @@ class BugReporting {
|
||||
.addListener('collectBugExtraData', BugReporting.collectExtraData, null);
|
||||
defaultExtras();
|
||||
}
|
||||
|
||||
if (!BugReporting._redboxSubscription) {
|
||||
BugReporting._redboxSubscription = RCTDeviceEventEmitter
|
||||
.addListener('collectRedBoxExtraData', BugReporting.collectExtraData, null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -104,11 +98,6 @@ class BugReporting {
|
||||
BugReportingNativeModule.setExtraData &&
|
||||
BugReportingNativeModule.setExtraData(extraData, fileData);
|
||||
|
||||
const RedBoxNativeModule = require('NativeModules').RedBox;
|
||||
RedBoxNativeModule &&
|
||||
RedBoxNativeModule.setExtraData &&
|
||||
RedBoxNativeModule.setExtraData(extraData, 'From BugReporting.js');
|
||||
|
||||
return { extras: extraData, files: fileData };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,15 +35,6 @@ const ASSET_TYPE_OPTIONS = {
|
||||
Photos: 'Photos',
|
||||
};
|
||||
|
||||
type GetPhotosParams = {
|
||||
first: number,
|
||||
after?: string,
|
||||
groupTypes?: $Keys<typeof GROUP_TYPES_OPTIONS>,
|
||||
groupName?: string,
|
||||
assetType?: $Keys<typeof ASSET_TYPE_OPTIONS>,
|
||||
mimeTypes?: Array<string>,
|
||||
};
|
||||
|
||||
/**
|
||||
* Shape of the param arg for the `getPhotos` function.
|
||||
*/
|
||||
@@ -72,8 +63,8 @@ const getPhotosParamChecker = createStrictShapeTypeChecker({
|
||||
groupName: PropTypes.string,
|
||||
|
||||
/**
|
||||
* Specifies filter on asset type
|
||||
*/
|
||||
* Specifies filter on asset type
|
||||
*/
|
||||
assetType: PropTypes.oneOf(Object.keys(ASSET_TYPE_OPTIONS)),
|
||||
|
||||
/**
|
||||
@@ -82,35 +73,6 @@ const getPhotosParamChecker = createStrictShapeTypeChecker({
|
||||
mimeTypes: PropTypes.arrayOf(PropTypes.string),
|
||||
});
|
||||
|
||||
type GetPhotosReturn = Promise<{
|
||||
edges: Array<{
|
||||
node: {
|
||||
type: string,
|
||||
group_name: string,
|
||||
image: {
|
||||
uri: string,
|
||||
height: number,
|
||||
width: number,
|
||||
isStored?: boolean,
|
||||
playableDuration: number,
|
||||
},
|
||||
timestamp: number,
|
||||
location?: {
|
||||
latitude?: number,
|
||||
longitude?: number,
|
||||
altitude?: number,
|
||||
heading?: number,
|
||||
speed?: number,
|
||||
},
|
||||
},
|
||||
}>,
|
||||
page_info: {
|
||||
has_next_page: boolean,
|
||||
start_cursor?: string,
|
||||
end_cursor?: string,
|
||||
},
|
||||
}>;
|
||||
|
||||
/**
|
||||
* Shape of the return value of the `getPhotos` function.
|
||||
*/
|
||||
@@ -126,7 +88,6 @@ const getPhotosReturnChecker = createStrictShapeTypeChecker({
|
||||
height: PropTypes.number.isRequired,
|
||||
width: PropTypes.number.isRequired,
|
||||
isStored: PropTypes.bool,
|
||||
playableDuration: PropTypes.number.isRequired,
|
||||
}).isRequired,
|
||||
timestamp: PropTypes.number.isRequired,
|
||||
location: createStrictShapeTypeChecker({
|
||||
@@ -164,17 +125,13 @@ class CameraRoll {
|
||||
/**
|
||||
* `CameraRoll.saveImageWithTag()` is deprecated. Use `CameraRoll.saveToCameraRoll()` instead.
|
||||
*/
|
||||
static saveImageWithTag(tag: string): Promise<string> {
|
||||
static saveImageWithTag(tag: string): Promise<Object> {
|
||||
console.warn(
|
||||
'`CameraRoll.saveImageWithTag()` is deprecated. Use `CameraRoll.saveToCameraRoll()` instead.',
|
||||
);
|
||||
return this.saveToCameraRoll(tag, 'photo');
|
||||
}
|
||||
|
||||
static deletePhotos(photos: Array<string>) {
|
||||
return RCTCameraRollManager.deletePhotos(photos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the photo or video to the camera roll / gallery.
|
||||
*
|
||||
@@ -192,7 +149,7 @@ class CameraRoll {
|
||||
static saveToCameraRoll(
|
||||
tag: string,
|
||||
type?: 'photo' | 'video',
|
||||
): Promise<string> {
|
||||
): Promise<Object> {
|
||||
invariant(
|
||||
typeof tag === 'string',
|
||||
'CameraRoll.saveToCameraRoll must be a valid string.',
|
||||
@@ -200,8 +157,8 @@ class CameraRoll {
|
||||
|
||||
invariant(
|
||||
type === 'photo' || type === 'video' || type === undefined,
|
||||
`The second argument to saveToCameraRoll must be 'photo' or 'video'. You passed ${type ||
|
||||
'unknown'}`,
|
||||
// $FlowFixMe(>=0.28.0)
|
||||
`The second argument to saveToCameraRoll must be 'photo' or 'video'. You passed ${type}`,
|
||||
);
|
||||
|
||||
let mediaType = 'photo';
|
||||
@@ -257,47 +214,10 @@ class CameraRoll {
|
||||
* - `speed`: {number}
|
||||
* - `page_info` : {object} : An object with the following shape:
|
||||
* - `has_next_page`: {boolean}
|
||||
* - `start_cursor`: {string}
|
||||
* - `end_cursor`: {string}
|
||||
*
|
||||
* Loading images:
|
||||
* ```
|
||||
* _handleButtonPress = () => {
|
||||
* CameraRoll.getPhotos({
|
||||
* first: 20,
|
||||
* assetType: 'All',
|
||||
* })
|
||||
* .then(r => {
|
||||
* this.setState({ photos: r.edges });
|
||||
* })
|
||||
* .catch((err) => {
|
||||
* //Error Loading Images
|
||||
* });
|
||||
* };
|
||||
* render() {
|
||||
* return (
|
||||
* <View>
|
||||
* <Button title="Load Images" onPress={this._handleButtonPress} />
|
||||
* <ScrollView>
|
||||
* {this.state.photos.map((p, i) => {
|
||||
* return (
|
||||
* <Image
|
||||
* key={i}
|
||||
* style={{
|
||||
* width: 300,
|
||||
* height: 100,
|
||||
* }}
|
||||
* source={{ uri: p.node.image.uri }}
|
||||
* />
|
||||
* );
|
||||
* })}
|
||||
* </ScrollView>
|
||||
* </View>
|
||||
* );
|
||||
* }
|
||||
* ```
|
||||
* - `start_cursor`: {boolean}
|
||||
* - `end_cursor`: {boolean}
|
||||
*/
|
||||
static getPhotos(params: GetPhotosParams): GetPhotosReturn {
|
||||
static getPhotos(params) {
|
||||
if (__DEV__) {
|
||||
checkPropTypes(
|
||||
{params: getPhotosParamChecker},
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
#import "RCTAssetsLibraryRequestHandler.h"
|
||||
|
||||
#import <stdatomic.h>
|
||||
#import <libkern/OSAtomic.h>
|
||||
|
||||
#import <AssetsLibrary/AssetsLibrary.h>
|
||||
#import <MobileCoreServices/MobileCoreServices.h>
|
||||
@@ -41,13 +41,13 @@ RCT_EXPORT_MODULE()
|
||||
- (id)sendRequest:(NSURLRequest *)request
|
||||
withDelegate:(id<RCTURLRequestDelegate>)delegate
|
||||
{
|
||||
__block atomic_bool cancelled = ATOMIC_VAR_INIT(NO);
|
||||
__block volatile uint32_t cancelled = 0;
|
||||
void (^cancellationBlock)(void) = ^{
|
||||
atomic_store(&cancelled, YES);
|
||||
OSAtomicOr32Barrier(1, &cancelled);
|
||||
};
|
||||
|
||||
[[self assetsLibrary] assetForURL:request.URL resultBlock:^(ALAsset *asset) {
|
||||
if (atomic_load(&cancelled)) {
|
||||
if (cancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ RCT_EXPORT_MODULE()
|
||||
[delegate URLRequest:cancellationBlock didCompleteWithError:error];
|
||||
}
|
||||
} failureBlock:^(NSError *loadError) {
|
||||
if (atomic_load(&cancelled)) {
|
||||
if (cancelled) {
|
||||
return;
|
||||
}
|
||||
[delegate URLRequest:cancellationBlock didCompleteWithError:loadError];
|
||||
|
||||
@@ -64,7 +64,6 @@
|
||||
indentWidth = 2;
|
||||
sourceTree = "<group>";
|
||||
tabWidth = 2;
|
||||
usesTabs = 0;
|
||||
};
|
||||
58B5115E1A9E6B3D00147676 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
#import <CoreLocation/CoreLocation.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <Photos/Photos.h>
|
||||
|
||||
#import <React/RCTBridge.h>
|
||||
#import <React/RCTConvert.h>
|
||||
@@ -81,8 +80,8 @@ RCT_EXPORT_MODULE()
|
||||
|
||||
@synthesize bridge = _bridge;
|
||||
|
||||
static NSString *const kErrorUnableToLoad = @"E_UNABLE_TO_LOAD";
|
||||
static NSString *const kErrorUnableToSave = @"E_UNABLE_TO_SAVE";
|
||||
NSString *const RCTErrorUnableToLoad = @"E_UNABLE_TO_LOAD";
|
||||
NSString *const RCTErrorUnableToSave = @"E_UNABLE_TO_SAVE";
|
||||
|
||||
RCT_EXPORT_METHOD(saveToCameraRoll:(NSURLRequest *)request
|
||||
type:(NSString *)type
|
||||
@@ -94,7 +93,7 @@ RCT_EXPORT_METHOD(saveToCameraRoll:(NSURLRequest *)request
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self->_bridge.assetsLibrary writeVideoAtPathToSavedPhotosAlbum:request.URL completionBlock:^(NSURL *assetURL, NSError *saveError) {
|
||||
if (saveError) {
|
||||
reject(kErrorUnableToSave, nil, saveError);
|
||||
reject(RCTErrorUnableToSave, nil, saveError);
|
||||
} else {
|
||||
resolve(assetURL.absoluteString);
|
||||
}
|
||||
@@ -104,7 +103,7 @@ RCT_EXPORT_METHOD(saveToCameraRoll:(NSURLRequest *)request
|
||||
[_bridge.imageLoader loadImageWithURLRequest:request
|
||||
callback:^(NSError *loadError, UIImage *loadedImage) {
|
||||
if (loadError) {
|
||||
reject(kErrorUnableToLoad, nil, loadError);
|
||||
reject(RCTErrorUnableToLoad, nil, loadError);
|
||||
return;
|
||||
}
|
||||
// It's unclear if writeImageToSavedPhotosAlbum is thread-safe
|
||||
@@ -112,7 +111,7 @@ RCT_EXPORT_METHOD(saveToCameraRoll:(NSURLRequest *)request
|
||||
[self->_bridge.assetsLibrary writeImageToSavedPhotosAlbum:loadedImage.CGImage metadata:nil completionBlock:^(NSURL *assetURL, NSError *saveError) {
|
||||
if (saveError) {
|
||||
RCTLogWarn(@"Error saving cropped image: %@", saveError);
|
||||
reject(kErrorUnableToSave, nil, saveError);
|
||||
reject(RCTErrorUnableToSave, nil, saveError);
|
||||
} else {
|
||||
resolve(assetURL.absoluteString);
|
||||
}
|
||||
@@ -188,11 +187,6 @@ RCT_EXPORT_METHOD(getPhotos:(NSDictionary *)params
|
||||
CLLocation *loc = [result valueForProperty:ALAssetPropertyLocation];
|
||||
NSDate *date = [result valueForProperty:ALAssetPropertyDate];
|
||||
NSString *filename = [result defaultRepresentation].filename;
|
||||
int64_t duration = 0;
|
||||
if ([[result valueForProperty:ALAssetPropertyType] isEqualToString:ALAssetTypeVideo]) {
|
||||
duration = [[result valueForProperty:ALAssetPropertyDuration] intValue];
|
||||
}
|
||||
|
||||
[assets addObject:@{
|
||||
@"node": @{
|
||||
@"type": [result valueForProperty:ALAssetPropertyType],
|
||||
@@ -203,7 +197,6 @@ RCT_EXPORT_METHOD(getPhotos:(NSDictionary *)params
|
||||
@"height": @(dimensions.height),
|
||||
@"width": @(dimensions.width),
|
||||
@"isStored": @YES,
|
||||
@"playableDuration": @(duration),
|
||||
},
|
||||
@"timestamp": @(date.timeIntervalSince1970),
|
||||
@"location": loc ? @{
|
||||
@@ -231,31 +224,10 @@ RCT_EXPORT_METHOD(getPhotos:(NSDictionary *)params
|
||||
if (error.code != ALAssetsLibraryAccessUserDeniedError) {
|
||||
RCTLogError(@"Failure while iterating through asset groups %@", error);
|
||||
}
|
||||
reject(kErrorUnableToLoad, nil, error);
|
||||
reject(RCTErrorUnableToLoad, nil, error);
|
||||
}];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(deletePhotos:(NSArray<NSString *>*)assets
|
||||
resolve:(RCTPromiseResolveBlock)resolve
|
||||
reject:(RCTPromiseRejectBlock)reject)
|
||||
{
|
||||
NSArray<NSURL *> *assets_ = [RCTConvert NSURLArray:assets];
|
||||
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
|
||||
PHFetchResult<PHAsset *> *fetched =
|
||||
[PHAsset fetchAssetsWithALAssetURLs:assets_ options:nil];
|
||||
[PHAssetChangeRequest deleteAssets:fetched];
|
||||
}
|
||||
completionHandler:^(BOOL success, NSError *error) {
|
||||
if (success == YES) {
|
||||
resolve(@(success));
|
||||
}
|
||||
else {
|
||||
reject(@"Couldn't delete", @"Couldn't delete assets", error);
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
static void checkPhotoLibraryConfig()
|
||||
{
|
||||
#if RCT_DEV
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user