mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Compare commits
287
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
56a2d6d720 | ||
|
|
0ba6ed897a | ||
|
|
7e4cd53383 | ||
|
|
69c277051e | ||
|
|
863a49f2ab | ||
|
|
5c00de7b6b | ||
|
|
17e0066c0d | ||
|
|
ec542f44dd | ||
|
|
d9bd9d5587 | ||
|
|
387e1c4d11 | ||
|
|
9a3cad4d89 | ||
|
|
48f29a74c5 | ||
|
|
8e03ced500 | ||
|
|
ac55ffd777 | ||
|
|
8bdd98ea48 | ||
|
|
2d57335fa7 | ||
|
|
2f8446319a | ||
|
|
815a07c77b | ||
|
|
53c1a4cc7b | ||
|
|
67e67ec83c | ||
|
|
0f96ebd93b | ||
|
|
f4fde9d84a | ||
|
|
4194bb242d | ||
|
|
675f14257a | ||
|
|
21dd3dd296 | ||
|
|
03d7b2aa0e | ||
|
|
2c5fbd79a2 | ||
|
|
49e35bd939 | ||
|
|
829f675b8b | ||
|
|
b58d848d9c | ||
|
|
294d95a236 | ||
|
|
9d214967d2 | ||
|
|
bba19e846e | ||
|
|
51b6749c07 | ||
|
|
5e80d95e03 | ||
|
|
e233646d09 | ||
|
|
1a790f8703 | ||
|
|
01a58d182a | ||
|
|
b973fe45bd | ||
|
|
488b6825c5 | ||
|
|
e7010348d8 | ||
|
|
27b6348f86 | ||
|
|
fdef3784f0 | ||
|
|
400a29e151 | ||
|
|
1a1a956831 | ||
|
|
854c2330eb | ||
|
|
fa0ac92b2c | ||
|
|
2d35bde101 | ||
|
|
4f883bd0bc | ||
|
|
2ef9b7f2da | ||
|
|
40b17926bb | ||
|
|
4f078d3fe2 | ||
|
|
a8c95d2417 | ||
|
|
6f007e8957 | ||
|
|
fa334ce464 | ||
|
|
a40bfa730e | ||
|
|
79a63d040f | ||
|
|
a5e135aed6 | ||
|
|
2f02dd4c8c | ||
|
|
47fe52380a | ||
|
|
528bbacf6b | ||
|
|
af0c863570 | ||
|
|
d37cdd97ae | ||
|
|
7aba456b04 | ||
|
|
f75e21f1ca | ||
|
|
09b43e479e | ||
|
|
9e6f3b8aff | ||
|
|
64d530ba07 | ||
|
|
46f4d3e1bc | ||
|
|
0c49c1f332 | ||
|
|
06d8f96a64 | ||
|
|
d3db764f38 | ||
|
|
b1e5c01483 | ||
|
|
94dac23583 | ||
|
|
8a882fe6d6 | ||
|
|
6c38972327 | ||
|
|
a1c479fb3b | ||
|
|
f88c9d6382 | ||
|
|
1019bda930 | ||
|
|
6e44356c9b | ||
|
|
644123aa6f | ||
|
|
1ea3065feb | ||
|
|
fa11faecb6 | ||
|
|
613afbab7f | ||
|
|
62d01006a1 | ||
|
|
1475fc4856 | ||
|
|
9daa17458a | ||
|
|
9658d9f82b | ||
|
|
114c258045 | ||
|
|
ceb1d1ca5b | ||
|
|
c19bc79688 | ||
|
|
9dfa2e7f3c | ||
|
|
0f9fc4b295 | ||
|
|
ce50f25d22 | ||
|
|
816d417189 | ||
|
|
108f9664bf | ||
|
|
d4517ddb9f | ||
|
|
ca91f0e3ac | ||
|
|
fda861a889 | ||
|
|
34b7ec82b5 | ||
|
|
8235a49a33 | ||
|
|
9e436d1439 | ||
|
|
ef596dec49 | ||
|
|
ebc98840e9 | ||
|
|
9f7cedbe14 | ||
|
|
752427b7b8 | ||
|
|
d99ba70c49 | ||
|
|
6c4ef287ad | ||
|
|
d7fa81f181 | ||
|
|
7492860ffb | ||
|
|
13829751b1 | ||
|
|
e485cde187 | ||
|
|
74963eb945 | ||
|
|
9227ba73ab | ||
|
|
28be33ac34 | ||
|
|
88980f2ef7 | ||
|
|
9f57dedc17 | ||
|
|
365a4d4b43 | ||
|
|
22a2553405 | ||
|
|
d74ff6fadd | ||
|
|
a2f3ba864e | ||
|
|
a70fdac5bd | ||
|
|
b1cdb7d553 | ||
|
|
3fbf7856d9 | ||
|
|
e756251413 | ||
|
|
00099093dc | ||
|
|
74f386633d | ||
|
|
a63fd378a4 | ||
|
|
5fba82deff | ||
|
|
da8bec9f8b | ||
|
|
a9c684a0ff | ||
|
|
22efd95be1 | ||
|
|
ef4214a49a | ||
|
|
619a8c9f29 | ||
|
|
964320447d | ||
|
|
c7ed03a95c | ||
|
|
3f969cb1db | ||
|
|
8ffc16c6e7 | ||
|
|
28d60b68ad | ||
|
|
31288161e1 | ||
|
|
d676746f14 | ||
|
|
afd988f85a | ||
|
|
ae2d5b1e68 | ||
|
|
ecaca80d42 | ||
|
|
429fcc8cab | ||
|
|
6224ef5301 | ||
|
|
e3ff3cf6cb | ||
|
|
ed5872e2cc | ||
|
|
2fef1bafc8 | ||
|
|
be7037fd8e | ||
|
|
29f8354c19 | ||
|
|
8aa568e867 | ||
|
|
be56a3efee | ||
|
|
3fc33bb54f | ||
|
|
7d1ec7a3dc | ||
|
|
2a3c37f424 | ||
|
|
7b1915e74d | ||
|
|
40a8434bde | ||
|
|
46fd864348 | ||
|
|
e9b83e608e | ||
|
|
869866cc5c | ||
|
|
df6c48cf36 | ||
|
|
b9d058a05c | ||
|
|
298f3bb69a | ||
|
|
2716f53220 | ||
|
|
cd263a2cc7 | ||
|
|
42c410ac84 | ||
|
|
0c18ec5b9c | ||
|
|
70d23e82ad | ||
|
|
4f6c157250 | ||
|
|
be8e7c6e65 | ||
|
|
6fa039dab0 | ||
|
|
46cc4907e3 | ||
|
|
f5975a97ad | ||
|
|
b8c86b8dec | ||
|
|
17bd6c8e84 | ||
|
|
f50af7f8a4 | ||
|
|
9a19867798 | ||
|
|
b8e79a7e8b | ||
|
|
0d4ff1b7ea | ||
|
|
bcfbdf4fbe | ||
|
|
5e11b8870a | ||
|
|
ff2658c3de | ||
|
|
f49f7932d5 | ||
|
|
faa9519021 | ||
|
|
8bd00a2361 | ||
|
|
bb6fceac27 | ||
|
|
c7846c4bfb | ||
|
|
6e7fb01c02 | ||
|
|
4f2cc42a2d | ||
|
|
cec2e80fc2 | ||
|
|
d7a9ca2893 | ||
|
|
431670f908 | ||
|
|
3c2bb3e90a | ||
|
|
4d3519cc6a | ||
|
|
e8893a021f | ||
|
|
0de470ec19 | ||
|
|
8c6b816caa | ||
|
|
1aac962378 | ||
|
|
877f1cde2e | ||
|
|
493f3e8da5 | ||
|
|
65184ec6b0 | ||
|
|
7e7d00aebe | ||
|
|
63ce56f8f5 | ||
|
|
f5efc460ad | ||
|
|
71ec85f24c | ||
|
|
52ffa5d13e | ||
|
|
b5e19adc02 | ||
|
|
d40db3a715 | ||
|
|
df2f33537a | ||
|
|
7891805d22 | ||
|
|
5649aed6d3 | ||
|
|
24e521c063 | ||
|
|
709ede799c | ||
|
|
9e34cbda9d | ||
|
|
6882132421 | ||
|
|
193a2bd4cd | ||
|
|
d0f7d4d107 | ||
|
|
b3a306a667 | ||
|
|
ee5c91c031 | ||
|
|
fc6dd78935 | ||
|
|
1f5bb374cd | ||
|
|
2b80cdf1bb | ||
|
|
6bb8617f3a | ||
|
|
ce3146a6f3 | ||
|
|
831a1bb4b1 | ||
|
|
2fe65b032e | ||
|
|
bd7bf94af9 | ||
|
|
7f94bff89a | ||
|
|
82088580ab | ||
|
|
210ae5b95a | ||
|
|
2b27f1aa19 | ||
|
|
fcf2c7cf61 | ||
|
|
ac1c8c265e | ||
|
|
52648326e6 | ||
|
|
95320626e1 | ||
|
|
5dbb3c586c | ||
|
|
a5af841d25 | ||
|
|
bf9cabb03c | ||
|
|
e758cb7f39 | ||
|
|
83ed9d170b | ||
|
|
c355a34de1 | ||
|
|
d2c569795c | ||
|
|
cae7179c94 | ||
|
|
b815eb59be | ||
|
|
5c17db8352 | ||
|
|
e8aa60430c | ||
|
|
164f6b6afd | ||
|
|
9024f56bda | ||
|
|
2fe7483c36 | ||
|
|
c8e98bbaf5 | ||
|
|
e16d67340e | ||
|
|
7abffc3f8c | ||
|
|
2f46712074 | ||
|
|
605a6e4031 | ||
|
|
bbbc18c4ee | ||
|
|
af6450c660 | ||
|
|
2a22d998f8 | ||
|
|
2ae24361c5 | ||
|
|
2afe7d4765 | ||
|
|
d5e3f081c6 | ||
|
|
06ebaf2205 | ||
|
|
7d9e902d72 | ||
|
|
a580a44b0d | ||
|
|
f363dfe766 | ||
|
|
e3c27f585a | ||
|
|
8f358a2088 | ||
|
|
702b7e877e | ||
|
|
4b4959a21c | ||
|
|
89c14579d0 | ||
|
|
f0f4bee429 | ||
|
|
b709ff4f13 | ||
|
|
040197a485 | ||
|
|
98a74b0a2b | ||
|
|
00f1a37b2c | ||
|
|
a9032a20ad | ||
|
|
1c3f6acfe5 | ||
|
|
7e9242bd1a | ||
|
|
a8f429d3a8 | ||
|
|
c249e52096 | ||
|
|
f40e398cff | ||
|
|
85bd98ecac | ||
|
|
2c74f93a62 | ||
|
|
c2b0f34533 | ||
|
|
0205207e02 | ||
|
|
22735f6903 | ||
|
|
b41d41fd40 |
@@ -0,0 +1,5 @@
|
||||
# Circle CI
|
||||
|
||||
This directory is home to the Circle CI configuration file. Circle is our continuous integration service provider. You can see the overall status of React Native's builds at https://circleci.com/gh/facebook/react-native
|
||||
|
||||
You may also see an individual PR's build status by scrolling down to the Checks section in the PR.
|
||||
+333
-217
@@ -1,48 +1,44 @@
|
||||
aliases:
|
||||
- &restore-node-cache
|
||||
# Cache Management
|
||||
- &restore-yarn-cache
|
||||
keys:
|
||||
- v1-dependencies-{{ arch }}-{{ checksum "package.json" }}
|
||||
- v1-yarn-{{ arch }}-{{ checksum "package.json" }}
|
||||
# Fallback in case checksum fails
|
||||
- v1-dependencies-{{ arch }}-
|
||||
|
||||
- &save-node-cache
|
||||
- v1-yarn-{{ arch }}-
|
||||
- &save-yarn-cache
|
||||
paths:
|
||||
- node_modules
|
||||
key: v1-dependencies-{{ arch }}-{{ checksum "package.json" }}
|
||||
- ~/.cache/yarn
|
||||
key: v1-yarn-{{ arch }}-{{ checksum "package.json" }}
|
||||
|
||||
- &restore-cache-analysis
|
||||
keys:
|
||||
- v1-analysis-dependencies-{{ arch }}-{{ checksum "package.json" }}{{ checksum "danger/package.json" }}
|
||||
- v1-analysis-dependencies-{{ arch }}-{{ checksum "package.json" }}{{ checksum "bots/package.json" }}
|
||||
# Fallback in case checksum fails
|
||||
- v1-analysis-dependencies-{{ arch }}-
|
||||
- &save-cache-analysis
|
||||
paths:
|
||||
- danger/node_modules
|
||||
- bots/node_modules
|
||||
- node_modules
|
||||
key: v1-analysis-dependencies-{{ arch }}-{{ checksum "package.json" }}{{ checksum "danger/package.json" }}
|
||||
key: v1-analysis-dependencies-{{ arch }}-{{ checksum "package.json" }}{{ checksum "bots/package.json" }}
|
||||
|
||||
- &restore-cache-android-packages
|
||||
keys:
|
||||
- v2-android-sdkmanager-packages-{{ arch }}-{{ checksum "scripts/circle-ci-android-setup.sh" }}
|
||||
- v1-android-sdkmanager-packages-{{ arch }}-api-26-alpha-{{ checksum "scripts/circle-ci-android-setup.sh" }}
|
||||
# Fallback in case checksum fails
|
||||
- v2-android-sdkmanager-packages-{{ arch }}-
|
||||
- v1-android-sdkmanager-packages-{{ arch }}-api-26-alpha-
|
||||
- &save-cache-android-packages
|
||||
paths:
|
||||
- /opt/android/sdk
|
||||
key: v2-android-sdkmanager-packages-{{ arch }}-{{ checksum "scripts/circle-ci-android-setup.sh" }}
|
||||
key: v1-android-sdkmanager-packages-{{ arch }}-api-26-alpha-{{ 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
|
||||
|
||||
- v2-android-ndk-{{ arch }}-r10e-32-64
|
||||
- &save-cache-ndk
|
||||
paths:
|
||||
- /opt/ndk
|
||||
key: v1-android-ndk-{{ arch }}-r10e-32-64
|
||||
key: v2-android-ndk-{{ arch }}-r10e-32-64
|
||||
|
||||
- &restore-cache-buck
|
||||
keys:
|
||||
@@ -60,36 +56,7 @@ aliases:
|
||||
- ~/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.11.16.01 --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
|
||||
|
||||
|
||||
# Branch Filtering
|
||||
- &filter-only-master-stable
|
||||
branches:
|
||||
only:
|
||||
@@ -112,8 +79,41 @@ aliases:
|
||||
- /.*-stable/
|
||||
- gh-pages
|
||||
|
||||
- &create-ndk-directory
|
||||
# Dependency Management
|
||||
- &install-ndk
|
||||
|
|
||||
source scripts/circle-ci-android-setup.sh && getAndroidNDK
|
||||
|
||||
- &yarn
|
||||
|
|
||||
yarn install --non-interactive --cache-folder ~/.cache/yarn
|
||||
|
||||
- &install-yarn
|
||||
|
|
||||
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
|
||||
echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
|
||||
sudo apt-get update && sudo apt-get install yarn
|
||||
|
||||
- &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.11.16.01 --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
|
||||
|
||||
- &create-ndk-directory
|
||||
name: Create Android NDK Directory
|
||||
command: |
|
||||
if [[ ! -e /opt/ndk ]]; then
|
||||
sudo mkdir /opt/ndk
|
||||
fi
|
||||
@@ -122,7 +122,8 @@ aliases:
|
||||
# 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
|
||||
|
|
||||
name: Configure Environment Variables
|
||||
command: |
|
||||
echo 'export PATH=${ANDROID_NDK}:~/react-native/gradle-2.9/bin:~/buck/bin:$PATH' >> $BASH_ENV
|
||||
source $BASH_ENV
|
||||
|
||||
@@ -130,29 +131,49 @@ aliases:
|
||||
|
|
||||
source scripts/circle-ci-android-setup.sh && getAndroidSDK
|
||||
|
||||
- &install-build-dependencies
|
||||
|
|
||||
- &install-android-build-dependencies
|
||||
name: Install Android Build Dependencies
|
||||
command: |
|
||||
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
|
||||
|
||||
# Test Definitions
|
||||
- &run-js-tests
|
||||
name: JavaScript Test Suite
|
||||
command: yarn test-ci
|
||||
|
||||
- &run-lint-checks
|
||||
name: Lint code
|
||||
command: yarn lint --format junit -o ~/react-native/reports/junit/js-lint-results.xml
|
||||
when: always
|
||||
|
||||
- &run-flow-checks
|
||||
name: Check for errors in code using Flow
|
||||
command: yarn flow check
|
||||
when: always
|
||||
|
||||
- &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
|
||||
|
||||
- &create-avd
|
||||
name: Create Android Virtual Device
|
||||
command: source scripts/circle-ci-android-setup.sh && createAVD
|
||||
|
||||
- &launch-avd
|
||||
name: Launch Android Virtual Device in Background
|
||||
command: source scripts/circle-ci-android-setup.sh && launchAVD
|
||||
background: true
|
||||
|
||||
- &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
|
||||
- &build-js-bundle
|
||||
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
|
||||
|
||||
- &compile-native-libs
|
||||
name: Compile Native Libs for Unit and Integration Tests
|
||||
@@ -160,24 +181,54 @@ aliases:
|
||||
no_output_timeout: 6m
|
||||
|
||||
- &run-android-unit-tests
|
||||
name: Unit Tests
|
||||
name: Run 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
|
||||
- &run-android-instrumentation-tests
|
||||
name: Run Instrumentation Tests
|
||||
command: |
|
||||
if [[ ! -e ReactAndroid/src/androidTest/assets/AndroidTestBundle.js ]]; then
|
||||
echo "JavaScript bundle missing, cannot run instrumentation tests. Verify build-js-bundle step completed successfully."; exit 1;
|
||||
fi
|
||||
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/ \;
|
||||
find . -type f -regex ".*/build/test-results/debug/.*xml" -exec cp {} ~/react-native/reports/junit/ \;
|
||||
find . -type f -regex ".*/outputs/androidTest-results/connected/.*xml" -exec cp {} ~/react-native/reports/junit/ \;
|
||||
find . -type f -regex ".*/buck-out/gen/ReactAndroid/src/test/.*/.*xml" -exec cp {} ~/react-native/reports/junit/ \;
|
||||
when: always
|
||||
|
||||
- &setup-artifacts
|
||||
name: Initial Setup
|
||||
command: |
|
||||
mkdir -p ~/react-native/reports/junit/
|
||||
|
||||
- &run-objc_ios-tests
|
||||
name: Objective-C iOS Test Suite
|
||||
command: ./scripts/objc-test-ios.sh
|
||||
|
||||
- &run-objc-tvos-tests
|
||||
name: Objective-C tvOS Test Suite
|
||||
command: ./scripts/objc-test-tvos.sh
|
||||
|
||||
- &run-objc-ios-e2e-tests
|
||||
name: Objective-C iOS End-to-End Test Suite
|
||||
command: node ./scripts/run-ci-e2e-tests.js --ios --js --retries 3;
|
||||
|
||||
- &run-objc-tvos-e2e-tests
|
||||
name: Objective-C tvOS End-to-End Test Suite
|
||||
command: node ./scripts/run-ci-e2e-tests.js --tvos --js --retries 3;
|
||||
|
||||
defaults: &defaults
|
||||
working_directory: ~/react-native
|
||||
|
||||
js_defaults: &js_defaults
|
||||
<<: *defaults
|
||||
docker:
|
||||
- image: circleci/node:8
|
||||
|
||||
android_defaults: &android_defaults
|
||||
<<: *defaults
|
||||
docker:
|
||||
@@ -191,125 +242,171 @@ android_defaults: &android_defaults
|
||||
- ANDROID_NDK: '/opt/ndk/android-ndk-r10e'
|
||||
- BUILD_THREADS: 2
|
||||
|
||||
macos_defaults: &macos_defaults
|
||||
<<: *defaults
|
||||
macos:
|
||||
xcode: "9.0"
|
||||
|
||||
version: 2
|
||||
jobs:
|
||||
# Runs JavaScript lint and flow checks
|
||||
run-js-checks:
|
||||
<<: *defaults
|
||||
docker:
|
||||
- image: circleci/node:8
|
||||
|
||||
# Set up a Node environment for downstream jobs
|
||||
checkout_code:
|
||||
<<: *js_defaults
|
||||
steps:
|
||||
- checkout
|
||||
- restore-cache: *restore-node-cache
|
||||
- run: *install-node-dependencies
|
||||
- save-cache: *save-node-cache
|
||||
- run: *setup-artifacts
|
||||
|
||||
- restore-cache: *restore-yarn-cache
|
||||
- run: *yarn
|
||||
- save-cache: *save-yarn-cache
|
||||
|
||||
- persist_to_workspace:
|
||||
root: .
|
||||
paths: .
|
||||
|
||||
# Runs JavaScript lint and flow checks.
|
||||
# Currently will fail a PR if lint/flow raises issues.
|
||||
js_checks:
|
||||
<<: *js_defaults
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: ~/react-native
|
||||
|
||||
- run: *run-lint-checks
|
||||
- run: *run-flow-checks
|
||||
|
||||
- store_test_results:
|
||||
path: ~/react-native/reports/junit
|
||||
- store_artifacts:
|
||||
path: ~/react-native/reports/junit
|
||||
- store_artifacts:
|
||||
path: ~/react-native/yarn.lock
|
||||
|
||||
# Runs JavaScript tests on Node 8
|
||||
test-js-node-8:
|
||||
<<: *defaults
|
||||
docker:
|
||||
- image: circleci/node:8
|
||||
test_javascript:
|
||||
<<: *js_defaults
|
||||
steps:
|
||||
- checkout
|
||||
- restore-cache: *restore-node-cache
|
||||
- run: *install-node-dependencies
|
||||
- save-cache: *save-node-cache
|
||||
- run: *run-node-tests
|
||||
- attach_workspace:
|
||||
at: ~/react-native
|
||||
|
||||
- run: *run-js-tests
|
||||
|
||||
- store_test_results:
|
||||
path: ~/react-native/reports/junit
|
||||
- store_artifacts:
|
||||
path: ~/react-native/reports/junit
|
||||
|
||||
# Runs JavaScript tests on Node 6
|
||||
test-js-node-6:
|
||||
test_javascript_node6_compatibility:
|
||||
<<: *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
|
||||
- run: *setup-artifacts
|
||||
|
||||
- restore-cache: *restore-yarn-cache
|
||||
- run: *yarn
|
||||
- save-cache: *save-yarn-cache
|
||||
|
||||
- run: *run-js-tests
|
||||
|
||||
- store_test_results:
|
||||
path: ~/react-native/reports/junit
|
||||
- store_artifacts:
|
||||
path: ~/react-native/reports/junit
|
||||
|
||||
# Runs unit tests on iOS devices
|
||||
test-objc-ios:
|
||||
<<: *defaults
|
||||
macos:
|
||||
xcode: "9.0"
|
||||
test_objc_ios:
|
||||
<<: *macos_defaults
|
||||
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
|
||||
- attach_workspace:
|
||||
at: ~/react-native
|
||||
|
||||
- run: *run-objc_ios-tests
|
||||
|
||||
- store_test_results:
|
||||
path: ~/react-native/reports/junit
|
||||
- store_artifacts:
|
||||
path: ~/react-native/reports/junit
|
||||
|
||||
# Runs unit tests on tvOS devices
|
||||
test-objc-tvos:
|
||||
<<: *defaults
|
||||
macos:
|
||||
xcode: "9.0"
|
||||
test_objc_tvos:
|
||||
<<: *macos_defaults
|
||||
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
|
||||
- attach_workspace:
|
||||
at: ~/react-native
|
||||
|
||||
- run: *run-objc-tvos-tests
|
||||
|
||||
- store_test_results:
|
||||
path: ~/react-native/reports/junit
|
||||
- store_artifacts:
|
||||
path: ~/react-native/reports/junit
|
||||
|
||||
# Runs end to end tests
|
||||
test-objc-e2e:
|
||||
<<: *defaults
|
||||
macos:
|
||||
xcode: "9.0"
|
||||
test_objc_ios_e2e:
|
||||
<<: *macos_defaults
|
||||
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;
|
||||
- attach_workspace:
|
||||
at: ~/react-native
|
||||
|
||||
- run: *run-objc-ios-e2e-tests
|
||||
|
||||
- store_test_results:
|
||||
path: ~/react-native/reports/junit
|
||||
- store_artifacts:
|
||||
path: ~/react-native/reports/junit
|
||||
|
||||
# Checks podspec
|
||||
test-podspec:
|
||||
test_podspec:
|
||||
<<: *defaults
|
||||
macos:
|
||||
xcode: "9.0"
|
||||
steps:
|
||||
- checkout
|
||||
- restore-cache: *restore-node-cache
|
||||
- run: *install-node-dependencies
|
||||
- save-cache: *save-node-cache
|
||||
- attach_workspace:
|
||||
at: ~/react-native
|
||||
|
||||
- run: ./scripts/process-podspecs.sh
|
||||
|
||||
# Publishes new version onto npm
|
||||
deploy:
|
||||
publish_npm_package:
|
||||
<<: *android_defaults
|
||||
steps:
|
||||
# Checkout code so that we can work with `git` in publish.js
|
||||
- checkout
|
||||
|
||||
- attach_workspace:
|
||||
at: ~/react-native
|
||||
|
||||
# Configure Android dependencies
|
||||
# Configure Android SDK and related dependencies
|
||||
- run: *configure-android-path
|
||||
- run: *install-build-dependencies
|
||||
- run: *install-android-build-dependencies
|
||||
- restore-cache: *restore-cache-android-packages
|
||||
- run: *install-android-packages
|
||||
- save-cache: *save-cache-android-packages
|
||||
|
||||
# Install Android NDK
|
||||
- run: *create-ndk-directory
|
||||
- restore-cache: *restore-cache-ndk
|
||||
- run: *install-ndk
|
||||
- save-cache: *save-cache-ndk
|
||||
|
||||
# Fetch dependencies using BUCK
|
||||
- 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
|
||||
@@ -317,6 +414,12 @@ jobs:
|
||||
- run: buck fetch ReactAndroid/src/androidTest/...
|
||||
- run: ./gradlew :ReactAndroid:downloadBoost :ReactAndroid:downloadDoubleConversion :ReactAndroid:downloadFolly :ReactAndroid:downloadGlog :ReactAndroid:downloadJSCHeaders
|
||||
|
||||
- run: *install-node
|
||||
- run: *install-yarn
|
||||
- restore-cache: *restore-yarn-cache
|
||||
- run: *yarn
|
||||
- save-cache: *save-yarn-cache
|
||||
|
||||
- run:
|
||||
name: Publish React Native Package
|
||||
command: |
|
||||
@@ -330,64 +433,37 @@ jobs:
|
||||
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:
|
||||
# Set up an Android environment for downstream jobs
|
||||
test_android:
|
||||
<<: *android_defaults
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
# Configure Android dependencies
|
||||
- attach_workspace:
|
||||
at: ~/react-native
|
||||
|
||||
# Configure Android SDK and related dependencies
|
||||
- run: *configure-android-path
|
||||
- run: *install-build-dependencies
|
||||
- run: *install-android-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
|
||||
|
||||
- run: *create-avd
|
||||
- run: *launch-avd
|
||||
|
||||
# Keep configuring Android dependencies while AVD boots up
|
||||
|
||||
# Install Android NDK
|
||||
- run: *create-ndk-directory
|
||||
- restore-cache: *restore-cache-ndk
|
||||
- run: *install-ndk
|
||||
- save-cache: *save-cache-ndk
|
||||
|
||||
# Fetch dependencies using BUCK
|
||||
- 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
|
||||
@@ -395,53 +471,57 @@ jobs:
|
||||
- run: buck fetch ReactAndroid/src/androidTest/...
|
||||
- run: ./gradlew :ReactAndroid:downloadBoost :ReactAndroid:downloadDoubleConversion :ReactAndroid:downloadFolly :ReactAndroid:downloadGlog :ReactAndroid:downloadJSCHeaders
|
||||
|
||||
# Build and compile
|
||||
- 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
|
||||
# Build JavaScript Bundle for instrumentation tests
|
||||
- run: *install-node
|
||||
- run: *build-js-bundle
|
||||
|
||||
# Wait for AVD to finish booting before running tests
|
||||
- run: *wait-for-avd
|
||||
|
||||
# Tests
|
||||
# Test Suite
|
||||
- run: *run-android-unit-tests
|
||||
- run: *run-android-integration-tests
|
||||
- run: *run-android-instrumentation-tests
|
||||
|
||||
# post (always runs)
|
||||
- run: *collect-android-test-results
|
||||
- store_test_results:
|
||||
path: ~/junit
|
||||
path: ~/react-native/reports/junit
|
||||
- store_artifacts:
|
||||
path: ~/junit
|
||||
path: ~/react-native/reports/junit
|
||||
|
||||
analyze-pull-request:
|
||||
<<: *defaults
|
||||
docker:
|
||||
- image: circleci/node:8
|
||||
|
||||
# Analyze pull request and raise any lint/flow issues.
|
||||
# Issues will be posted to the PR itself via GitHub bots.
|
||||
# This workflow should only fail if the bots fail to run.
|
||||
analyze_pr:
|
||||
<<: *js_defaults
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: ~/react-native
|
||||
|
||||
- restore-cache: *restore-cache-analysis
|
||||
- run: *install-node-dependencies
|
||||
- run: *yarn
|
||||
- run:
|
||||
name: Install Dependencies
|
||||
name: Install Additional 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
|
||||
if [ -n "$CIRCLE_PR_NUMBER" ]; then
|
||||
yarn add github@0.2.4
|
||||
cd bots
|
||||
yarn install --non-interactive --cache-folder ~/.cache/yarn
|
||||
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
|
||||
if [ -n "$CIRCLE_PR_NUMBER" ]; then
|
||||
cd bots && DANGER_GITHUB_API_TOKEN="e622517d9f1136ea8900""07c6373666312cdfaa69" yarn danger
|
||||
else
|
||||
echo "Skipping pull request analysis."
|
||||
fi
|
||||
@@ -449,52 +529,88 @@ jobs:
|
||||
- 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
|
||||
if [ -n "$CIRCLE_PR_NUMBER" ]; then
|
||||
cat <(echo eslint; yarn --silent lint --format=json; echo flow; yarn --silent flow 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:
|
||||
test:
|
||||
jobs:
|
||||
|
||||
# Run lint and flow checks
|
||||
- run-js-checks:
|
||||
# Checkout repo and run Yarn
|
||||
- checkout_code:
|
||||
filters: *filter-ignore-gh-pages
|
||||
|
||||
# Test JavaScript on Node 8 and 6
|
||||
- test-js-node-8:
|
||||
# Run lint and flow checks
|
||||
- js_checks:
|
||||
filters: *filter-ignore-gh-pages
|
||||
- test-js-node-6:
|
||||
requires:
|
||||
- checkout_code
|
||||
|
||||
# Test JavaScript
|
||||
- test_javascript:
|
||||
filters: *filter-ignore-gh-pages
|
||||
requires:
|
||||
- checkout_code
|
||||
|
||||
# Test JavaScript using Node 6, the minimum supported version
|
||||
- test_javascript_node6_compatibility:
|
||||
filters: *filter-ignore-gh-pages
|
||||
|
||||
# Test Android
|
||||
- build-js-bundle:
|
||||
- test_android:
|
||||
filters: *filter-ignore-gh-pages
|
||||
- test-android:
|
||||
requires:
|
||||
- build-js-bundle
|
||||
- checkout_code
|
||||
|
||||
# Test iOS & tvOS
|
||||
- test-objc-ios:
|
||||
- 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:
|
||||
- checkout_code
|
||||
- test_objc_tvos:
|
||||
filters: *filter-ignore-gh-pages
|
||||
requires:
|
||||
- checkout_code
|
||||
|
||||
# End-to-end tests
|
||||
- test_objc_ios_e2e:
|
||||
filters: *filter-ignore-gh-pages
|
||||
requires:
|
||||
- checkout_code
|
||||
|
||||
# Only runs on PRs
|
||||
analyze:
|
||||
jobs:
|
||||
# Checkout repo and run Yarn
|
||||
- checkout_code:
|
||||
filters: *filter-ignore-master-stable
|
||||
|
||||
# Run code checks
|
||||
- analyze_pr:
|
||||
filters: *filter-ignore-master-stable
|
||||
requires:
|
||||
- checkout_code
|
||||
|
||||
# Only runs on NN-stable branches
|
||||
deploy:
|
||||
jobs:
|
||||
# Checkout repo and run Yarn
|
||||
- checkout_code:
|
||||
filters: *filter-only-stable
|
||||
|
||||
# If we are on a stable branch, wait for approval to deploy to npm
|
||||
- approve_publish_npm_package:
|
||||
filters: *filter-only-stable
|
||||
type: approval
|
||||
|
||||
- publish_npm_package:
|
||||
requires:
|
||||
- checkout_code
|
||||
- approve_publish_npm_package
|
||||
|
||||
@@ -3,10 +3,6 @@
|
||||
|
||||
"parser": "babel-eslint",
|
||||
|
||||
"ecmaFeatures": {
|
||||
"jsx": true
|
||||
},
|
||||
|
||||
"env": {
|
||||
"es6": true,
|
||||
"jest": true,
|
||||
|
||||
+2
-2
@@ -6,7 +6,7 @@
|
||||
.*/local-cli/templates/.*
|
||||
|
||||
; Ignore the Dangerfile
|
||||
<PROJECT_ROOT>/danger/dangerfile.js
|
||||
<PROJECT_ROOT>/bots/dangerfile.js
|
||||
|
||||
; Ignore "BUCK" generated dirs
|
||||
<PROJECT_ROOT>/\.buckd/
|
||||
@@ -52,4 +52,4 @@ suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError
|
||||
|
||||
[version]
|
||||
^0.63.0
|
||||
^0.65.0
|
||||
|
||||
+8
-1
@@ -36,6 +36,9 @@ buck-out
|
||||
/ReactAndroid/src/main/jni/prebuilt/lib/x86/
|
||||
/ReactAndroid/src/main/gen
|
||||
|
||||
# Watchman
|
||||
.watchmanconfig
|
||||
|
||||
# Android
|
||||
.idea
|
||||
.gradle
|
||||
@@ -47,7 +50,11 @@ local.properties
|
||||
node_modules
|
||||
*.log
|
||||
.nvm
|
||||
/danger/node_modules/
|
||||
/bots/node_modules/
|
||||
|
||||
# TODO: Check in yarn.lock in open source. Right now we need to keep it out
|
||||
# from the GitHub repo as importing it might conflict with internal workspaces
|
||||
*/**/yarn.lock
|
||||
|
||||
# OS X
|
||||
.DS_Store
|
||||
|
||||
+2
-2
@@ -14,7 +14,7 @@ There are many ways to contribute to React Native, and many of them do not invol
|
||||
* 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.
|
||||
* 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 the "EDIT" button in the top-right corner 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.
|
||||
|
||||
@@ -78,7 +78,7 @@ Working on your first Pull Request? You can learn how from this free video serie
|
||||
|
||||
[**How to Contribute to an Open Source Project on GitHub**](https://egghead.io/courses/how-to-contribute-to-an-open-source-project-on-github)
|
||||
|
||||
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.
|
||||
We have a list of [beginner friendly issues](https://github.com/facebook/react-native/labels/Good%20first%20issue) 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.
|
||||
|
||||
### Proposing a change
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ var ReactContentSizeUpdateTest = createReactClass({
|
||||
mixins: [Subscribable.Mixin,
|
||||
TimerMixin],
|
||||
|
||||
componentWillMount: function() {
|
||||
UNSAFE_componentWillMount: function() {
|
||||
this.addListenerOn(
|
||||
RCTNativeAppEventEmitter,
|
||||
'rootViewDidChangeIntrinsicSize',
|
||||
|
||||
@@ -27,7 +27,7 @@ var SizeFlexibilityUpdateTest = createReactClass({
|
||||
displayName: 'SizeFlexibilityUpdateTest',
|
||||
mixins: [Subscribable.Mixin],
|
||||
|
||||
componentWillMount: function() {
|
||||
UNSAFE_componentWillMount: function() {
|
||||
this.addListenerOn(
|
||||
RCTNativeAppEventEmitter,
|
||||
'rootViewDidChangeIntrinsicSize',
|
||||
|
||||
@@ -94,11 +94,11 @@ describe('Animated tests', () => {
|
||||
opacity: anim,
|
||||
},
|
||||
};
|
||||
c.componentWillMount();
|
||||
c.UNSAFE_componentWillMount();
|
||||
|
||||
expect(anim.__detach).not.toBeCalled();
|
||||
c._component = {};
|
||||
c.componentWillReceiveProps({
|
||||
c.UNSAFE_componentWillReceiveProps({
|
||||
style: {
|
||||
opacity: anim,
|
||||
},
|
||||
@@ -120,7 +120,7 @@ describe('Animated tests', () => {
|
||||
opacity: anim,
|
||||
},
|
||||
};
|
||||
c.componentWillMount();
|
||||
c.UNSAFE_componentWillMount();
|
||||
|
||||
Animated.timing(anim, {toValue: 10, duration: 1000}).start(callback);
|
||||
c._component = {};
|
||||
|
||||
@@ -29,7 +29,7 @@ const NativeAnimatedHelper = require('NativeAnimatedHelper');
|
||||
function createAndMountComponent(ComponentClass, props) {
|
||||
const component = new ComponentClass();
|
||||
component.props = props;
|
||||
component.componentWillMount();
|
||||
component.UNSAFE_componentWillMount();
|
||||
// Simulate that refs were set.
|
||||
component._component = {};
|
||||
component.componentDidMount();
|
||||
|
||||
@@ -121,7 +121,7 @@ describe('bezier', function() {
|
||||
assertClose(easing(0.5), 0.5, 2);
|
||||
});
|
||||
});
|
||||
it('should be symetrical', function() {
|
||||
it('should be symmetrical', function() {
|
||||
repeat(10)(function() {
|
||||
var a = Math.random(),
|
||||
b = 2 * Math.random() - 0.5,
|
||||
|
||||
@@ -42,7 +42,7 @@ function createAnimatedComponent(Component: any): any {
|
||||
this._component.setNativeProps(props);
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
UNSAFE_componentWillMount() {
|
||||
this._attachProps(this.props);
|
||||
}
|
||||
|
||||
@@ -127,7 +127,7 @@ function createAnimatedComponent(Component: any): any {
|
||||
oldPropsAnimated && oldPropsAnimated.__detach();
|
||||
}
|
||||
|
||||
componentWillReceiveProps(newProps) {
|
||||
UNSAFE_componentWillReceiveProps(newProps) {
|
||||
this._attachProps(newProps);
|
||||
}
|
||||
|
||||
@@ -149,7 +149,7 @@ function createAnimatedComponent(Component: any): any {
|
||||
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
|
||||
// go through the NativeViewHierarchyManager since it operates on the shadow
|
||||
// thread.
|
||||
collapsable={
|
||||
this._propsAnimated.__isNative ? false : props.collapsable
|
||||
|
||||
@@ -374,7 +374,7 @@ class AnimatedInterpolation extends AnimatedWithChildren {
|
||||
|
||||
return {
|
||||
inputRange: this._config.inputRange,
|
||||
// Only the `outputRange` can contain strings so we don't need to tranform `inputRange` here
|
||||
// Only the `outputRange` can contain strings so we don't need to transform `inputRange` here
|
||||
outputRange: this.__transformDataType(this._config.outputRange),
|
||||
extrapolateLeft:
|
||||
this._config.extrapolateLeft || this._config.extrapolate || 'extend',
|
||||
|
||||
@@ -33,7 +33,7 @@ class AnimatedValueXY extends AnimatedWithChildren {
|
||||
_listeners: {[key: string]: {x: string, y: string}};
|
||||
|
||||
constructor(
|
||||
valueIn?: ?{x: number | AnimatedValue, y: number | AnimatedValue},
|
||||
valueIn?: ?{+x: number | AnimatedValue, +y: number | AnimatedValue},
|
||||
) {
|
||||
super();
|
||||
const value: any = valueIn || {x: 0, y: 0}; // @flowfixme: shouldn't need `: any`
|
||||
@@ -43,7 +43,7 @@ class AnimatedValueXY extends AnimatedWithChildren {
|
||||
} else {
|
||||
invariant(
|
||||
value.x instanceof AnimatedValue && value.y instanceof AnimatedValue,
|
||||
'AnimatedValueXY must be initalized with an object of numbers or ' +
|
||||
'AnimatedValueXY must be initialized with an object of numbers or ' +
|
||||
'AnimatedValues.',
|
||||
);
|
||||
this.x = value.x;
|
||||
|
||||
@@ -18,7 +18,7 @@ const MessageQueue = require('MessageQueue');
|
||||
// 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
|
||||
// __fbUninstallRNGlobalErrorHandler is conditionally set by the Inspector while the VM is paused for initialization
|
||||
// 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
|
||||
|
||||
|
||||
+41
-53
@@ -8,19 +8,12 @@
|
||||
*
|
||||
* @providesModule Blob
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
|
||||
'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';
|
||||
import type {BlobData, BlobOptions} from 'BlobTypes';
|
||||
|
||||
/**
|
||||
* Opaque JS representation of some binary data in native.
|
||||
@@ -60,51 +53,16 @@ import type { BlobProps } from 'BlobTypes';
|
||||
* 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);
|
||||
}
|
||||
_data: ?BlobData;
|
||||
|
||||
/**
|
||||
* 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,
|
||||
});
|
||||
constructor(parts: Array<Blob | string> = [], options?: BlobOptions) {
|
||||
const BlobManager = require('BlobManager');
|
||||
this.data = BlobManager.createFromParts(parts, options).data;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -112,9 +70,22 @@ class Blob {
|
||||
* the data in the specified range of bytes of the source Blob.
|
||||
* Reference: https://developer.mozilla.org/en-US/docs/Web/API/Blob/slice
|
||||
*/
|
||||
set data(data: ?BlobData) {
|
||||
this._data = data;
|
||||
}
|
||||
|
||||
get data(): BlobData {
|
||||
if (!this._data) {
|
||||
throw new Error('Blob has been closed and is no longer available');
|
||||
}
|
||||
|
||||
return this._data;
|
||||
}
|
||||
|
||||
slice(start?: number, end?: number): Blob {
|
||||
let offset = this.offset;
|
||||
let size = this.size;
|
||||
const BlobManager = require('BlobManager');
|
||||
let {offset, size} = this.data;
|
||||
|
||||
if (typeof start === 'number') {
|
||||
if (start > size) {
|
||||
start = size;
|
||||
@@ -129,8 +100,8 @@ class Blob {
|
||||
size = end - start;
|
||||
}
|
||||
}
|
||||
return Blob.create({
|
||||
blobId: this.blobId,
|
||||
return BlobManager.createFromOptions({
|
||||
blobId: this.data.blobId,
|
||||
offset,
|
||||
size,
|
||||
});
|
||||
@@ -149,7 +120,24 @@ class Blob {
|
||||
* `new Blob([blob, ...])` actually copies the data in memory.
|
||||
*/
|
||||
close() {
|
||||
BlobModule.release(this.blobId);
|
||||
const BlobManager = require('BlobManager');
|
||||
BlobManager.release(this.data.blobId);
|
||||
this.data = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Size of the data contained in the Blob object, in bytes.
|
||||
*/
|
||||
get size(): number {
|
||||
return this.data.size;
|
||||
}
|
||||
|
||||
/*
|
||||
* String indicating the MIME type of the data contained in the Blob.
|
||||
* If the type is unknown, this string is empty.
|
||||
*/
|
||||
get type(): string {
|
||||
return this.data.type || '';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
/**
|
||||
* 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 BlobManager
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const Blob = require('Blob');
|
||||
const BlobRegistry = require('BlobRegistry');
|
||||
const {BlobModule} = require('NativeModules');
|
||||
|
||||
import type {BlobData, BlobOptions} from 'BlobTypes';
|
||||
|
||||
/*eslint-disable no-bitwise */
|
||||
/*eslint-disable eqeqeq */
|
||||
|
||||
/**
|
||||
* Based on the rfc4122-compliant solution posted at
|
||||
* http://stackoverflow.com/questions/105034
|
||||
*/
|
||||
function uuidv4(): string {
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
|
||||
const r = (Math.random() * 16) | 0,
|
||||
v = c == 'x' ? r : (r & 0x3) | 0x8;
|
||||
return v.toString(16);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Module to manage blobs. Wrapper around the native blob module.
|
||||
*/
|
||||
class BlobManager {
|
||||
/**
|
||||
* If the native blob module is available.
|
||||
*/
|
||||
static isAvailable = !!BlobModule;
|
||||
|
||||
/**
|
||||
* Create blob from existing array of blobs.
|
||||
*/
|
||||
static createFromParts(
|
||||
parts: Array<Blob | string>,
|
||||
options?: BlobOptions,
|
||||
): Blob {
|
||||
const blobId = uuidv4();
|
||||
const items = parts.map(part => {
|
||||
if (
|
||||
part instanceof ArrayBuffer ||
|
||||
(global.ArrayBufferView && part instanceof global.ArrayBufferView)
|
||||
) {
|
||||
throw new Error(
|
||||
"Creating blobs from 'ArrayBuffer' and 'ArrayBufferView' are not supported",
|
||||
);
|
||||
}
|
||||
if (part instanceof Blob) {
|
||||
return {
|
||||
data: part.data,
|
||||
type: 'blob',
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
data: String(part),
|
||||
type: 'string',
|
||||
};
|
||||
}
|
||||
});
|
||||
const size = items.reduce((acc, curr) => {
|
||||
if (curr.type === 'string') {
|
||||
return acc + global.unescape(encodeURI(curr.data)).length;
|
||||
} else {
|
||||
return acc + curr.data.size;
|
||||
}
|
||||
}, 0);
|
||||
|
||||
BlobModule.createFromParts(items, blobId);
|
||||
|
||||
return BlobManager.createFromOptions({
|
||||
blobId,
|
||||
offset: 0,
|
||||
size,
|
||||
type: options ? options.type : '',
|
||||
lastModified: options ? options.lastModified : Date.now(),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create blob instance from blob data from native.
|
||||
* Used internally by modules like XHR, WebSocket, etc.
|
||||
*/
|
||||
static createFromOptions(options: BlobData): Blob {
|
||||
BlobRegistry.register(options.blobId);
|
||||
return Object.assign(Object.create(Blob.prototype), {data: options});
|
||||
}
|
||||
|
||||
/**
|
||||
* Deallocate resources for a blob.
|
||||
*/
|
||||
static release(blobId: string): void {
|
||||
BlobRegistry.unregister(blobId);
|
||||
if (BlobRegistry.has(blobId)) {
|
||||
return;
|
||||
}
|
||||
BlobModule.release(blobId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject the blob content handler in the networking module to support blob
|
||||
* requests and responses.
|
||||
*/
|
||||
static addNetworkingHandler(): void {
|
||||
BlobModule.addNetworkingHandler();
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate the websocket should return a blob for incoming binary
|
||||
* messages.
|
||||
*/
|
||||
static addWebSocketHandler(socketId: number): void {
|
||||
BlobModule.addWebSocketHandler(socketId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate the websocket should no longer return a blob for incoming
|
||||
* binary messages.
|
||||
*/
|
||||
static removeWebSocketHandler(socketId: number): void {
|
||||
BlobModule.removeWebSocketHandler(socketId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a blob message to a websocket.
|
||||
*/
|
||||
static sendOverSocket(blob: Blob, socketId: number): void {
|
||||
BlobModule.sendOverSocket(blob.data, socketId);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BlobManager;
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* 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 BlobRegistry
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
|
||||
const registry: {[key: string]: number} = {};
|
||||
|
||||
const register = (id: string) => {
|
||||
if (registry[id]) {
|
||||
registry[id]++;
|
||||
} else {
|
||||
registry[id] = 1;
|
||||
}
|
||||
};
|
||||
|
||||
const unregister = (id: string) => {
|
||||
if (registry[id]) {
|
||||
registry[id]--;
|
||||
if (registry[id] <= 0) {
|
||||
delete registry[id];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const has = (id: string) => {
|
||||
return registry[id] && registry[id] > 0;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
register,
|
||||
unregister,
|
||||
has,
|
||||
};
|
||||
@@ -8,18 +8,21 @@
|
||||
*
|
||||
* @providesModule BlobTypes
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
export type BlobProps = {
|
||||
export type BlobData = {
|
||||
blobId: string,
|
||||
offset: number,
|
||||
size: number,
|
||||
name?: string,
|
||||
type?: string,
|
||||
lastModified?: number,
|
||||
};
|
||||
|
||||
export type FileProps = BlobProps & {
|
||||
name: string,
|
||||
export type BlobOptions = {
|
||||
type: string,
|
||||
lastModified: number,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* 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 File
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const Blob = require('Blob');
|
||||
|
||||
const invariant = require('fbjs/lib/invariant');
|
||||
|
||||
import type {BlobOptions} from 'BlobTypes';
|
||||
|
||||
/**
|
||||
* The File interface provides information about files.
|
||||
*/
|
||||
class File extends Blob {
|
||||
/**
|
||||
* Constructor for JS consumers.
|
||||
*/
|
||||
constructor(
|
||||
parts: Array<Blob | string>,
|
||||
name: string,
|
||||
options?: BlobOptions,
|
||||
) {
|
||||
invariant(
|
||||
parts != null && name != null,
|
||||
'Failed to construct `File`: Must pass both `parts` and `name` arguments.',
|
||||
);
|
||||
|
||||
super(parts, options);
|
||||
this.data.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Name of the file.
|
||||
*/
|
||||
get name(): string {
|
||||
invariant(this.data.name != null, 'Files must have a name set.');
|
||||
return this.data.name;
|
||||
}
|
||||
|
||||
/*
|
||||
* Last modified time of the file.
|
||||
*/
|
||||
get lastModified(): number {
|
||||
return this.data.lastModified || 0;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = File;
|
||||
@@ -0,0 +1,156 @@
|
||||
/**
|
||||
* 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 FileReader
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const EventTarget = require('event-target-shim');
|
||||
const Blob = require('Blob');
|
||||
const {FileReaderModule} = require('NativeModules');
|
||||
|
||||
type ReadyState =
|
||||
| 0 // EMPTY
|
||||
| 1 // LOADING
|
||||
| 2; // DONE
|
||||
|
||||
type ReaderResult = string | ArrayBuffer;
|
||||
|
||||
const READER_EVENTS = [
|
||||
'abort',
|
||||
'error',
|
||||
'load',
|
||||
'loadstart',
|
||||
'loadend',
|
||||
'progress',
|
||||
];
|
||||
|
||||
const EMPTY = 0;
|
||||
const LOADING = 1;
|
||||
const DONE = 2;
|
||||
|
||||
class FileReader extends EventTarget(...READER_EVENTS) {
|
||||
static EMPTY = EMPTY;
|
||||
static LOADING = LOADING;
|
||||
static DONE = DONE;
|
||||
|
||||
EMPTY = EMPTY;
|
||||
LOADING = LOADING;
|
||||
DONE = DONE;
|
||||
|
||||
_readyState: ReadyState;
|
||||
_error: ?Error;
|
||||
_result: ?ReaderResult;
|
||||
_aborted: boolean = false;
|
||||
_subscriptions: Array<*> = [];
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this._reset();
|
||||
}
|
||||
|
||||
_reset(): void {
|
||||
this._readyState = EMPTY;
|
||||
this._error = null;
|
||||
this._result = null;
|
||||
}
|
||||
|
||||
_clearSubscriptions(): void {
|
||||
this._subscriptions.forEach(sub => sub.remove());
|
||||
this._subscriptions = [];
|
||||
}
|
||||
|
||||
_setReadyState(newState: ReadyState) {
|
||||
this._readyState = newState;
|
||||
this.dispatchEvent({type: 'readystatechange'});
|
||||
if (newState === DONE) {
|
||||
if (this._aborted) {
|
||||
this.dispatchEvent({type: 'abort'});
|
||||
} else if (this._error) {
|
||||
this.dispatchEvent({type: 'error'});
|
||||
} else {
|
||||
this.dispatchEvent({type: 'load'});
|
||||
}
|
||||
this.dispatchEvent({type: 'loadend'});
|
||||
}
|
||||
}
|
||||
|
||||
readAsArrayBuffer() {
|
||||
throw new Error('FileReader.readAsArrayBuffer is not implemented');
|
||||
}
|
||||
|
||||
readAsDataURL(blob: Blob) {
|
||||
this._aborted = false;
|
||||
|
||||
FileReaderModule.readAsDataURL(blob.data).then(
|
||||
(text: string) => {
|
||||
if (this._aborted) {
|
||||
return;
|
||||
}
|
||||
this._result = text;
|
||||
this._setReadyState(DONE);
|
||||
},
|
||||
error => {
|
||||
if (this._aborted) {
|
||||
return;
|
||||
}
|
||||
this._error = error;
|
||||
this._setReadyState(DONE);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
readAsText(blob: Blob, encoding: string = 'UTF-8') {
|
||||
this._aborted = false;
|
||||
|
||||
FileReaderModule.readAsText(blob.data, encoding).then(
|
||||
(text: string) => {
|
||||
if (this._aborted) {
|
||||
return;
|
||||
}
|
||||
this._result = text;
|
||||
this._setReadyState(DONE);
|
||||
},
|
||||
error => {
|
||||
if (this._aborted) {
|
||||
return;
|
||||
}
|
||||
this._error = error;
|
||||
this._setReadyState(DONE);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
abort() {
|
||||
this._aborted = true;
|
||||
// only call onreadystatechange if there is something to abort, as per spec
|
||||
if (this._readyState !== EMPTY && this._readyState !== DONE) {
|
||||
this._reset();
|
||||
this._setReadyState(DONE);
|
||||
}
|
||||
// Reset again after, in case modified in handler
|
||||
this._reset();
|
||||
}
|
||||
|
||||
get readyState(): ReadyState {
|
||||
return this._readyState;
|
||||
}
|
||||
|
||||
get error(): ?Error {
|
||||
return this._error;
|
||||
}
|
||||
|
||||
get result(): ?ReaderResult {
|
||||
return this._result;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = FileReader;
|
||||
@@ -7,12 +7,18 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
19BA88FE1F84391700741C5A /* RCTFileReaderModule.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = ADDFBA6A1F33455F0064C998 /* RCTFileReaderModule.h */; };
|
||||
19BA88FF1F84392900741C5A /* RCTFileReaderModule.h in Headers */ = {isa = PBXBuildFile; fileRef = ADDFBA6A1F33455F0064C998 /* RCTFileReaderModule.h */; };
|
||||
19BA89001F84392F00741C5A /* RCTFileReaderModule.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = ADDFBA6A1F33455F0064C998 /* RCTFileReaderModule.h */; };
|
||||
19BA89011F84393D00741C5A /* RCTFileReaderModule.m in Sources */ = {isa = PBXBuildFile; fileRef = ADDFBA6B1F33455F0064C998 /* RCTFileReaderModule.m */; };
|
||||
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 */; };
|
||||
AD9A43C31DFC7126008DC588 /* RCTBlobManager.mm in Sources */ = {isa = PBXBuildFile; fileRef = AD9A43C21DFC7126008DC588 /* RCTBlobManager.mm */; };
|
||||
ADD01A711E09404A00F6D226 /* RCTBlobManager.mm in Sources */ = {isa = PBXBuildFile; fileRef = AD9A43C21DFC7126008DC588 /* RCTBlobManager.mm */; };
|
||||
ADDFBA6C1F33455F0064C998 /* RCTFileReaderModule.h in Headers */ = {isa = PBXBuildFile; fileRef = ADDFBA6A1F33455F0064C998 /* RCTFileReaderModule.h */; };
|
||||
ADDFBA6D1F33455F0064C998 /* RCTFileReaderModule.m in Sources */ = {isa = PBXBuildFile; fileRef = ADDFBA6B1F33455F0064C998 /* RCTFileReaderModule.m */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
@@ -22,6 +28,7 @@
|
||||
dstPath = include/RCTBlob;
|
||||
dstSubfolderSpec = 16;
|
||||
files = (
|
||||
19BA88FE1F84391700741C5A /* RCTFileReaderModule.h in Copy Headers */,
|
||||
AD08711A1E2162C8007D136D /* RCTBlobManager.h in Copy Headers */,
|
||||
);
|
||||
name = "Copy Headers";
|
||||
@@ -33,6 +40,7 @@
|
||||
dstPath = include/RCTBlob;
|
||||
dstSubfolderSpec = 16;
|
||||
files = (
|
||||
19BA89001F84392F00741C5A /* RCTFileReaderModule.h in Copy Headers */,
|
||||
AD0871131E215B28007D136D /* RCTBlobManager.h in Copy Headers */,
|
||||
);
|
||||
name = "Copy Headers";
|
||||
@@ -42,17 +50,21 @@
|
||||
|
||||
/* 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>"; };
|
||||
AD9A43C11DFC7126008DC588 /* RCTBlobManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = RCTBlobManager.h; sourceTree = "<group>"; };
|
||||
AD9A43C21DFC7126008DC588 /* RCTBlobManager.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RCTBlobManager.mm; sourceTree = "<group>"; };
|
||||
ADD01A681E09402E00F6D226 /* libRCTBlob-tvOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libRCTBlob-tvOS.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
ADDFBA6A1F33455F0064C998 /* RCTFileReaderModule.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = RCTFileReaderModule.h; sourceTree = "<group>"; };
|
||||
ADDFBA6B1F33455F0064C998 /* RCTFileReaderModule.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTFileReaderModule.m; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
358F4ECE1D1E81A9004DF814 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
ADDFBA6A1F33455F0064C998 /* RCTFileReaderModule.h */,
|
||||
ADDFBA6B1F33455F0064C998 /* RCTFileReaderModule.m */,
|
||||
AD9A43C11DFC7126008DC588 /* RCTBlobManager.h */,
|
||||
AD9A43C21DFC7126008DC588 /* RCTBlobManager.m */,
|
||||
AD9A43C21DFC7126008DC588 /* RCTBlobManager.mm */,
|
||||
358F4ED81D1E81A9004DF814 /* Products */,
|
||||
);
|
||||
indentWidth = 2;
|
||||
@@ -77,6 +89,7 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
AD0871161E215EC9007D136D /* RCTBlobManager.h in Headers */,
|
||||
ADDFBA6C1F33455F0064C998 /* RCTFileReaderModule.h in Headers */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -84,6 +97,7 @@
|
||||
isa = PBXHeadersBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
19BA88FF1F84392900741C5A /* RCTFileReaderModule.h in Headers */,
|
||||
AD0871181E215ED1007D136D /* RCTBlobManager.h in Headers */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -132,7 +146,7 @@
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastUpgradeCheck = 0730;
|
||||
ORGANIZATIONNAME = "Silk Labs";
|
||||
ORGANIZATIONNAME = Facebook;
|
||||
TargetAttributes = {
|
||||
358F4ED61D1E81A9004DF814 = {
|
||||
CreatedOnToolsVersion = 7.3;
|
||||
@@ -166,7 +180,8 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
AD9A43C31DFC7126008DC588 /* RCTBlobManager.m in Sources */,
|
||||
ADDFBA6D1F33455F0064C998 /* RCTFileReaderModule.m in Sources */,
|
||||
AD9A43C31DFC7126008DC588 /* RCTBlobManager.mm in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -174,7 +189,8 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
ADD01A711E09404A00F6D226 /* RCTBlobManager.m in Sources */,
|
||||
19BA89011F84393D00741C5A /* RCTFileReaderModule.m in Sources */,
|
||||
ADD01A711E09404A00F6D226 /* RCTBlobManager.mm in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
||||
@@ -13,4 +13,18 @@
|
||||
|
||||
@interface RCTBlobManager : NSObject <RCTBridgeModule, RCTURLRequestHandler>
|
||||
|
||||
- (NSString *)store:(NSData *)data;
|
||||
|
||||
- (void)store:(NSData *)data withId:(NSString *)blobId;
|
||||
|
||||
- (NSData *)resolve:(NSDictionary<NSString *, id> *)blob;
|
||||
|
||||
- (NSData *)resolve:(NSString *)blobId offset:(NSInteger)offset size:(NSInteger)size;
|
||||
|
||||
- (NSData *)resolveURL:(NSURL *)url;
|
||||
|
||||
- (void)remove:(NSString *)blobId;
|
||||
|
||||
- (void)createFromParts:(NSArray<NSDictionary<NSString *, id> *> *)parts withId:(NSString *)blobId;
|
||||
|
||||
@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
|
||||
Executable
+294
@@ -0,0 +1,294 @@
|
||||
/**
|
||||
* 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 <mutex>
|
||||
|
||||
#import <React/RCTConvert.h>
|
||||
#import <React/RCTNetworking.h>
|
||||
#import <React/RCTUtils.h>
|
||||
#import <React/RCTWebSocketModule.h>
|
||||
|
||||
static NSString *const kBlobURIScheme = @"blob";
|
||||
|
||||
@interface RCTBlobManager () <RCTNetworkingRequestHandler, RCTNetworkingResponseHandler, RCTWebSocketContentHandler>
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTBlobManager
|
||||
{
|
||||
// Blobs should be thread safe since they are used from the websocket and networking module,
|
||||
// make sure to use proper locking when accessing this.
|
||||
NSMutableDictionary<NSString *, NSData *> *_blobs;
|
||||
std::mutex _blobsMutex;
|
||||
|
||||
NSOperationQueue *_queue;
|
||||
}
|
||||
|
||||
RCT_EXPORT_MODULE(BlobModule)
|
||||
|
||||
@synthesize bridge = _bridge;
|
||||
|
||||
- (void)setBridge:(RCTBridge *)bridge
|
||||
{
|
||||
_bridge = bridge;
|
||||
|
||||
std::lock_guard<std::mutex> lock(_blobsMutex);
|
||||
_blobs = [NSMutableDictionary new];
|
||||
}
|
||||
|
||||
+ (BOOL)requiresMainQueueSetup
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (NSDictionary<NSString *, id> *)constantsToExport
|
||||
{
|
||||
return @{
|
||||
@"BLOB_URI_SCHEME": kBlobURIScheme,
|
||||
@"BLOB_URI_HOST": [NSNull null],
|
||||
};
|
||||
}
|
||||
|
||||
- (NSString *)store:(NSData *)data
|
||||
{
|
||||
NSString *blobId = [NSUUID UUID].UUIDString;
|
||||
[self store:data withId:blobId];
|
||||
return blobId;
|
||||
}
|
||||
|
||||
- (void)store:(NSData *)data withId:(NSString *)blobId
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_blobsMutex);
|
||||
_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;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_blobsMutex);
|
||||
data = _blobs[blobId];
|
||||
}
|
||||
if (!data) {
|
||||
return nil;
|
||||
}
|
||||
if (offset != 0 || (size != -1 && size != data.length)) {
|
||||
data = [data subdataWithRange:NSMakeRange(offset, size)];
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
- (NSData *)resolveURL:(NSURL *)url
|
||||
{
|
||||
NSURLComponents *components = [[NSURLComponents alloc] initWithURL: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];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (blobId) {
|
||||
return [self resolve:blobId offset:offset size:size];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)remove:(NSString *)blobId
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_blobsMutex);
|
||||
[_blobs removeObjectForKey:blobId];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(addNetworkingHandler)
|
||||
{
|
||||
dispatch_async(_bridge.networking.methodQueue, ^{
|
||||
[self->_bridge.networking addRequestHandler:self];
|
||||
[self->_bridge.networking addResponseHandler:self];
|
||||
});
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(addWebSocketHandler:(nonnull NSNumber *)socketID)
|
||||
{
|
||||
dispatch_async(_bridge.webSocketModule.methodQueue, ^{
|
||||
[self->_bridge.webSocketModule setContentHandler:self forSocketID:socketID];
|
||||
});
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(removeWebSocketHandler:(nonnull NSNumber *)socketID)
|
||||
{
|
||||
dispatch_async(_bridge.webSocketModule.methodQueue, ^{
|
||||
[self->_bridge.webSocketModule setContentHandler:nil forSocketID:socketID];
|
||||
});
|
||||
}
|
||||
|
||||
// @lint-ignore FBOBJCUNTYPEDCOLLECTION1
|
||||
RCT_EXPORT_METHOD(sendOverSocket:(NSDictionary *)blob socketID:(nonnull NSNumber *)socketID)
|
||||
{
|
||||
dispatch_async(_bridge.webSocketModule.methodQueue, ^{
|
||||
[self->_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) {
|
||||
NSString *type = [RCTConvert NSString:part[@"type"]];
|
||||
|
||||
if ([type isEqualToString:@"blob"]) {
|
||||
NSData *partData = [self resolve:part[@"data"]];
|
||||
[data appendData:partData];
|
||||
} else if ([type isEqualToString:@"string"]) {
|
||||
NSData *partData = [[RCTConvert NSString:part[@"data"]] dataUsingEncoding:NSUTF8StringEncoding];
|
||||
[data appendData:partData];
|
||||
} else {
|
||||
[NSException raise:@"Invalid type for blob" format:@"%@ is invalid", type];
|
||||
}
|
||||
}
|
||||
[self store:data withId:blobId];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(release:(NSString *)blobId)
|
||||
{
|
||||
[self remove: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 __typeof(self) weakSelf = self;
|
||||
__weak __block NSBlockOperation *weakOp;
|
||||
__block NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
|
||||
__typeof(self) strongSelf = weakSelf;
|
||||
if (!strongSelf) {
|
||||
return;
|
||||
}
|
||||
NSURLResponse *response = [[NSURLResponse alloc] initWithURL:request.URL
|
||||
MIMEType:nil
|
||||
expectedContentLength:-1
|
||||
textEncodingName:nil];
|
||||
|
||||
[delegate URLRequest:weakOp didReceiveResponse:response];
|
||||
|
||||
NSData *data = [strongSelf resolveURL:response.URL];
|
||||
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];
|
||||
}
|
||||
|
||||
#pragma mark - RCTNetworkingRequestHandler methods
|
||||
|
||||
// @lint-ignore FBOBJCUNTYPEDCOLLECTION1
|
||||
- (BOOL)canHandleNetworkingRequest:(NSDictionary *)data
|
||||
{
|
||||
return data[@"blob"] != nil;
|
||||
}
|
||||
|
||||
// @lint-ignore FBOBJCUNTYPEDCOLLECTION1
|
||||
- (NSDictionary *)handleNetworkingRequest:(NSDictionary *)data
|
||||
{
|
||||
// @lint-ignore FBOBJCUNTYPEDCOLLECTION1
|
||||
NSDictionary *blob = [RCTConvert NSDictionary:data[@"blob"]];
|
||||
|
||||
NSString *contentType = @"application/octet-stream";
|
||||
NSString *blobType = [RCTConvert NSString:blob[@"type"]];
|
||||
if (blobType != nil && blobType.length > 0) {
|
||||
contentType = blob[@"type"];
|
||||
}
|
||||
|
||||
return @{@"body": [self resolve:blob], @"contentType": contentType};
|
||||
}
|
||||
|
||||
- (BOOL)canHandleNetworkingResponse:(NSString *)responseType
|
||||
{
|
||||
return [responseType isEqualToString:@"blob"];
|
||||
}
|
||||
|
||||
- (id)handleNetworkingResponse:(NSURLResponse *)response data:(NSData *)data
|
||||
{
|
||||
// An empty body will have nil for data, in this case we need to return
|
||||
// an empty blob as per the XMLHttpRequest spec.
|
||||
data = data ?: [NSData new];
|
||||
return @{
|
||||
@"blobId": [self store:data],
|
||||
@"offset": @0,
|
||||
@"size": @(data.length),
|
||||
@"name": RCTNullIfNil([response suggestedFilename]),
|
||||
@"type": RCTNullIfNil([response MIMEType]),
|
||||
};
|
||||
}
|
||||
|
||||
#pragma mark - RCTWebSocketContentHandler methods
|
||||
|
||||
- (id)processWebsocketMessage:(id)message
|
||||
forSocketID:(NSNumber *)socketID
|
||||
withType:(NSString *__autoreleasing _Nonnull *)type
|
||||
{
|
||||
if (![message isKindOfClass:[NSData class]]) {
|
||||
*type = @"text";
|
||||
return message;
|
||||
}
|
||||
|
||||
*type = @"blob";
|
||||
return @{
|
||||
@"blobId": [self store:message],
|
||||
@"offset": @0,
|
||||
@"size": @(((NSData *)message).length),
|
||||
};
|
||||
}
|
||||
|
||||
@end
|
||||
+2
-2
@@ -7,8 +7,8 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import <React/RCTShadowView.h>
|
||||
#import <React/RCTBridgeModule.h>
|
||||
|
||||
@interface RCTMultilineTextInputShadowView : RCTShadowView
|
||||
@interface RCTFileReaderModule : NSObject <RCTBridgeModule>
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* 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 "RCTFileReaderModule.h"
|
||||
|
||||
#import <React/RCTBridge.h>
|
||||
#import <React/RCTConvert.h>
|
||||
|
||||
#import "RCTBlobManager.h"
|
||||
|
||||
|
||||
@implementation RCTFileReaderModule
|
||||
|
||||
RCT_EXPORT_MODULE(FileReaderModule)
|
||||
|
||||
@synthesize bridge = _bridge;
|
||||
|
||||
RCT_EXPORT_METHOD(readAsText:(NSDictionary<NSString *, id> *)blob
|
||||
encoding:(NSString *)encoding
|
||||
resolve:(RCTPromiseResolveBlock)resolve
|
||||
reject:(RCTPromiseRejectBlock)reject)
|
||||
{
|
||||
RCTBlobManager *blobManager = [[self bridge] moduleForClass:[RCTBlobManager class]];
|
||||
NSData *data = [blobManager resolve:blob];
|
||||
|
||||
if (data == nil) {
|
||||
reject(RCTErrorUnspecified,
|
||||
[NSString stringWithFormat:@"Unable to resolve data for blob: %@", [RCTConvert NSString:blob[@"blobId"]]], nil);
|
||||
} else {
|
||||
NSStringEncoding stringEncoding;
|
||||
|
||||
if (encoding == nil) {
|
||||
stringEncoding = NSUTF8StringEncoding;
|
||||
} else {
|
||||
stringEncoding = CFStringConvertEncodingToNSStringEncoding(CFStringConvertIANACharSetNameToEncoding((CFStringRef) encoding));
|
||||
}
|
||||
|
||||
NSString *text = [[NSString alloc] initWithData:data encoding:stringEncoding];
|
||||
|
||||
resolve(text);
|
||||
}
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(readAsDataURL:(NSDictionary<NSString *, id> *)blob
|
||||
resolve:(RCTPromiseResolveBlock)resolve
|
||||
reject:(RCTPromiseRejectBlock)reject)
|
||||
{
|
||||
RCTBlobManager *blobManager = [[self bridge] moduleForClass:[RCTBlobManager class]];
|
||||
NSData *data = [blobManager resolve:blob];
|
||||
|
||||
if (data == nil) {
|
||||
reject(RCTErrorUnspecified,
|
||||
[NSString stringWithFormat:@"Unable to resolve data for blob: %@", [RCTConvert NSString:blob[@"blobId"]]], nil);
|
||||
} else {
|
||||
NSString *type = [RCTConvert NSString:blob[@"type"]];
|
||||
NSString *text = [NSString stringWithFormat:@"data:%@;base64,%@",
|
||||
type != nil && [type length] > 0 ? type : @"application/octet-stream",
|
||||
[data base64EncodedStringWithOptions:0]];
|
||||
|
||||
resolve(text);
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -52,16 +52,16 @@ if (BlobModule && typeof BlobModule.BLOB_URI_SCHEME === 'string') {
|
||||
*/
|
||||
class URL {
|
||||
constructor() {
|
||||
throw new Error('Creating BlobURL objects is not supported yet.');
|
||||
throw new Error('Creating URL 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
|
||||
}`;
|
||||
return `${BLOB_URL_PREFIX}${blob.data.blobId}?offset=${
|
||||
blob.data.offset
|
||||
}&size=${blob.size}`;
|
||||
}
|
||||
|
||||
static revokeObjectURL(url: string) {
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
const BlobModule = {
|
||||
createFromParts() {},
|
||||
release() {},
|
||||
};
|
||||
|
||||
module.exports = BlobModule;
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
const FileReaderModule = {
|
||||
async readAsText() {
|
||||
return '';
|
||||
},
|
||||
async readAsDataURL() {
|
||||
return 'data:text/plain;base64,NDI=';
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = FileReaderModule;
|
||||
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @format
|
||||
* @emails oncall+react_native
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
jest.setMock('NativeModules', {
|
||||
BlobModule: require('../__mocks__/BlobModule'),
|
||||
});
|
||||
|
||||
var Blob = require('Blob');
|
||||
|
||||
describe('Blob', function() {
|
||||
it('should create empty blob', () => {
|
||||
const blob = new Blob();
|
||||
expect(blob).toBeInstanceOf(Blob);
|
||||
expect(blob.data.offset).toBe(0);
|
||||
expect(blob.data.size).toBe(0);
|
||||
expect(blob.size).toBe(0);
|
||||
expect(blob.type).toBe('');
|
||||
});
|
||||
|
||||
it('should create blob from other blobs and strings', () => {
|
||||
const blobA = new Blob();
|
||||
const blobB = new Blob();
|
||||
const textA = 'i \u2665 dogs';
|
||||
const textB = '\uD800\uDC00';
|
||||
const textC =
|
||||
'Z\u0351\u036B\u0343\u036A\u0302\u036B\u033D\u034F\u0334\u0319\u0324' +
|
||||
'\u031E\u0349\u035A\u032F\u031E\u0320\u034DA\u036B\u0357\u0334\u0362' +
|
||||
'\u0335\u031C\u0330\u0354L\u0368\u0367\u0369\u0358\u0320G\u0311\u0357' +
|
||||
'\u030E\u0305\u035B\u0341\u0334\u033B\u0348\u034D\u0354\u0339O\u0342' +
|
||||
'\u030C\u030C\u0358\u0328\u0335\u0339\u033B\u031D\u0333!\u033F\u030B' +
|
||||
'\u0365\u0365\u0302\u0363\u0310\u0301\u0301\u035E\u035C\u0356\u032C' +
|
||||
'\u0330\u0319\u0317';
|
||||
|
||||
blobA.data.size = 34540;
|
||||
blobB.data.size = 65452;
|
||||
|
||||
const blob = new Blob([blobA, blobB, textA, textB, textC]);
|
||||
|
||||
expect(blob.size).toBe(
|
||||
blobA.size +
|
||||
blobB.size +
|
||||
global.Buffer.byteLength(textA, 'UTF-8') +
|
||||
global.Buffer.byteLength(textB, 'UTF-8') +
|
||||
global.Buffer.byteLength(textC, 'UTF-8'),
|
||||
);
|
||||
expect(blob.type).toBe('');
|
||||
});
|
||||
|
||||
it('should slice a blob', () => {
|
||||
const blob = new Blob();
|
||||
|
||||
blob.data.size = 34546;
|
||||
|
||||
const sliceA = blob.slice(0, 2354);
|
||||
|
||||
expect(sliceA.data.offset).toBe(0);
|
||||
expect(sliceA.size).toBe(2354);
|
||||
expect(sliceA.type).toBe('');
|
||||
|
||||
const sliceB = blob.slice(2384, 7621);
|
||||
|
||||
expect(sliceB.data.offset).toBe(2384);
|
||||
expect(sliceB.size).toBe(7621 - 2384);
|
||||
expect(sliceB.type).toBe('');
|
||||
});
|
||||
|
||||
it('should close a blob', () => {
|
||||
const blob = new Blob();
|
||||
|
||||
blob.close();
|
||||
|
||||
expect(() => blob.size).toThrow();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @format
|
||||
* @emails oncall+react_native
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
jest.setMock('NativeModules', {
|
||||
BlobModule: require('../__mocks__/BlobModule'),
|
||||
});
|
||||
|
||||
var Blob = require('Blob');
|
||||
var BlobManager = require('BlobManager');
|
||||
|
||||
describe('BlobManager', function() {
|
||||
it('should create blob from parts', () => {
|
||||
const blob = BlobManager.createFromParts([], {type: 'text/html'});
|
||||
expect(blob).toBeInstanceOf(Blob);
|
||||
expect(blob.type).toBe('text/html');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @format
|
||||
* @emails oncall+react_native
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
jest.setMock('NativeModules', {
|
||||
BlobModule: require('../__mocks__/BlobModule'),
|
||||
});
|
||||
|
||||
const File = require('File');
|
||||
|
||||
describe('File', function() {
|
||||
it('should create empty file', () => {
|
||||
const file = new File([], 'test.jpg');
|
||||
expect(file).toBeInstanceOf(File);
|
||||
expect(file.data.offset).toBe(0);
|
||||
expect(file.data.size).toBe(0);
|
||||
expect(file.size).toBe(0);
|
||||
expect(file.type).toBe('');
|
||||
expect(file.name).toBe('test.jpg');
|
||||
expect(file.lastModified).toEqual(expect.any(Number));
|
||||
});
|
||||
|
||||
it('should create empty file with type', () => {
|
||||
const file = new File([], 'test.jpg', {type: 'image/jpeg'});
|
||||
expect(file.type).toBe('image/jpeg');
|
||||
});
|
||||
|
||||
it('should create empty file with lastModified', () => {
|
||||
const file = new File([], 'test.jpg', {lastModified: 1337});
|
||||
expect(file.lastModified).toBe(1337);
|
||||
});
|
||||
|
||||
it('should throw on invalid arguments', () => {
|
||||
expect(() => new File()).toThrow();
|
||||
expect(() => new File([])).toThrow();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @format
|
||||
* @emails oncall+react_native
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
jest.unmock('event-target-shim').setMock('NativeModules', {
|
||||
BlobModule: require('../__mocks__/BlobModule'),
|
||||
FileReaderModule: require('../__mocks__/FileReaderModule'),
|
||||
});
|
||||
|
||||
var Blob = require('Blob');
|
||||
var FileReader = require('FileReader');
|
||||
|
||||
describe('FileReader', function() {
|
||||
it('should read blob as text', async () => {
|
||||
const e = await new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = resolve;
|
||||
reader.onerror = reject;
|
||||
reader.readAsText(new Blob());
|
||||
});
|
||||
expect(e.target.result).toBe('');
|
||||
});
|
||||
|
||||
it('should read blob as data URL', async () => {
|
||||
const e = await new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = resolve;
|
||||
reader.onerror = reject;
|
||||
reader.readAsDataURL(new Blob());
|
||||
});
|
||||
expect(e.target.result).toBe('data:text/plain;base64,NDI=');
|
||||
});
|
||||
});
|
||||
@@ -147,15 +147,9 @@ const getPhotosReturnChecker = createStrictShapeTypeChecker({
|
||||
});
|
||||
|
||||
/**
|
||||
* `CameraRoll` provides access to the local camera roll / gallery.
|
||||
* Before using this you must link the `RCTCameraRoll` library.
|
||||
* You can refer to [Linking](docs/linking-libraries-ios.html) for help.
|
||||
*
|
||||
* ### Permissions
|
||||
* The user's permission is required in order to access the Camera Roll on devices running iOS 10 or later.
|
||||
* Add the `NSPhotoLibraryUsageDescription` key in your `Info.plist` with a string that describes how your
|
||||
* app will use this data. This key will appear as `Privacy - Photo Library Usage Description` in Xcode.
|
||||
* `CameraRoll` provides access to the local camera roll or photo library.
|
||||
*
|
||||
* See https://facebook.github.io/react-native/docs/cameraroll.html
|
||||
*/
|
||||
class CameraRoll {
|
||||
static GroupTypesOptions: Object = GROUP_TYPES_OPTIONS;
|
||||
@@ -176,18 +170,9 @@ class CameraRoll {
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the photo or video to the camera roll / gallery.
|
||||
* Saves the photo or video to the camera roll or photo library.
|
||||
*
|
||||
* On Android, the tag must be a local image or video URI, such as `"file:///sdcard/img.png"`.
|
||||
*
|
||||
* On iOS, the tag can be any image URI (including local, remote asset-library and base64 data URIs)
|
||||
* or a local video file URI (remote or data URIs are not supported for saving video at this time).
|
||||
*
|
||||
* If the tag has a file extension of .mov or .mp4, it will be inferred as a video. Otherwise
|
||||
* it will be treated as a photo. To override the automatic choice, you can pass an optional
|
||||
* `type` parameter that must be one of 'photo' or 'video'.
|
||||
*
|
||||
* Returns a Promise which will resolve with the new URI.
|
||||
* See https://facebook.github.io/react-native/docs/cameraroll.html#savetocameraroll
|
||||
*/
|
||||
static saveToCameraRoll(
|
||||
tag: string,
|
||||
@@ -218,84 +203,7 @@ class CameraRoll {
|
||||
* Returns a Promise with photo identifier objects from the local camera
|
||||
* roll of the device matching shape defined by `getPhotosReturnChecker`.
|
||||
*
|
||||
* Expects a params object of the following shape:
|
||||
*
|
||||
* - `first` : {number} : The number of photos wanted in reverse order of the photo application (i.e. most recent first for SavedPhotos).
|
||||
* - `after` : {string} : A cursor that matches `page_info { end_cursor }` returned from a previous call to `getPhotos`.
|
||||
* - `groupTypes` : {string} : Specifies which group types to filter the results to. Valid values are:
|
||||
* - `Album`
|
||||
* - `All`
|
||||
* - `Event`
|
||||
* - `Faces`
|
||||
* - `Library`
|
||||
* - `PhotoStream`
|
||||
* - `SavedPhotos` // default
|
||||
* - `groupName` : {string} : Specifies filter on group names, like 'Recent Photos' or custom album titles.
|
||||
* - `assetType` : {string} : Specifies filter on asset type. Valid values are:
|
||||
* - `All`
|
||||
* - `Videos`
|
||||
* - `Photos` // default
|
||||
* - `mimeTypes` : {string} : Filter by mimetype (e.g. image/jpeg).
|
||||
*
|
||||
* Returns a Promise which when resolved will be of the following shape:
|
||||
*
|
||||
* - `edges` : {Array<node>} An array of node objects
|
||||
* - `node`: {object} An object with the following shape:
|
||||
* - `type`: {string}
|
||||
* - `group_name`: {string}
|
||||
* - `image`: {object} : An object with the following shape:
|
||||
* - `uri`: {string}
|
||||
* - `height`: {number}
|
||||
* - `width`: {number}
|
||||
* - `isStored`: {boolean}
|
||||
* - `timestamp`: {number}
|
||||
* - `location`: {object} : An object with the following shape:
|
||||
* - `latitude`: {number}
|
||||
* - `longitude`: {number}
|
||||
* - `altitude`: {number}
|
||||
* - `heading`: {number}
|
||||
* - `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>
|
||||
* );
|
||||
* }
|
||||
* ```
|
||||
* See https://facebook.github.io/react-native/docs/cameraroll.html#getphotos
|
||||
*/
|
||||
static getPhotos(params: GetPhotosParams): GetPhotosReturn {
|
||||
if (__DEV__) {
|
||||
|
||||
@@ -199,7 +199,7 @@ RCT_EXPORT_METHOD(getPhotos:(NSDictionary *)params
|
||||
@"group_name": [group valueForProperty:ALAssetsGroupPropertyName],
|
||||
@"image": @{
|
||||
@"uri": uri,
|
||||
@"filename" : filename,
|
||||
@"filename" : filename ?: [NSNull null],
|
||||
@"height": @(dimensions.height),
|
||||
@"width": @(dimensions.width),
|
||||
@"isStored": @YES,
|
||||
|
||||
@@ -68,10 +68,16 @@ const KeyboardAvoidingView = createReactClass({
|
||||
* may be non-zero in some use cases. The default value is 0.
|
||||
*/
|
||||
keyboardVerticalOffset: PropTypes.number.isRequired,
|
||||
/**
|
||||
* This is to allow us to manually control which KAV shuld take effect when
|
||||
* having more than one KAV at the same screen
|
||||
*/
|
||||
enabled: PropTypes.bool.isRequired,
|
||||
},
|
||||
|
||||
getDefaultProps() {
|
||||
return {
|
||||
enabled: true,
|
||||
keyboardVerticalOffset: 0,
|
||||
};
|
||||
},
|
||||
@@ -127,7 +133,7 @@ const KeyboardAvoidingView = createReactClass({
|
||||
this.frame = event.nativeEvent.layout;
|
||||
},
|
||||
|
||||
componentWillUpdate(nextProps: Object, nextState: Object, nextContext?: Object): void {
|
||||
UNSAFE_componentWillUpdate(nextProps: Object, nextState: Object, nextContext?: Object): void {
|
||||
if (nextState.bottom === this.state.bottom &&
|
||||
this.props.behavior === 'height' &&
|
||||
nextProps.behavior === 'height') {
|
||||
@@ -137,7 +143,7 @@ const KeyboardAvoidingView = createReactClass({
|
||||
}
|
||||
},
|
||||
|
||||
componentWillMount() {
|
||||
UNSAFE_componentWillMount() {
|
||||
if (Platform.OS === 'ios') {
|
||||
this.subscriptions = [
|
||||
Keyboard.addListener('keyboardWillChangeFrame', this._onKeyboardChange),
|
||||
@@ -157,7 +163,7 @@ const KeyboardAvoidingView = createReactClass({
|
||||
render(): React.Element<any> {
|
||||
// $FlowFixMe(>=0.41.0)
|
||||
const {behavior, children, style, ...props} = this.props;
|
||||
|
||||
const bottomHeight = this.props.enabled ? this.state.bottom : 0;
|
||||
switch (behavior) {
|
||||
case 'height':
|
||||
let heightStyle;
|
||||
@@ -166,7 +172,7 @@ const KeyboardAvoidingView = createReactClass({
|
||||
// i.e. this.state.bottom is greater than 0. If we remove that condition,
|
||||
// this.frame.height will never go back to its original value.
|
||||
// When height changes, we need to disable flex.
|
||||
heightStyle = {height: this.frame.height - this.state.bottom, flex: 0};
|
||||
heightStyle = {height: this.frame.height - bottomHeight, flex: 0};
|
||||
}
|
||||
return (
|
||||
<View ref={viewRef} style={[style, heightStyle]} onLayout={this._onLayout} {...props}>
|
||||
@@ -175,7 +181,7 @@ const KeyboardAvoidingView = createReactClass({
|
||||
);
|
||||
|
||||
case 'position':
|
||||
const positionStyle = {bottom: this.state.bottom};
|
||||
const positionStyle = {bottom: bottomHeight};
|
||||
const { contentContainerStyle } = this.props;
|
||||
|
||||
return (
|
||||
@@ -187,7 +193,7 @@ const KeyboardAvoidingView = createReactClass({
|
||||
);
|
||||
|
||||
case 'padding':
|
||||
const paddingStyle = {paddingBottom: this.state.bottom};
|
||||
const paddingStyle = {paddingBottom: bottomHeight};
|
||||
return (
|
||||
<View ref={viewRef} style={[style, paddingStyle]} onLayout={this._onLayout} {...props}>
|
||||
{children}
|
||||
|
||||
@@ -23,7 +23,7 @@ var LazyRenderer = createReactClass({
|
||||
render: PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
componentWillMount: function(): void {
|
||||
UNSAFE_componentWillMount: function(): void {
|
||||
this.setState({
|
||||
_lazyRender : true,
|
||||
});
|
||||
|
||||
@@ -337,7 +337,7 @@ var NavigatorIOS = createReactClass({
|
||||
/**
|
||||
* Use this to specify additional props to pass to the rendered
|
||||
* component. `NavigatorIOS` will automatically pass in `route` and
|
||||
* `navigator` props to the comoponent.
|
||||
* `navigator` props to the component.
|
||||
*/
|
||||
passProps: PropTypes.object,
|
||||
|
||||
@@ -515,7 +515,7 @@ var NavigatorIOS = createReactClass({
|
||||
|
||||
navigator: (undefined: ?Object),
|
||||
|
||||
componentWillMount: function() {
|
||||
UNSAFE_componentWillMount: function() {
|
||||
// Precompute a pack of callbacks that's frequently generated and passed to
|
||||
// instances.
|
||||
this.navigator = {
|
||||
|
||||
@@ -66,7 +66,7 @@ class PickerAndroid extends React.Component<{
|
||||
};
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
UNSAFE_componentWillReceiveProps(nextProps) {
|
||||
this.setState(this._stateFromProps(nextProps));
|
||||
}
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ var PickerIOS = createReactClass({
|
||||
return this._stateFromProps(this.props);
|
||||
},
|
||||
|
||||
componentWillReceiveProps: function(nextProps) {
|
||||
UNSAFE_componentWillReceiveProps: function(nextProps) {
|
||||
this.setState(this._stateFromProps(nextProps));
|
||||
},
|
||||
|
||||
|
||||
@@ -548,7 +548,7 @@ const ScrollResponderMixin = {
|
||||
*
|
||||
* The `keyboardWillShow` is called before input focus.
|
||||
*/
|
||||
componentWillMount: function() {
|
||||
UNSAFE_componentWillMount: function() {
|
||||
const {keyboardShouldPersistTaps} = this.props;
|
||||
warning(
|
||||
typeof keyboardShouldPersistTaps !== 'boolean',
|
||||
|
||||
@@ -37,6 +37,7 @@ const requireNativeComponent = require('requireNativeComponent');
|
||||
* found when Flow v0.54 was deployed. To see the error delete this comment and
|
||||
* run Flow. */
|
||||
const warning = require('fbjs/lib/warning');
|
||||
const resolveAssetSource = require('resolveAssetSource');
|
||||
|
||||
import type {NativeMethodsMixinType} from 'ReactNativeTypes';
|
||||
|
||||
@@ -189,6 +190,11 @@ const ScrollView = createReactClass({
|
||||
'black',
|
||||
'white',
|
||||
]),
|
||||
/**
|
||||
* If sticky headers should stick at the bottom instead of the top of the
|
||||
* ScrollView. This is usually used with inverted ScrollViews.
|
||||
*/
|
||||
invertStickyHeaders: PropTypes.bool,
|
||||
/**
|
||||
* When true, the ScrollView will try to lock to only vertical or horizontal
|
||||
* scrolling while dragging. The default value is false.
|
||||
@@ -233,6 +239,34 @@ const ScrollView = createReactClass({
|
||||
* - `true`, deprecated, use 'always' instead
|
||||
*/
|
||||
keyboardShouldPersistTaps: PropTypes.oneOf(['always', 'never', 'handled', false, true]),
|
||||
/**
|
||||
* When set, the scroll view will adjust the scroll position so that the first child that is
|
||||
* currently visible and at or beyond `minIndexForVisible` will not change position. This is
|
||||
* useful for lists that are loading content in both directions, e.g. a chat thread, where new
|
||||
* messages coming in might otherwise cause the scroll position to jump. A value of 0 is common,
|
||||
* but other values such as 1 can be used to skip loading spinners or other content that should
|
||||
* not maintain position.
|
||||
*
|
||||
* The optional `autoscrollToTopThreshold` can be used to make the content automatically scroll
|
||||
* to the top after making the adjustment if the user was within the threshold of the top before
|
||||
* the adjustment was made. This is also useful for chat-like applications where you want to see
|
||||
* new messages scroll into place, but not if the user has scrolled up a ways and it would be
|
||||
* disruptive to scroll a bunch.
|
||||
*
|
||||
* Caveat 1: Reordering elements in the scrollview with this enabled will probably cause
|
||||
* jumpiness and jank. It can be fixed, but there are currently no plans to do so. For now,
|
||||
* don't re-order the content of any ScrollViews or Lists that use this feature.
|
||||
*
|
||||
* Caveat 2: This simply uses `contentOffset` and `frame.origin` in native code to compute
|
||||
* visibility. Occlusion, transforms, and other complexity won't be taken into account as to
|
||||
* whether content is "visible" or not.
|
||||
*
|
||||
* @platform ios
|
||||
*/
|
||||
maintainVisibleContentPosition: PropTypes.shape({
|
||||
minIndexForVisible: PropTypes.number.isRequired,
|
||||
autoscrollToTopThreshold: PropTypes.number,
|
||||
}),
|
||||
/**
|
||||
* The maximum allowed zoom scale. The default value is 1.0.
|
||||
* @platform ios
|
||||
@@ -442,6 +476,25 @@ const ScrollView = createReactClass({
|
||||
* @platform ios
|
||||
*/
|
||||
DEPRECATED_sendUpdatedChildFrames: PropTypes.bool,
|
||||
/**
|
||||
* Optionally an image can be used for the scroll bar thumb. This will
|
||||
* override the color. While the image is loading or the image fails to
|
||||
* load the color will be used instead. Use an alpha of 0 in the color
|
||||
* to avoid seeing it while the image is loading.
|
||||
*
|
||||
* - `uri` - a string representing the resource identifier for the image, which
|
||||
* should be either a local file path or the name of a static image resource
|
||||
* - `number` - Opaque type returned by something like
|
||||
* `import IMAGE from './image.jpg'`.
|
||||
* @platform vr
|
||||
*/
|
||||
scrollBarThumbImage: PropTypes.oneOfType([
|
||||
PropTypes.shape({
|
||||
uri: PropTypes.string,
|
||||
}),
|
||||
// Opaque type returned by import IMAGE from './image.jpg'
|
||||
PropTypes.number,
|
||||
]),
|
||||
},
|
||||
|
||||
mixins: [ScrollResponder.Mixin],
|
||||
@@ -451,10 +504,13 @@ const ScrollView = createReactClass({
|
||||
_stickyHeaderRefs: (new Map(): Map<number, ScrollViewStickyHeader>),
|
||||
_headerLayoutYs: (new Map(): Map<string, number>),
|
||||
getInitialState: function() {
|
||||
return this.scrollResponderMixinGetInitialState();
|
||||
return {
|
||||
...this.scrollResponderMixinGetInitialState(),
|
||||
layoutHeight: null,
|
||||
};
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
UNSAFE_componentWillMount: function() {
|
||||
this._scrollAnimatedValue = new Animated.Value(this.props.contentOffset ? this.props.contentOffset.y : 0);
|
||||
this._scrollAnimatedValue.setOffset(this.props.contentInset ? this.props.contentInset.top : 0);
|
||||
this._stickyHeaderRefs = new Map();
|
||||
@@ -628,6 +684,15 @@ const ScrollView = createReactClass({
|
||||
this.scrollResponderHandleScroll(e);
|
||||
},
|
||||
|
||||
_handleLayout: function(e: Object) {
|
||||
if (this.props.invertStickyHeaders) {
|
||||
this.setState({ layoutHeight: e.nativeEvent.layout.height });
|
||||
}
|
||||
if (this.props.onLayout) {
|
||||
this.props.onLayout(e);
|
||||
}
|
||||
},
|
||||
|
||||
_handleContentOnLayout: function(e: Object) {
|
||||
const {width, height} = e.nativeEvent.layout;
|
||||
this.props.onContentSizeChange && this.props.onContentSizeChange(width, height);
|
||||
@@ -646,14 +711,7 @@ const ScrollView = createReactClass({
|
||||
render: function() {
|
||||
let ScrollViewClass;
|
||||
let ScrollContentContainerViewClass;
|
||||
if (Platform.OS === 'ios') {
|
||||
ScrollViewClass = RCTScrollView;
|
||||
ScrollContentContainerViewClass = RCTScrollContentView;
|
||||
warning(
|
||||
!this.props.snapToInterval || !this.props.pagingEnabled,
|
||||
'snapToInterval is currently ignored when pagingEnabled is true.'
|
||||
);
|
||||
} else if (Platform.OS === 'android') {
|
||||
if (Platform.OS === 'android') {
|
||||
if (this.props.horizontal) {
|
||||
ScrollViewClass = AndroidHorizontalScrollView;
|
||||
ScrollContentContainerViewClass = AndroidHorizontalScrollContentView;
|
||||
@@ -661,6 +719,13 @@ const ScrollView = createReactClass({
|
||||
ScrollViewClass = AndroidScrollView;
|
||||
ScrollContentContainerViewClass = View;
|
||||
}
|
||||
} else {
|
||||
ScrollViewClass = RCTScrollView;
|
||||
ScrollContentContainerViewClass = RCTScrollContentView;
|
||||
warning(
|
||||
!this.props.snapToInterval || !this.props.pagingEnabled,
|
||||
'snapToInterval is currently ignored when pagingEnabled is true.'
|
||||
);
|
||||
}
|
||||
|
||||
invariant(
|
||||
@@ -713,7 +778,9 @@ const ScrollView = createReactClass({
|
||||
this._headerLayoutYs.get(this._getKeyForIndex(nextIndex, childArray))
|
||||
}
|
||||
onLayout={(event) => this._onStickyHeaderLayout(index, event, key)}
|
||||
scrollAnimatedValue={this._scrollAnimatedValue}>
|
||||
scrollAnimatedValue={this._scrollAnimatedValue}
|
||||
inverted={this.props.invertStickyHeaders}
|
||||
scrollViewHeight={this.state.layoutHeight}>
|
||||
{child}
|
||||
</ScrollViewStickyHeader>
|
||||
);
|
||||
@@ -760,6 +827,7 @@ const ScrollView = createReactClass({
|
||||
// Override the onContentSizeChange from props, since this event can
|
||||
// bubble up from TextInputs
|
||||
onContentSizeChange: null,
|
||||
onLayout: this._handleLayout,
|
||||
onMomentumScrollBegin: this.scrollResponderHandleMomentumScrollBegin,
|
||||
onMomentumScrollEnd: this.scrollResponderHandleMomentumScrollEnd,
|
||||
onResponderGrant: this.scrollResponderHandleResponderGrant,
|
||||
@@ -777,6 +845,7 @@ const ScrollView = createReactClass({
|
||||
onTouchMove: this.scrollResponderHandleTouchMove,
|
||||
onTouchStart: this.scrollResponderHandleTouchStart,
|
||||
onTouchCancel: this.scrollResponderHandleTouchCancel,
|
||||
scrollBarThumbImage: resolveAssetSource(this.props.scrollBarThumbImage),
|
||||
scrollEventThrottle: hasStickyHeaders ? 1 : this.props.scrollEventThrottle,
|
||||
sendMomentumEvents: (this.props.onMomentumScrollBegin || this.props.onMomentumScrollEnd) ?
|
||||
true : false,
|
||||
@@ -882,6 +951,17 @@ if (Platform.OS === 'android') {
|
||||
nativeOnlyProps,
|
||||
);
|
||||
RCTScrollContentView = requireNativeComponent('RCTScrollContentView', View);
|
||||
} else {
|
||||
nativeOnlyProps = {
|
||||
nativeOnly: {
|
||||
}
|
||||
};
|
||||
RCTScrollView = requireNativeComponent(
|
||||
'RCTScrollView',
|
||||
null,
|
||||
nativeOnlyProps,
|
||||
);
|
||||
RCTScrollContentView = requireNativeComponent('RCTScrollContentView', View);
|
||||
}
|
||||
|
||||
module.exports = ScrollView;
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
*
|
||||
* @providesModule ScrollViewStickyHeader
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
@@ -15,34 +16,40 @@ const Animated = require('Animated');
|
||||
const React = require('React');
|
||||
const StyleSheet = require('StyleSheet');
|
||||
|
||||
import type {LayoutEvent} from 'CoreEventTypes';
|
||||
|
||||
type Props = {
|
||||
children?: React.Element<any>,
|
||||
nextHeaderLayoutY: ?number,
|
||||
onLayout: (event: Object) => void,
|
||||
onLayout: (event: LayoutEvent) => void,
|
||||
scrollAnimatedValue: Animated.Value,
|
||||
// Will cause sticky headers to stick at the bottom of the ScrollView instead
|
||||
// of the top.
|
||||
inverted: ?boolean,
|
||||
// The height of the parent ScrollView. Currently only set when inverted.
|
||||
scrollViewHeight: ?number,
|
||||
};
|
||||
|
||||
class ScrollViewStickyHeader extends React.Component<Props, {
|
||||
type State = {
|
||||
measured: boolean,
|
||||
layoutY: number,
|
||||
layoutHeight: number,
|
||||
nextHeaderLayoutY: ?number,
|
||||
}> {
|
||||
constructor(props: Props, context: Object) {
|
||||
super(props, context);
|
||||
this.state = {
|
||||
measured: false,
|
||||
layoutY: 0,
|
||||
layoutHeight: 0,
|
||||
nextHeaderLayoutY: props.nextHeaderLayoutY,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
class ScrollViewStickyHeader extends React.Component<Props, State> {
|
||||
state = {
|
||||
measured: false,
|
||||
layoutY: 0,
|
||||
layoutHeight: 0,
|
||||
nextHeaderLayoutY: this.props.nextHeaderLayoutY,
|
||||
};
|
||||
|
||||
setNextHeaderY(y: number) {
|
||||
this.setState({ nextHeaderLayoutY: y });
|
||||
this.setState({nextHeaderLayoutY: y});
|
||||
}
|
||||
|
||||
_onLayout = (event) => {
|
||||
_onLayout = event => {
|
||||
this.setState({
|
||||
measured: true,
|
||||
layoutY: event.nativeEvent.layout.y,
|
||||
@@ -57,32 +64,70 @@ class ScrollViewStickyHeader extends React.Component<Props, {
|
||||
};
|
||||
|
||||
render() {
|
||||
const {inverted, scrollViewHeight} = this.props;
|
||||
const {measured, layoutHeight, layoutY, nextHeaderLayoutY} = this.state;
|
||||
const inputRange: Array<number> = [-1, 0];
|
||||
const outputRange: Array<number> = [0, 0];
|
||||
|
||||
if (measured) {
|
||||
// The interpolation looks like:
|
||||
// - Negative scroll: no translation
|
||||
// - From 0 to the y of the header: no translation. This will cause the header
|
||||
// to scroll normally until it reaches the top of the scroll view.
|
||||
// - From header y to when the next header y hits the bottom edge of the header: translate
|
||||
// equally to scroll. This will cause the header to stay at the top of the scroll view.
|
||||
// - Past the collision with the next header y: no more translation. This will cause the
|
||||
// header to continue scrolling up and make room for the next sticky header.
|
||||
// In the case that there is no next header just translate equally to
|
||||
// scroll indefinetly.
|
||||
inputRange.push(layoutY);
|
||||
outputRange.push(0);
|
||||
// Sometimes headers jump around so we make sure we don't violate the monotonic inputRange
|
||||
// condition.
|
||||
const collisionPoint = (nextHeaderLayoutY || 0) - layoutHeight;
|
||||
if (collisionPoint >= layoutY) {
|
||||
inputRange.push(collisionPoint, collisionPoint + 1);
|
||||
outputRange.push(collisionPoint - layoutY, collisionPoint - layoutY);
|
||||
if (inverted) {
|
||||
// The interpolation looks like:
|
||||
// - Negative scroll: no translation
|
||||
// - `stickStartPoint` is the point at which the header will start sticking.
|
||||
// It is calculated using the ScrollView viewport height so it is a the bottom.
|
||||
// - Headers that are in the initial viewport will never stick, `stickStartPoint`
|
||||
// will be negative.
|
||||
// - From 0 to `stickStartPoint` no translation. This will cause the header
|
||||
// to scroll normally until it reaches the top of the scroll view.
|
||||
// - From `stickStartPoint` to when the next header y hits the bottom edge of the header: translate
|
||||
// equally to scroll. This will cause the header to stay at the top of the scroll view.
|
||||
// - Past the collision with the next header y: no more translation. This will cause the
|
||||
// header to continue scrolling up and make room for the next sticky header.
|
||||
// In the case that there is no next header just translate equally to
|
||||
// scroll indefinitely.
|
||||
if (scrollViewHeight != null) {
|
||||
const stickStartPoint = layoutY + layoutHeight - scrollViewHeight;
|
||||
if (stickStartPoint > 0) {
|
||||
inputRange.push(stickStartPoint);
|
||||
outputRange.push(0);
|
||||
inputRange.push(stickStartPoint + 1);
|
||||
outputRange.push(1);
|
||||
// If the next sticky header has not loaded yet (probably windowing) or is the last
|
||||
// we can just keep it sticked forever.
|
||||
const collisionPoint =
|
||||
(nextHeaderLayoutY || 0) - layoutHeight - scrollViewHeight;
|
||||
if (collisionPoint > stickStartPoint) {
|
||||
inputRange.push(collisionPoint, collisionPoint + 1);
|
||||
outputRange.push(
|
||||
collisionPoint - stickStartPoint,
|
||||
collisionPoint - stickStartPoint,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
inputRange.push(layoutY + 1);
|
||||
outputRange.push(1);
|
||||
// The interpolation looks like:
|
||||
// - Negative scroll: no translation
|
||||
// - From 0 to the y of the header: no translation. This will cause the header
|
||||
// to scroll normally until it reaches the top of the scroll view.
|
||||
// - From header y to when the next header y hits the bottom edge of the header: translate
|
||||
// equally to scroll. This will cause the header to stay at the top of the scroll view.
|
||||
// - Past the collision with the next header y: no more translation. This will cause the
|
||||
// header to continue scrolling up and make room for the next sticky header.
|
||||
// In the case that there is no next header just translate equally to
|
||||
// scroll indefinitely.
|
||||
inputRange.push(layoutY);
|
||||
outputRange.push(0);
|
||||
// If the next sticky header has not loaded yet (probably windowing) or is the last
|
||||
// we can just keep it sticked forever.
|
||||
const collisionPoint = (nextHeaderLayoutY || 0) - layoutHeight;
|
||||
if (collisionPoint >= layoutY) {
|
||||
inputRange.push(collisionPoint, collisionPoint + 1);
|
||||
outputRange.push(collisionPoint - layoutY, collisionPoint - layoutY);
|
||||
} else {
|
||||
inputRange.push(layoutY + 1);
|
||||
outputRange.push(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -315,7 +315,7 @@ class StatusBar extends React.Component<{
|
||||
// Every time a StatusBar component is mounted, we push it's prop to a stack
|
||||
// and always update the native status bar with the props from the top of then
|
||||
// stack. This allows having multiple StatusBar components and the one that is
|
||||
// added last or is deeper in the view hierachy will have priority.
|
||||
// added last or is deeper in the view hierarchy will have priority.
|
||||
this._stackEntry = createStackEntry(this.props);
|
||||
StatusBar._propsStack.push(this._stackEntry);
|
||||
this._updatePropsStack();
|
||||
|
||||
@@ -25,7 +25,7 @@ var Subscribable = {};
|
||||
|
||||
Subscribable.Mixin = {
|
||||
|
||||
componentWillMount: function() {
|
||||
UNSAFE_componentWillMount: function() {
|
||||
this._subscribableSubscriptions = [];
|
||||
},
|
||||
|
||||
|
||||
@@ -102,13 +102,13 @@ class TabBarItemIOS extends React.Component {
|
||||
hasBeenSelected: false,
|
||||
};
|
||||
|
||||
componentWillMount() {
|
||||
UNSAFE_componentWillMount() {
|
||||
if (this.props.selected) {
|
||||
this.setState({hasBeenSelected: true});
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps: { selected?: boolean }) {
|
||||
UNSAFE_componentWillReceiveProps(nextProps: { selected?: boolean }) {
|
||||
if (this.state.hasBeenSelected || nextProps.selected) {
|
||||
this.setState({hasBeenSelected: true});
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
*
|
||||
* @providesModule TextInput
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
@@ -30,6 +31,7 @@ const TimerMixin = require('react-timer-mixin');
|
||||
const TouchableWithoutFeedback = require('TouchableWithoutFeedback');
|
||||
const UIManager = require('UIManager');
|
||||
const ViewPropTypes = require('ViewPropTypes');
|
||||
const {ViewContextTypes} = require('ViewContext');
|
||||
|
||||
/* $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
|
||||
@@ -47,11 +49,19 @@ const onlyMultiline = {
|
||||
children: true,
|
||||
};
|
||||
|
||||
import type {ViewChildContext} from 'ViewContext';
|
||||
|
||||
if (Platform.OS === 'android') {
|
||||
var AndroidTextInput = requireNativeComponent('AndroidTextInput', null);
|
||||
} else if (Platform.OS === 'ios') {
|
||||
var RCTMultilineTextInputView = requireNativeComponent('RCTMultilineTextInputView', null);
|
||||
var RCTSinglelineTextInputView = requireNativeComponent('RCTSinglelineTextInputView', null);
|
||||
var RCTMultilineTextInputView = requireNativeComponent(
|
||||
'RCTMultilineTextInputView',
|
||||
null,
|
||||
);
|
||||
var RCTSinglelineTextInputView = requireNativeComponent(
|
||||
'RCTSinglelineTextInputView',
|
||||
null,
|
||||
);
|
||||
}
|
||||
|
||||
type Event = Object;
|
||||
@@ -279,11 +289,7 @@ const TextInput = createReactClass({
|
||||
* Determines the color of the keyboard.
|
||||
* @platform ios
|
||||
*/
|
||||
keyboardAppearance: PropTypes.oneOf([
|
||||
'default',
|
||||
'light',
|
||||
'dark',
|
||||
]),
|
||||
keyboardAppearance: PropTypes.oneOf(['default', 'light', 'dark']),
|
||||
/**
|
||||
* Determines how the return key should look. On Android you can also use
|
||||
* `returnKeyLabel`.
|
||||
@@ -448,8 +454,8 @@ const TextInput = createReactClass({
|
||||
*/
|
||||
secureTextEntry: PropTypes.bool,
|
||||
/**
|
||||
* The highlight and cursor color of the text input.
|
||||
*/
|
||||
* The highlight and cursor color of the text input.
|
||||
*/
|
||||
selectionColor: ColorPropType,
|
||||
/**
|
||||
* An instance of `DocumentSelectionState`, this is some state that is responsible for
|
||||
@@ -493,6 +499,7 @@ const TextInput = createReactClass({
|
||||
defaultValue: PropTypes.string,
|
||||
/**
|
||||
* When the clear button should appear on the right side of the text view.
|
||||
* This property is supported only for single-line TextInput component.
|
||||
* @platform ios
|
||||
*/
|
||||
clearButtonMode: PropTypes.oneOf([
|
||||
@@ -585,6 +592,7 @@ const TextInput = createReactClass({
|
||||
]),
|
||||
/**
|
||||
* If `true`, caret is hidden. The default value is `false`.
|
||||
* This property is supported only for single-line TextInput component on iOS.
|
||||
*/
|
||||
caretHidden: PropTypes.bool,
|
||||
},
|
||||
@@ -603,13 +611,10 @@ const TextInput = createReactClass({
|
||||
* Returns `true` if the input is currently focused; `false` otherwise.
|
||||
*/
|
||||
isFocused: function(): boolean {
|
||||
return TextInputState.currentlyFocusedField() ===
|
||||
ReactNative.findNodeHandle(this._inputRef);
|
||||
},
|
||||
|
||||
contextTypes: {
|
||||
onFocusRequested: PropTypes.func,
|
||||
focusEmitter: PropTypes.instanceOf(EventEmitter),
|
||||
return (
|
||||
TextInputState.currentlyFocusedField() ===
|
||||
ReactNative.findNodeHandle(this._inputRef)
|
||||
);
|
||||
},
|
||||
|
||||
_inputRef: (undefined: any),
|
||||
@@ -627,13 +632,13 @@ const TextInput = createReactClass({
|
||||
}
|
||||
this._focusSubscription = this.context.focusEmitter.addListener(
|
||||
'focus',
|
||||
(el) => {
|
||||
el => {
|
||||
if (this === el) {
|
||||
this.requestAnimationFrame(this.focus);
|
||||
} else if (this.isFocused()) {
|
||||
this.blur();
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
if (this.props.autoFocus) {
|
||||
this.context.onFocusRequested(this);
|
||||
@@ -647,12 +652,18 @@ const TextInput = createReactClass({
|
||||
}
|
||||
},
|
||||
|
||||
getChildContext: function(): Object {
|
||||
return {isInAParentText: true};
|
||||
getChildContext(): ViewChildContext {
|
||||
return {
|
||||
isInAParentText: true,
|
||||
};
|
||||
},
|
||||
|
||||
childContextTypes: {
|
||||
isInAParentText: PropTypes.bool
|
||||
childContextTypes: ViewContextTypes,
|
||||
|
||||
contextTypes: {
|
||||
...ViewContextTypes,
|
||||
onFocusRequested: PropTypes.func,
|
||||
focusEmitter: PropTypes.instanceOf(EventEmitter),
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -664,34 +675,37 @@ const TextInput = createReactClass({
|
||||
|
||||
render: function() {
|
||||
if (Platform.OS === 'ios') {
|
||||
return this._renderIOS();
|
||||
return UIManager.RCTVirtualText
|
||||
? this._renderIOS()
|
||||
: this._renderIOSLegacy();
|
||||
} else if (Platform.OS === 'android') {
|
||||
return this._renderAndroid();
|
||||
}
|
||||
},
|
||||
|
||||
_getText: function(): ?string {
|
||||
return typeof this.props.value === 'string' ?
|
||||
this.props.value :
|
||||
(
|
||||
typeof this.props.defaultValue === 'string' ?
|
||||
this.props.defaultValue :
|
||||
''
|
||||
);
|
||||
return typeof this.props.value === 'string'
|
||||
? this.props.value
|
||||
: typeof this.props.defaultValue === 'string'
|
||||
? this.props.defaultValue
|
||||
: '';
|
||||
},
|
||||
|
||||
_setNativeRef: function(ref: any) {
|
||||
this._inputRef = ref;
|
||||
},
|
||||
|
||||
_renderIOS: function() {
|
||||
_renderIOSLegacy: function() {
|
||||
var textContainer;
|
||||
|
||||
var props = Object.assign({}, this.props);
|
||||
props.style = [this.props.style];
|
||||
|
||||
if (props.selection && props.selection.end == null) {
|
||||
props.selection = {start: props.selection.start, end: props.selection.start};
|
||||
props.selection = {
|
||||
start: props.selection.start,
|
||||
end: props.selection.start,
|
||||
};
|
||||
}
|
||||
|
||||
if (!props.multiline) {
|
||||
@@ -699,13 +713,15 @@ const TextInput = createReactClass({
|
||||
for (var propKey in onlyMultiline) {
|
||||
if (props[propKey]) {
|
||||
const error = new Error(
|
||||
'TextInput prop `' + propKey + '` is only supported with multiline.'
|
||||
'TextInput prop `' +
|
||||
propKey +
|
||||
'` is only supported with multiline.',
|
||||
);
|
||||
warning(false, '%s', error.stack);
|
||||
}
|
||||
}
|
||||
}
|
||||
textContainer =
|
||||
textContainer = (
|
||||
<RCTSinglelineTextInputView
|
||||
ref={this._setNativeRef}
|
||||
{...props}
|
||||
@@ -715,23 +731,28 @@ const TextInput = createReactClass({
|
||||
onSelectionChange={this._onSelectionChange}
|
||||
onSelectionChangeShouldSetResponder={emptyFunction.thatReturnsTrue}
|
||||
text={this._getText()}
|
||||
/>;
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
var children = props.children;
|
||||
var childCount = 0;
|
||||
React.Children.forEach(children, () => ++childCount);
|
||||
invariant(
|
||||
!(props.value && childCount),
|
||||
'Cannot specify both value and children.'
|
||||
'Cannot specify both value and children.',
|
||||
);
|
||||
if (childCount >= 1) {
|
||||
children = <Text style={props.style} allowFontScaling={props.allowFontScaling}>{children}</Text>;
|
||||
children = (
|
||||
<Text style={props.style} allowFontScaling={props.allowFontScaling}>
|
||||
{children}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
if (props.inputView) {
|
||||
children = [children, props.inputView];
|
||||
}
|
||||
props.style.unshift(styles.multilineInput);
|
||||
textContainer =
|
||||
textContainer = (
|
||||
<RCTMultilineTextInputView
|
||||
ref={this._setNativeRef}
|
||||
{...props}
|
||||
@@ -746,7 +767,8 @@ const TextInput = createReactClass({
|
||||
text={this._getText()}
|
||||
dataDetectorTypes={this.props.dataDetectorTypes}
|
||||
onScroll={this._onScroll}
|
||||
/>;
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -764,6 +786,57 @@ const TextInput = createReactClass({
|
||||
);
|
||||
},
|
||||
|
||||
_renderIOS: function() {
|
||||
var props = Object.assign({}, this.props);
|
||||
props.style = [this.props.style];
|
||||
|
||||
if (props.selection && props.selection.end == null) {
|
||||
props.selection = {
|
||||
start: props.selection.start,
|
||||
end: props.selection.start,
|
||||
};
|
||||
}
|
||||
|
||||
const RCTTextInputView = props.multiline
|
||||
? RCTMultilineTextInputView
|
||||
: RCTSinglelineTextInputView;
|
||||
|
||||
if (props.multiline) {
|
||||
props.style.unshift(styles.multilineInput);
|
||||
}
|
||||
|
||||
const textContainer = (
|
||||
<RCTTextInputView
|
||||
ref={this._setNativeRef}
|
||||
{...props}
|
||||
onFocus={this._onFocus}
|
||||
onBlur={this._onBlur}
|
||||
onChange={this._onChange}
|
||||
onContentSizeChange={this.props.onContentSizeChange}
|
||||
onSelectionChange={this._onSelectionChange}
|
||||
onTextInput={this._onTextInput}
|
||||
onSelectionChangeShouldSetResponder={emptyFunction.thatReturnsTrue}
|
||||
text={this._getText()}
|
||||
dataDetectorTypes={this.props.dataDetectorTypes}
|
||||
onScroll={this._onScroll}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<TouchableWithoutFeedback
|
||||
onLayout={props.onLayout}
|
||||
onPress={this._onPress}
|
||||
rejectResponderTermination={true}
|
||||
accessible={props.accessible}
|
||||
accessibilityLabel={props.accessibilityLabel}
|
||||
accessibilityTraits={props.accessibilityTraits}
|
||||
nativeID={this.props.nativeID}
|
||||
testID={props.testID}>
|
||||
{textContainer}
|
||||
</TouchableWithoutFeedback>
|
||||
);
|
||||
},
|
||||
|
||||
_renderAndroid: function() {
|
||||
const props = Object.assign({}, this.props);
|
||||
props.style = [this.props.style];
|
||||
@@ -779,17 +852,20 @@ const TextInput = createReactClass({
|
||||
React.Children.forEach(children, () => ++childCount);
|
||||
invariant(
|
||||
!(this.props.value && childCount),
|
||||
'Cannot specify both value and children.'
|
||||
'Cannot specify both value and children.',
|
||||
);
|
||||
if (childCount > 1) {
|
||||
children = <Text>{children}</Text>;
|
||||
}
|
||||
|
||||
if (props.selection && props.selection.end == null) {
|
||||
props.selection = {start: props.selection.start, end: props.selection.start};
|
||||
props.selection = {
|
||||
start: props.selection.start,
|
||||
end: props.selection.start,
|
||||
};
|
||||
}
|
||||
|
||||
const textContainer =
|
||||
const textContainer = (
|
||||
<AndroidTextInput
|
||||
ref={this._setNativeRef}
|
||||
{...props}
|
||||
@@ -804,7 +880,8 @@ const TextInput = createReactClass({
|
||||
disableFullscreenUI={this.props.disableFullscreenUI}
|
||||
textBreakStrategy={this.props.textBreakStrategy}
|
||||
onScroll={this._onScroll}
|
||||
/>;
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<TouchableWithoutFeedback
|
||||
@@ -875,22 +952,28 @@ const TextInput = createReactClass({
|
||||
}
|
||||
},
|
||||
|
||||
componentDidUpdate: function () {
|
||||
componentDidUpdate: function() {
|
||||
// This is necessary in case native updates the text and JS decides
|
||||
// that the update should be ignored and we should stick with the value
|
||||
// that we have in JS.
|
||||
const nativeProps = {};
|
||||
|
||||
if (this._lastNativeText !== this.props.value && typeof this.props.value === 'string') {
|
||||
if (
|
||||
this._lastNativeText !== this.props.value &&
|
||||
typeof this.props.value === 'string'
|
||||
) {
|
||||
nativeProps.text = this.props.value;
|
||||
}
|
||||
|
||||
// Selection is also a controlled prop, if the native value doesn't match
|
||||
// JS, update to the JS value.
|
||||
const {selection} = this.props;
|
||||
if (this._lastNativeSelection && selection &&
|
||||
(this._lastNativeSelection.start !== selection.start ||
|
||||
this._lastNativeSelection.end !== selection.end)) {
|
||||
if (
|
||||
this._lastNativeSelection &&
|
||||
selection &&
|
||||
(this._lastNativeSelection.start !== selection.start ||
|
||||
this._lastNativeSelection.end !== selection.end)
|
||||
) {
|
||||
nativeProps.selection = this.props.selection;
|
||||
}
|
||||
|
||||
|
||||
@@ -203,7 +203,7 @@ const TouchableHighlight = createReactClass({
|
||||
clearTimeout(this._hideTimeout);
|
||||
},
|
||||
|
||||
componentWillReceiveProps: function(nextProps) {
|
||||
UNSAFE_componentWillReceiveProps: function(nextProps) {
|
||||
ensurePositiveDelayProps(nextProps);
|
||||
},
|
||||
|
||||
|
||||
@@ -147,7 +147,7 @@ var TouchableNativeFeedback = createReactClass({
|
||||
ensurePositiveDelayProps(this.props);
|
||||
},
|
||||
|
||||
componentWillReceiveProps: function(nextProps) {
|
||||
UNSAFE_componentWillReceiveProps: function(nextProps) {
|
||||
ensurePositiveDelayProps(nextProps);
|
||||
},
|
||||
|
||||
|
||||
@@ -158,7 +158,7 @@ var TouchableOpacity = createReactClass({
|
||||
ensurePositiveDelayProps(this.props);
|
||||
},
|
||||
|
||||
componentWillReceiveProps: function(nextProps) {
|
||||
UNSAFE_componentWillReceiveProps: function(nextProps) {
|
||||
ensurePositiveDelayProps(nextProps);
|
||||
},
|
||||
|
||||
|
||||
@@ -122,7 +122,7 @@ const TouchableWithoutFeedback = createReactClass({
|
||||
ensurePositiveDelayProps(this.props);
|
||||
},
|
||||
|
||||
componentWillReceiveProps: function(nextProps: Object) {
|
||||
UNSAFE_componentWillReceiveProps: function(nextProps: Object) {
|
||||
ensurePositiveDelayProps(nextProps);
|
||||
},
|
||||
|
||||
|
||||
@@ -8,22 +8,24 @@
|
||||
*
|
||||
* @providesModule View
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const NativeMethodsMixin = require('NativeMethodsMixin');
|
||||
const Platform = require('Platform');
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('React');
|
||||
const ReactNativeStyleAttributes = require('ReactNativeStyleAttributes');
|
||||
const ReactNativeViewAttributes = require('ReactNativeViewAttributes');
|
||||
const ViewPropTypes = require('ViewPropTypes');
|
||||
const {ViewContextTypes} = require('ViewContext');
|
||||
|
||||
const createReactClass = require('create-react-class');
|
||||
const invariant = require('fbjs/lib/invariant');
|
||||
const requireNativeComponent = require('requireNativeComponent');
|
||||
|
||||
import type {ViewProps} from 'ViewPropTypes';
|
||||
import type {ViewChildContext} from 'ViewContext';
|
||||
|
||||
export type Props = ViewProps;
|
||||
|
||||
@@ -52,17 +54,22 @@ const View = createReactClass({
|
||||
*/
|
||||
viewConfig: {
|
||||
uiViewClassName: 'RCTView',
|
||||
validAttributes: ReactNativeViewAttributes.RCTView
|
||||
validAttributes: ReactNativeViewAttributes.RCTView,
|
||||
},
|
||||
|
||||
contextTypes: {
|
||||
isInAParentText: PropTypes.bool,
|
||||
childContextTypes: ViewContextTypes,
|
||||
|
||||
getChildContext(): ViewChildContext {
|
||||
return {
|
||||
isInAParentText: false,
|
||||
};
|
||||
},
|
||||
|
||||
render: function() {
|
||||
render() {
|
||||
invariant(
|
||||
!(this.context.isInAParentText && Platform.OS === 'android'),
|
||||
'Nesting of <View> within <Text> is not supported on Android.');
|
||||
'Nesting of <View> within <Text> is not supported on Android.',
|
||||
);
|
||||
|
||||
// WARNING: This method will not be used in production mode as in that mode we
|
||||
// replace wrapper component View with generated native wrapper RCTView. Avoid
|
||||
@@ -76,17 +83,18 @@ const RCTView = requireNativeComponent('RCTView', View, {
|
||||
nativeOnly: {
|
||||
nativeBackgroundAndroid: true,
|
||||
nativeForegroundAndroid: true,
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
if (__DEV__) {
|
||||
const UIManager = require('UIManager');
|
||||
const viewConfig = UIManager.viewConfigs && UIManager.viewConfigs.RCTView || {};
|
||||
const viewConfig =
|
||||
(UIManager.viewConfigs && UIManager.viewConfigs.RCTView) || {};
|
||||
for (const prop in viewConfig.nativeProps) {
|
||||
const viewAny: any = View; // Appease flow
|
||||
if (!viewAny.propTypes[prop] && !ReactNativeStyleAttributes[prop]) {
|
||||
throw new Error(
|
||||
'View is missing propType for native prop `' + prop + '`'
|
||||
'View is missing propType for native prop `' + prop + '`',
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -98,4 +106,4 @@ if (__DEV__) {
|
||||
}
|
||||
|
||||
// No one should depend on the DEV-mode createClass View wrapper.
|
||||
module.exports = ((ViewToExport : any) : typeof RCTView);
|
||||
module.exports = ((ViewToExport: any): typeof RCTView);
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* 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 ViewContext
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const PropTypes = require('prop-types');
|
||||
|
||||
export type ViewChildContext = {|
|
||||
+isInAParentText: boolean,
|
||||
|};
|
||||
|
||||
export const ViewContextTypes = {
|
||||
isInAParentText: PropTypes.bool,
|
||||
};
|
||||
@@ -55,6 +55,7 @@ export type ViewProps = {
|
||||
importantForAccessibility?: 'auto'| 'yes'| 'no'| 'no-hide-descendants',
|
||||
accessibilityTraits?: AccessibilityTrait | Array<AccessibilityTrait>,
|
||||
accessibilityViewIsModal?: bool,
|
||||
accessibilityElementsHidden?: bool,
|
||||
onAccessibilityAction?: Function,
|
||||
onAccessibilityTap?: Function,
|
||||
onMagicTap?: Function,
|
||||
@@ -173,6 +174,16 @@ module.exports = {
|
||||
* See http://facebook.github.io/react-native/docs/view.html#accessibilityviewismodal
|
||||
*/
|
||||
accessibilityViewIsModal: PropTypes.bool,
|
||||
|
||||
/**
|
||||
* A value indicating whether the accessibility elements contained within
|
||||
* this accessibility element are hidden.
|
||||
*
|
||||
* @platform ios
|
||||
*
|
||||
* See http://facebook.github.io/react-native/docs/view.html#accessibilityElementsHidden
|
||||
*/
|
||||
accessibilityElementsHidden: PropTypes.bool,
|
||||
|
||||
/**
|
||||
* When `accessible` is true, the system will try to invoke this function
|
||||
|
||||
@@ -247,7 +247,7 @@ class WebView extends React.Component {
|
||||
startInLoadingState: true,
|
||||
};
|
||||
|
||||
componentWillMount() {
|
||||
UNSAFE_componentWillMount() {
|
||||
if (this.props.startInLoadingState) {
|
||||
this.setState({viewState: WebViewState.LOADING});
|
||||
}
|
||||
|
||||
@@ -410,7 +410,7 @@ class WebView extends React.Component {
|
||||
startInLoadingState: true,
|
||||
};
|
||||
|
||||
componentWillMount() {
|
||||
UNSAFE_componentWillMount() {
|
||||
if (this.props.startInLoadingState) {
|
||||
this.setState({viewState: WebViewState.LOADING});
|
||||
}
|
||||
|
||||
@@ -171,6 +171,8 @@ polyfillGlobal('Request', () => require('fetch').Request);
|
||||
polyfillGlobal('Response', () => require('fetch').Response);
|
||||
polyfillGlobal('WebSocket', () => require('WebSocket'));
|
||||
polyfillGlobal('Blob', () => require('Blob'));
|
||||
polyfillGlobal('File', () => require('File'));
|
||||
polyfillGlobal('FileReader', () => require('FileReader'));
|
||||
polyfillGlobal('URL', () => require('URL'));
|
||||
|
||||
// Set up alert
|
||||
@@ -203,6 +205,7 @@ BatchedBridge.registerLazyCallableModule('RCTLog', () => require('RCTLog'));
|
||||
BatchedBridge.registerLazyCallableModule('RCTDeviceEventEmitter', () => require('RCTDeviceEventEmitter'));
|
||||
BatchedBridge.registerLazyCallableModule('RCTNativeAppEventEmitter', () => require('RCTNativeAppEventEmitter'));
|
||||
BatchedBridge.registerLazyCallableModule('PerformanceLogger', () => require('PerformanceLogger'));
|
||||
BatchedBridge.registerLazyCallableModule('JSDevSupportModule', () => require('JSDevSupportModule'));
|
||||
|
||||
global.fetchSegment = function(
|
||||
segmentId: number,
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
exports.version = {
|
||||
major: 0,
|
||||
minor: 0,
|
||||
patch: 0,
|
||||
minor: 54,
|
||||
patch: 4,
|
||||
prerelease: null,
|
||||
};
|
||||
|
||||
@@ -244,11 +244,7 @@ const JSTimers = {
|
||||
* @param {function} func Callback to be invoked after `duration` ms.
|
||||
* @param {number} duration Number of milliseconds.
|
||||
*/
|
||||
setTimeout: function(
|
||||
func: Function,
|
||||
duration: number,
|
||||
...args?: any
|
||||
): number {
|
||||
setTimeout: function(func: Function, duration: number, ...args: any): number {
|
||||
if (__DEV__ && IS_ANDROID && duration > MAX_TIMER_DURATION_MS) {
|
||||
console.warn(
|
||||
ANDROID_LONG_TIMER_MESSAGE +
|
||||
@@ -273,7 +269,7 @@ const JSTimers = {
|
||||
setInterval: function(
|
||||
func: Function,
|
||||
duration: number,
|
||||
...args?: any
|
||||
...args: any
|
||||
): number {
|
||||
if (__DEV__ && IS_ANDROID && duration > MAX_TIMER_DURATION_MS) {
|
||||
console.warn(
|
||||
@@ -296,7 +292,7 @@ const JSTimers = {
|
||||
* @param {function} func Callback to be invoked before the end of the
|
||||
* current JavaScript execution loop.
|
||||
*/
|
||||
setImmediate: function(func: Function, ...args?: any) {
|
||||
setImmediate: function(func: Function, ...args: any) {
|
||||
const id = _allocateCallback(
|
||||
() => func.apply(undefined, args),
|
||||
'setImmediate',
|
||||
|
||||
@@ -32,7 +32,7 @@ const DEBUG = false;
|
||||
*
|
||||
* `<Incremental>` solves this by slicing up rendering into chunks that are
|
||||
* spread across multiple event loops. Expensive components can be sliced up
|
||||
* recursively by wrapping pieces of them and their decendents in
|
||||
* recursively by wrapping pieces of them and their descendants in
|
||||
* `<Incremental>` components. `<IncrementalGroup>` can be used to make sure
|
||||
* everything in the group is rendered recursively before calling `onDone` and
|
||||
* moving on to another sibling group (e.g. render one row at a time, even if
|
||||
@@ -83,7 +83,7 @@ const DEBUG = false;
|
||||
*/
|
||||
export type Props = {
|
||||
/**
|
||||
* Called when all the decendents have finished rendering and mounting
|
||||
* Called when all the descendants have finished rendering and mounting
|
||||
* recursively.
|
||||
*/
|
||||
onDone?: () => void,
|
||||
@@ -129,7 +129,7 @@ class Incremental extends React.Component<Props, State> {
|
||||
return ctx.groupId + ':' + this._incrementId + '-' + this.props.name;
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
UNSAFE_componentWillMount() {
|
||||
const ctx = this.context.incrementalGroup;
|
||||
if (!ctx) {
|
||||
return;
|
||||
|
||||
@@ -37,7 +37,7 @@ import type {Props, Context} from 'Incremental';
|
||||
class IncrementalGroup extends React.Component<Props & {disabled?: boolean}> {
|
||||
context: Context;
|
||||
_groupInc: string;
|
||||
componentWillMount() {
|
||||
UNSAFE_componentWillMount() {
|
||||
this._groupInc = `g${++_groupCounter}-`;
|
||||
DEBUG && infoLog(
|
||||
'create IncrementalGroup with id ' + this.getGroupId()
|
||||
|
||||
@@ -27,7 +27,7 @@ import type {Context} from 'Incremental';
|
||||
*
|
||||
* `<IncrementalPresenter>` can be used to group sets of `<Incremental>` renders
|
||||
* such that they are initially invisible and removed from layout until all
|
||||
* decendents have finished rendering, at which point they are drawn all at once
|
||||
* descendants have finished rendering, at which point they are drawn all at once
|
||||
* so the UI doesn't jump around during the incremental rendering process.
|
||||
*
|
||||
* See Incremental.js for more info.
|
||||
|
||||
@@ -51,7 +51,7 @@ type State = {
|
||||
*
|
||||
* - It ensures that at most 1 row is swiped open (auto closes others)
|
||||
* - It can bounce the 1st row of the list so users know it's swipeable
|
||||
* - Increase performance on iOS by locking list swiping when row swiping is occuring
|
||||
* - Increase performance on iOS by locking list swiping when row swiping is occurring
|
||||
* - More to come
|
||||
*/
|
||||
|
||||
|
||||
@@ -105,7 +105,7 @@ class SwipeableListView extends React.Component<Props, State> {
|
||||
};
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps: Props): void {
|
||||
UNSAFE_componentWillReceiveProps(nextProps: Props): void {
|
||||
if (this.state.dataSource.getDataSource() !== nextProps.dataSource.getDataSource()) {
|
||||
this.setState({
|
||||
dataSource: nextProps.dataSource,
|
||||
|
||||
@@ -125,7 +125,7 @@ const SwipeableRow = createReactClass({
|
||||
};
|
||||
},
|
||||
|
||||
componentWillMount(): void {
|
||||
UNSAFE_componentWillMount(): void {
|
||||
this._panResponder = PanResponder.create({
|
||||
onMoveShouldSetPanResponderCapture: this._handleMoveShouldSetPanResponderCapture,
|
||||
onPanResponderGrant: this._handlePanResponderGrant,
|
||||
@@ -149,7 +149,7 @@ const SwipeableRow = createReactClass({
|
||||
}
|
||||
},
|
||||
|
||||
componentWillReceiveProps(nextProps: Object): void {
|
||||
UNSAFE_componentWillReceiveProps(nextProps: Object): void {
|
||||
/**
|
||||
* We do not need an "animateOpen(noCallback)" because this animation is
|
||||
* handled internally by this component.
|
||||
@@ -159,15 +159,6 @@ const SwipeableRow = createReactClass({
|
||||
}
|
||||
},
|
||||
|
||||
shouldComponentUpdate(nextProps: Object, nextState: Object): boolean {
|
||||
if (this.props.shouldBounceOnMount && !nextProps.shouldBounceOnMount) {
|
||||
// No need to rerender if SwipeableListView is disabling the bounce flag
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
render(): React.Element<any> {
|
||||
// The view hidden behind the main view
|
||||
let slideOutView;
|
||||
|
||||
@@ -243,7 +243,7 @@ class WindowedListView extends React.Component<Props, State> {
|
||||
DEBUG && infoLog(' knope');
|
||||
return false;
|
||||
}
|
||||
componentWillReceiveProps() {
|
||||
UNSAFE_componentWillReceiveProps() {
|
||||
this._computeRowsToRenderBatcher.schedule();
|
||||
}
|
||||
_onMomentumScrollEnd = (e: Object) => {
|
||||
@@ -392,7 +392,7 @@ class WindowedListView extends React.Component<Props, State> {
|
||||
}
|
||||
if (props.onEndReached) {
|
||||
// Make sure we call onEndReached exactly once every time we reach the
|
||||
// end. Resets if scoll back up and down again.
|
||||
// end. Resets if scroll back up and down again.
|
||||
const willBeAtTheEnd = lastRow === (totalRows - 1);
|
||||
if (willBeAtTheEnd && !this._hasCalledOnEndReached) {
|
||||
props.onEndReached();
|
||||
@@ -624,7 +624,7 @@ class CellRenderer extends React.Component<CellProps> {
|
||||
_perfUpdateID: number = 0;
|
||||
_asyncCookie: any;
|
||||
_includeInLayoutLatch: boolean = false;
|
||||
componentWillMount() {
|
||||
UNSAFE_componentWillMount() {
|
||||
if (this.props.asyncRowPerfEventName) {
|
||||
this._perfUpdateID = g_perf_update_id++;
|
||||
this._asyncCookie = Systrace.beginAsyncEvent(
|
||||
@@ -694,7 +694,7 @@ class CellRenderer extends React.Component<CellProps> {
|
||||
this.props.onProgressChange({rowKey: this.props.rowKey, inProgress: false});
|
||||
this.props.onWillUnmount(this.props.rowKey);
|
||||
}
|
||||
componentWillReceiveProps(newProps) {
|
||||
UNSAFE_componentWillReceiveProps(newProps) {
|
||||
if (newProps.includeInLayout && !this.props.includeInLayout) {
|
||||
invariant(this._offscreenRenderDone, 'Should never try to add to layout before render done');
|
||||
this._includeInLayoutLatch = true; // Once we render in layout, make sure it sticks.
|
||||
|
||||
@@ -45,58 +45,14 @@ type GeoOptions = {
|
||||
* The Geolocation API extends the web spec:
|
||||
* https://developer.mozilla.org/en-US/docs/Web/API/Geolocation
|
||||
*
|
||||
* As a browser polyfill, this API is available through the `navigator.geolocation`
|
||||
* global - you do not need to `import` it.
|
||||
*
|
||||
* ### Configuration and Permissions
|
||||
*
|
||||
* <div class="banner-crna-ejected">
|
||||
* <h3>Projects with Native Code Only</h3>
|
||||
* <p>
|
||||
* This section only applies to projects made with <code>react-native init</code>
|
||||
* or to those made with Create React Native App which have since ejected. For
|
||||
* more information about ejecting, please see
|
||||
* the <a href="https://github.com/react-community/create-react-native-app/blob/master/EJECTING.md" target="_blank">guide</a> on
|
||||
* the Create React Native App repository.
|
||||
* </p>
|
||||
* </div>
|
||||
*
|
||||
* #### iOS
|
||||
* You need to include the `NSLocationWhenInUseUsageDescription` key
|
||||
* in Info.plist to enable geolocation when using the app. Geolocation is
|
||||
* enabled by default when you create a project with `react-native init`.
|
||||
*
|
||||
* In order to enable geolocation in the background, you need to include the
|
||||
* 'NSLocationAlwaysUsageDescription' key in Info.plist and add location as
|
||||
* a background mode in the 'Capabilities' tab in Xcode.
|
||||
*
|
||||
* #### Android
|
||||
* To request access to location, you need to add the following line to your
|
||||
* app's `AndroidManifest.xml`:
|
||||
*
|
||||
* `<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />`
|
||||
*
|
||||
* Android API >= 18 Positions will also contain a `mocked` boolean to indicate if position
|
||||
* was created from a mock provider.
|
||||
*
|
||||
* <p>
|
||||
* Android API >= 23 Requires an additional step to check for, and request
|
||||
* the ACCESS_FINE_LOCATION permission using
|
||||
* the <a href="https://facebook.github.io/react-native/docs/permissionsandroid.html" target="_blank">PermissionsAndroid API</a>.
|
||||
* Failure to do so may result in a hard crash.
|
||||
* </p>
|
||||
* See https://facebook.github.io/react-native/docs/geolocation.html
|
||||
*/
|
||||
var Geolocation = {
|
||||
|
||||
/*
|
||||
* Sets configuration options that will be used in all location requests.
|
||||
*
|
||||
* ### Options
|
||||
*
|
||||
* #### iOS
|
||||
*
|
||||
* - `skipPermissionRequests` - defaults to `false`, if `true` you must request permissions
|
||||
* before using Geolocation APIs.
|
||||
* See https://facebook.github.io/react-native/docs/geolocation.html#setrnconfiguration
|
||||
*
|
||||
*/
|
||||
setRNConfiguration: function(
|
||||
@@ -109,19 +65,17 @@ var Geolocation = {
|
||||
|
||||
/*
|
||||
* Request suitable Location permission based on the key configured on pList.
|
||||
* If NSLocationAlwaysUsageDescription is set, it will request Always authorization,
|
||||
* although if NSLocationWhenInUseUsageDescription is set, it will request InUse
|
||||
* authorization.
|
||||
*
|
||||
* See https://facebook.github.io/react-native/docs/geolocation.html#requestauthorization
|
||||
*/
|
||||
requestAuthorization: function() {
|
||||
RCTLocationObserver.requestAuthorization();
|
||||
},
|
||||
|
||||
/*
|
||||
* Invokes the success callback once with the latest location info. Supported
|
||||
* options: timeout (ms), maximumAge (ms), enableHighAccuracy (bool)
|
||||
* On Android, if the location is cached this can return almost immediately,
|
||||
* or it will request an update which might take a while.
|
||||
* Invokes the success callback once with the latest location info.
|
||||
*
|
||||
* See https://facebook.github.io/react-native/docs/geolocation.html#getcurrentposition
|
||||
*/
|
||||
getCurrentPosition: async function(
|
||||
geo_success: Function,
|
||||
@@ -156,8 +110,9 @@ var Geolocation = {
|
||||
},
|
||||
|
||||
/*
|
||||
* Invokes the success callback whenever the location changes. Supported
|
||||
* options: timeout (ms), maximumAge (ms), enableHighAccuracy (bool), distanceFilter(m), useSignificantChanges (bool)
|
||||
* Invokes the success callback whenever the location changes.
|
||||
*
|
||||
* See https://facebook.github.io/react-native/docs/geolocation.html#watchposition
|
||||
*/
|
||||
watchPosition: function(success: Function, error?: Function, options?: GeoOptions): number {
|
||||
if (!updatesEnabled) {
|
||||
|
||||
@@ -158,6 +158,11 @@ RCT_EXPORT_MODULE()
|
||||
if (!_locationConfiguration.skipPermissionRequests) {
|
||||
[self requestAuthorization];
|
||||
}
|
||||
|
||||
if (!_locationManager) {
|
||||
_locationManager = [CLLocationManager new];
|
||||
_locationManager.delegate = self;
|
||||
}
|
||||
|
||||
_locationManager.distanceFilter = distanceFilter;
|
||||
_locationManager.desiredAccuracy = desiredAccuracy;
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
*
|
||||
* @providesModule AssetRegistry
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
|
||||
export type PackagerAsset = {
|
||||
+__packager_asset: boolean,
|
||||
+fileSystemLocation: string,
|
||||
@@ -24,7 +24,6 @@ export type PackagerAsset = {
|
||||
+type: string,
|
||||
};
|
||||
|
||||
|
||||
var assets: Array<PackagerAsset> = [];
|
||||
|
||||
function registerAsset(asset: PackagerAsset): number {
|
||||
@@ -37,4 +36,4 @@ function getAssetByID(assetId: number): PackagerAsset {
|
||||
return assets[assetId - 1];
|
||||
}
|
||||
|
||||
module.exports = { registerAsset, getAssetByID };
|
||||
module.exports = {registerAsset, getAssetByID};
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
*
|
||||
* @providesModule AssetSourceResolver
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
@@ -19,7 +20,7 @@ export type ResolvedAssetSource = {
|
||||
scale: number,
|
||||
};
|
||||
|
||||
import type { PackagerAsset } from 'AssetRegistry';
|
||||
import type {PackagerAsset} from 'AssetRegistry';
|
||||
|
||||
const PixelRatio = require('PixelRatio');
|
||||
const Platform = require('Platform');
|
||||
@@ -43,22 +44,18 @@ function getScaledAssetPath(asset): string {
|
||||
function getAssetPathInDrawableFolder(asset): string {
|
||||
var scale = AssetSourceResolver.pickScale(asset.scales, PixelRatio.get());
|
||||
var drawbleFolder = assetPathUtils.getAndroidResourceFolderName(asset, scale);
|
||||
var fileName = assetPathUtils.getAndroidResourceIdentifier(asset);
|
||||
var fileName = assetPathUtils.getAndroidResourceIdentifier(asset);
|
||||
return drawbleFolder + '/' + fileName + '.' + asset.type;
|
||||
}
|
||||
|
||||
class AssetSourceResolver {
|
||||
|
||||
serverUrl: ?string;
|
||||
// where the jsbundle is being run from
|
||||
jsbundleUrl: ?string;
|
||||
// the asset to resolve
|
||||
asset: PackagerAsset;
|
||||
|
||||
constructor(serverUrl: ?string,
|
||||
jsbundleUrl: ?string,
|
||||
asset: PackagerAsset
|
||||
) {
|
||||
constructor(serverUrl: ?string, jsbundleUrl: ?string, asset: PackagerAsset) {
|
||||
this.serverUrl = serverUrl;
|
||||
this.jsbundleUrl = jsbundleUrl;
|
||||
this.asset = asset;
|
||||
@@ -78,9 +75,9 @@ class AssetSourceResolver {
|
||||
}
|
||||
|
||||
if (Platform.OS === 'android') {
|
||||
return this.isLoadedFromFileSystem() ?
|
||||
this.drawableFolderInBundle() :
|
||||
this.resourceIdentifierWithoutScale();
|
||||
return this.isLoadedFromFileSystem()
|
||||
? this.drawableFolderInBundle()
|
||||
: this.resourceIdentifierWithoutScale();
|
||||
} else {
|
||||
return this.scaledAssetURLNearBundle();
|
||||
}
|
||||
@@ -93,8 +90,12 @@ class AssetSourceResolver {
|
||||
assetServerURL(): ResolvedAssetSource {
|
||||
invariant(!!this.serverUrl, 'need server to load from');
|
||||
return this.fromSource(
|
||||
this.serverUrl + getScaledAssetPath(this.asset) +
|
||||
'?platform=' + Platform.OS + '&hash=' + this.asset.hash
|
||||
this.serverUrl +
|
||||
getScaledAssetPath(this.asset) +
|
||||
'?platform=' +
|
||||
Platform.OS +
|
||||
'&hash=' +
|
||||
this.asset.hash,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -122,8 +123,13 @@ class AssetSourceResolver {
|
||||
* E.g. 'assets_awesomemodule_icon'
|
||||
*/
|
||||
resourceIdentifierWithoutScale(): ResolvedAssetSource {
|
||||
invariant(Platform.OS === 'android', 'resource identifiers work on Android');
|
||||
return this.fromSource(assetPathUtils.getAndroidResourceIdentifier(this.asset));
|
||||
invariant(
|
||||
Platform.OS === 'android',
|
||||
'resource identifiers work on Android',
|
||||
);
|
||||
return this.fromSource(
|
||||
assetPathUtils.getAndroidResourceIdentifier(this.asset),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -133,9 +139,7 @@ class AssetSourceResolver {
|
||||
*/
|
||||
drawableFolderInBundle(): ResolvedAssetSource {
|
||||
const path = this.jsbundleUrl || 'file://';
|
||||
return this.fromSource(
|
||||
path + getAssetPathInDrawableFolder(this.asset)
|
||||
);
|
||||
return this.fromSource(path + getAssetPathInDrawableFolder(this.asset));
|
||||
}
|
||||
|
||||
fromSource(source: string): ResolvedAssetSource {
|
||||
@@ -161,7 +165,6 @@ class AssetSourceResolver {
|
||||
// in which case we default to 1
|
||||
return scales[scales.length - 1] || 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = AssetSourceResolver;
|
||||
module.exports = AssetSourceResolver;
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
*
|
||||
* @providesModule Image
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
@@ -18,23 +19,19 @@ var NativeModules = require('NativeModules');
|
||||
var React = require('React');
|
||||
var PropTypes = require('prop-types');
|
||||
var ReactNativeViewAttributes = require('ReactNativeViewAttributes');
|
||||
var Set = require('Set');
|
||||
var StyleSheet = require('StyleSheet');
|
||||
var StyleSheetPropType = require('StyleSheetPropType');
|
||||
var View = require('View');
|
||||
var ViewPropTypes = require('ViewPropTypes');
|
||||
var ViewStylePropTypes = require('ViewStylePropTypes');
|
||||
|
||||
var createReactClass = require('create-react-class');
|
||||
var filterObject = require('fbjs/lib/filterObject');
|
||||
var flattenStyle = require('flattenStyle');
|
||||
var merge = require('merge');
|
||||
var requireNativeComponent = require('requireNativeComponent');
|
||||
var resolveAssetSource = require('resolveAssetSource');
|
||||
|
||||
var {
|
||||
ImageLoader,
|
||||
} = NativeModules;
|
||||
const {ViewContextTypes} = require('ViewContext');
|
||||
|
||||
var {ImageLoader} = NativeModules;
|
||||
|
||||
let _requestId = 1;
|
||||
function generateRequestId() {
|
||||
@@ -42,57 +39,19 @@ function generateRequestId() {
|
||||
}
|
||||
|
||||
/**
|
||||
* <Image> - A react component for displaying different types of images,
|
||||
* A React component for displaying different types of images,
|
||||
* including network images, static resources, temporary local images, and
|
||||
* images from local disk, such as the camera roll. Example usage:
|
||||
* images from local disk, such as the camera roll.
|
||||
*
|
||||
* renderImages: function() {
|
||||
* return (
|
||||
* <View>
|
||||
* <Image
|
||||
* style={styles.icon}
|
||||
* source={require('./myIcon.png')}
|
||||
* />
|
||||
* <Image
|
||||
* style={styles.logo}
|
||||
* source={{uri: 'https://facebook.github.io/react-native/img/opengraph.png'}}
|
||||
* />
|
||||
* </View>
|
||||
* );
|
||||
* },
|
||||
*
|
||||
* More example code in ImageExample.js
|
||||
* See https://facebook.github.io/react-native/docs/image.html
|
||||
*/
|
||||
|
||||
var ImageViewAttributes = merge(ReactNativeViewAttributes.UIView, {
|
||||
src: true,
|
||||
loadingIndicatorSrc: true,
|
||||
resizeMethod: true,
|
||||
resizeMode: true,
|
||||
progressiveRenderingEnabled: true,
|
||||
fadeDuration: true,
|
||||
shouldNotifyLoadEvents: true,
|
||||
});
|
||||
|
||||
var ViewStyleKeys = new Set(Object.keys(ViewStylePropTypes));
|
||||
var ImageSpecificStyleKeys = new Set(Object.keys(ImageStylePropTypes).filter(x => !ViewStyleKeys.has(x)));
|
||||
|
||||
var Image = createReactClass({
|
||||
displayName: 'Image',
|
||||
propTypes: {
|
||||
...ViewPropTypes,
|
||||
style: StyleSheetPropType(ImageStylePropTypes),
|
||||
/**
|
||||
* `uri` is a string representing the resource identifier for the image, which
|
||||
* could be an http address, a local file path, or a static image
|
||||
* resource (which should be wrapped in the `require('./path/to/image.png')` function).
|
||||
*
|
||||
* `headers` is an object representing the HTTP headers to send along with the request
|
||||
* for a remote image.
|
||||
*
|
||||
* This prop can also contain several remote `uri`, specified together with
|
||||
* their width and height. The native side will then choose the best `uri` to display
|
||||
* based on the measured size of the image container.
|
||||
/**
|
||||
* See https://facebook.github.io/react-native/docs/image.html#source
|
||||
*/
|
||||
source: PropTypes.oneOfType([
|
||||
PropTypes.shape({
|
||||
@@ -108,16 +67,17 @@ var Image = createReactClass({
|
||||
width: PropTypes.number,
|
||||
height: PropTypes.number,
|
||||
headers: PropTypes.objectOf(PropTypes.string),
|
||||
}))
|
||||
}),
|
||||
),
|
||||
]),
|
||||
/**
|
||||
* blurRadius: the blur radius of the blur filter added to the image
|
||||
*/
|
||||
* blurRadius: the blur radius of the blur filter added to the image
|
||||
*
|
||||
* See https://facebook.github.io/react-native/docs/image.html#blurradius
|
||||
*/
|
||||
blurRadius: PropTypes.number,
|
||||
/**
|
||||
* similarly to `source`, this property represents the resource used to render
|
||||
* the loading indicator for the image, displayed until image is ready to be
|
||||
* displayed, typically after when it got downloaded from network.
|
||||
* See https://facebook.github.io/react-native/docs/image.html#loadingindicatorsource
|
||||
*/
|
||||
loadingIndicatorSource: PropTypes.oneOfType([
|
||||
PropTypes.shape({
|
||||
@@ -152,40 +112,14 @@ var Image = createReactClass({
|
||||
* The mechanism that should be used to resize the image when the image's dimensions
|
||||
* differ from the image view's dimensions. Defaults to `auto`.
|
||||
*
|
||||
* - `auto`: Use heuristics to pick between `resize` and `scale`.
|
||||
*
|
||||
* - `resize`: A software operation which changes the encoded image in memory before it
|
||||
* gets decoded. This should be used instead of `scale` when the image is much larger
|
||||
* than the view.
|
||||
*
|
||||
* - `scale`: The image gets drawn downscaled or upscaled. Compared to `resize`, `scale` is
|
||||
* faster (usually hardware accelerated) and produces higher quality images. This
|
||||
* should be used if the image is smaller than the view. It should also be used if the
|
||||
* image is slightly bigger than the view.
|
||||
*
|
||||
* More details about `resize` and `scale` can be found at http://frescolib.org/docs/resizing-rotating.html.
|
||||
*
|
||||
* @platform android
|
||||
* See https://facebook.github.io/react-native/docs/image.html#resizemethod
|
||||
*/
|
||||
resizeMethod: PropTypes.oneOf(['auto', 'resize', 'scale']),
|
||||
/**
|
||||
* Determines how to resize the image when the frame doesn't match the raw
|
||||
* image dimensions.
|
||||
*
|
||||
* 'cover': Scale the image uniformly (maintain the image's aspect ratio)
|
||||
* so that both dimensions (width and height) of the image will be equal
|
||||
* to or larger than the corresponding dimension of the view (minus padding).
|
||||
*
|
||||
* 'contain': Scale the image uniformly (maintain the image's aspect ratio)
|
||||
* so that both dimensions (width and height) of the image will be equal to
|
||||
* or less than the corresponding dimension of the view (minus padding).
|
||||
*
|
||||
* 'stretch': Scale width and height independently, This may change the
|
||||
* aspect ratio of the src.
|
||||
*
|
||||
* 'center': Scale the image down so that it is completely visible,
|
||||
* if bigger than the area of the view.
|
||||
* The image will not be scaled up.
|
||||
* See https://facebook.github.io/react-native/docs/image.html#resizemode
|
||||
*/
|
||||
resizeMode: PropTypes.oneOf(['cover', 'contain', 'stretch', 'center']),
|
||||
},
|
||||
@@ -202,14 +136,19 @@ var Image = createReactClass({
|
||||
.then(function(sizes) {
|
||||
success(sizes.width, sizes.height);
|
||||
})
|
||||
.catch(failure || function() {
|
||||
console.warn('Failed to get size for image: ' + url);
|
||||
});
|
||||
.catch(
|
||||
failure ||
|
||||
function() {
|
||||
console.warn('Failed to get size for image: ' + url);
|
||||
},
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Prefetches a remote image for later use by downloading it to the disk
|
||||
* cache
|
||||
*
|
||||
* See https://facebook.github.io/react-native/docs/image.html#prefetch
|
||||
*/
|
||||
prefetch(url: string, callback: ?Function) {
|
||||
const requestId = generateRequestId();
|
||||
@@ -218,7 +157,9 @@ var Image = createReactClass({
|
||||
},
|
||||
|
||||
/**
|
||||
* Abort prefetch request
|
||||
* Abort prefetch request.
|
||||
*
|
||||
* See https://facebook.github.io/react-native/docs/image.html#abortprefetch
|
||||
*/
|
||||
abortPrefetch(requestId: number) {
|
||||
ImageLoader.abortRequest(requestId);
|
||||
@@ -227,18 +168,18 @@ var Image = createReactClass({
|
||||
/**
|
||||
* Perform cache interrogation.
|
||||
*
|
||||
* @param urls the list of image URLs to check the cache for.
|
||||
* @return a mapping from url to cache status, such as "disk" or "memory". If a requested URL is
|
||||
* not in the mapping, it means it's not in the cache.
|
||||
* See https://facebook.github.io/react-native/docs/image.html#querycache
|
||||
*/
|
||||
async queryCache(urls: Array<string>): Promise<Map<string, 'memory' | 'disk'>> {
|
||||
async queryCache(
|
||||
urls: Array<string>,
|
||||
): Promise<Map<string, 'memory' | 'disk'>> {
|
||||
return await ImageLoader.queryCache(urls);
|
||||
},
|
||||
|
||||
/**
|
||||
* Resolves an asset reference into an object which has the properties `uri`, `width`,
|
||||
* and `height`. The input may either be a number (opaque type returned by
|
||||
* require('./foo.png')) or an `ImageSource` like { uri: '<http location || file path>' }
|
||||
* Resolves an asset reference into an object.
|
||||
*
|
||||
* See https://facebook.github.io/react-native/docs/image.html#resolveassetsource
|
||||
*/
|
||||
resolveAssetSource: resolveAssetSource,
|
||||
},
|
||||
@@ -254,13 +195,13 @@ var Image = createReactClass({
|
||||
validAttributes: ReactNativeViewAttributes.RCTView,
|
||||
},
|
||||
|
||||
contextTypes: {
|
||||
isInAParentText: PropTypes.bool
|
||||
},
|
||||
contextTypes: ViewContextTypes,
|
||||
|
||||
render: function() {
|
||||
const source = resolveAssetSource(this.props.source);
|
||||
const loadingIndicatorSource = resolveAssetSource(this.props.loadingIndicatorSource);
|
||||
const loadingIndicatorSource = resolveAssetSource(
|
||||
this.props.loadingIndicatorSource,
|
||||
);
|
||||
|
||||
// As opposed to the ios version, here we render `null` when there is no source, source.uri
|
||||
// or source array.
|
||||
@@ -270,11 +211,15 @@ var Image = createReactClass({
|
||||
}
|
||||
|
||||
if (this.props.src) {
|
||||
console.warn('The <Image> component requires a `source` property rather than `src`.');
|
||||
console.warn(
|
||||
'The <Image> component requires a `source` property rather than `src`.',
|
||||
);
|
||||
}
|
||||
|
||||
if (this.props.children) {
|
||||
throw new Error('The <Image> component cannot contain children. If you want to render content on top of the image, consider using the <ImageBackground> component or absolute positioning.');
|
||||
throw new Error(
|
||||
'The <Image> component cannot contain children. If you want to render content on top of the image, consider using the <ImageBackground> component or absolute positioning.',
|
||||
);
|
||||
}
|
||||
|
||||
if (source && (source.uri || Array.isArray(source))) {
|
||||
@@ -292,20 +237,27 @@ var Image = createReactClass({
|
||||
const {onLoadStart, onLoad, onLoadEnd, onError} = this.props;
|
||||
const nativeProps = merge(this.props, {
|
||||
style,
|
||||
shouldNotifyLoadEvents: !!(onLoadStart || onLoad || onLoadEnd || onError),
|
||||
shouldNotifyLoadEvents: !!(
|
||||
onLoadStart ||
|
||||
onLoad ||
|
||||
onLoadEnd ||
|
||||
onError
|
||||
),
|
||||
src: sources,
|
||||
headers: source.headers,
|
||||
loadingIndicatorSrc: loadingIndicatorSource ? loadingIndicatorSource.uri : null,
|
||||
loadingIndicatorSrc: loadingIndicatorSource
|
||||
? loadingIndicatorSource.uri
|
||||
: null,
|
||||
});
|
||||
|
||||
if (this.context.isInAParentText) {
|
||||
return <RCTTextInlineImage {...nativeProps}/>;
|
||||
return <RCTTextInlineImage {...nativeProps} />;
|
||||
} else {
|
||||
return <RKImage {...nativeProps}/>;
|
||||
return <RKImage {...nativeProps} />;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
@@ -323,6 +275,10 @@ var cfg = {
|
||||
},
|
||||
};
|
||||
var RKImage = requireNativeComponent('RCTImageView', Image, cfg);
|
||||
var RCTTextInlineImage = requireNativeComponent('RCTTextInlineImage', Image, cfg);
|
||||
var RCTTextInlineImage = requireNativeComponent(
|
||||
'RCTTextInlineImage',
|
||||
Image,
|
||||
cfg,
|
||||
);
|
||||
|
||||
module.exports = Image;
|
||||
|
||||
+69
-201
@@ -8,6 +8,7 @@
|
||||
*
|
||||
* @providesModule Image
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
@@ -35,141 +36,27 @@ const ImageViewManager = NativeModules.ImageViewManager;
|
||||
* including network images, static resources, temporary local images, and
|
||||
* images from local disk, such as the camera roll.
|
||||
*
|
||||
* This example shows fetching and displaying an image from local storage
|
||||
* as well as one from network and even from data provided in the `'data:'` uri scheme.
|
||||
*
|
||||
* > Note that for network and data images, you will need to manually specify the dimensions of your image!
|
||||
*
|
||||
* ```ReactNativeWebPlayer
|
||||
* import React, { Component } from 'react';
|
||||
* import { AppRegistry, View, Image } from 'react-native';
|
||||
*
|
||||
* export default class DisplayAnImage extends Component {
|
||||
* render() {
|
||||
* return (
|
||||
* <View>
|
||||
* <Image
|
||||
* source={require('./img/favicon.png')}
|
||||
* />
|
||||
* <Image
|
||||
* style={{width: 50, height: 50}}
|
||||
* source={{uri: 'https://facebook.github.io/react-native/img/favicon.png'}}
|
||||
* />
|
||||
* <Image
|
||||
* style={{width: 66, height: 58}}
|
||||
* source={{uri: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADMAAAAzCAYAAAA6oTAqAAAAEXRFWHRTb2Z0d2FyZQBwbmdjcnVzaEB1SfMAAABQSURBVGje7dSxCQBACARB+2/ab8BEeQNhFi6WSYzYLYudDQYGBgYGBgYGBgYGBgYGBgZmcvDqYGBgmhivGQYGBgYGBgYGBgYGBgYGBgbmQw+P/eMrC5UTVAAAAABJRU5ErkJggg=='}}
|
||||
* />
|
||||
* </View>
|
||||
* );
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* // skip this line if using Create React Native App
|
||||
* AppRegistry.registerComponent('DisplayAnImage', () => DisplayAnImage);
|
||||
* ```
|
||||
*
|
||||
* You can also add `style` to an image:
|
||||
*
|
||||
* ```ReactNativeWebPlayer
|
||||
* import React, { Component } from 'react';
|
||||
* import { AppRegistry, View, Image, StyleSheet } from 'react-native';
|
||||
*
|
||||
* const styles = StyleSheet.create({
|
||||
* stretch: {
|
||||
* width: 50,
|
||||
* height: 200
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* export default class DisplayAnImageWithStyle extends Component {
|
||||
* render() {
|
||||
* return (
|
||||
* <View>
|
||||
* <Image
|
||||
* style={styles.stretch}
|
||||
* source={require('./img/favicon.png')}
|
||||
* />
|
||||
* </View>
|
||||
* );
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* // skip these lines if using Create React Native App
|
||||
* AppRegistry.registerComponent(
|
||||
* 'DisplayAnImageWithStyle',
|
||||
* () => DisplayAnImageWithStyle
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* ### GIF and WebP support on Android
|
||||
*
|
||||
* When building your own native code, GIF and WebP are not supported by default on Android.
|
||||
*
|
||||
* You will need to add some optional modules in `android/app/build.gradle`, depending on the needs of your app.
|
||||
*
|
||||
* ```
|
||||
* dependencies {
|
||||
* // If your app supports Android versions before Ice Cream Sandwich (API level 14)
|
||||
* compile 'com.facebook.fresco:animated-base-support:1.3.0'
|
||||
*
|
||||
* // For animated GIF support
|
||||
* compile 'com.facebook.fresco:animated-gif:1.3.0'
|
||||
*
|
||||
* // For WebP support, including animated WebP
|
||||
* compile 'com.facebook.fresco:animated-webp:1.3.0'
|
||||
* compile 'com.facebook.fresco:webpsupport:1.3.0'
|
||||
*
|
||||
* // For WebP support, without animations
|
||||
* compile 'com.facebook.fresco:webpsupport:1.3.0'
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* Also, if you use GIF with ProGuard, you will need to add this rule in `proguard-rules.pro` :
|
||||
* ```
|
||||
* -keep class com.facebook.imagepipeline.animated.factory.AnimatedFactoryImpl {
|
||||
* public AnimatedFactoryImpl(com.facebook.imagepipeline.bitmaps.PlatformBitmapFactory, com.facebook.imagepipeline.core.ExecutorSupplier);
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* See https://facebook.github.io/react-native/docs/image.html
|
||||
*/
|
||||
const Image = createReactClass({
|
||||
displayName: 'Image',
|
||||
propTypes: {
|
||||
/**
|
||||
* > `ImageResizeMode` is an `Enum` for different image resizing modes, set via the
|
||||
* > `resizeMode` style property on `Image` components. The values are `contain`, `cover`,
|
||||
* > `stretch`, `center`, `repeat`.
|
||||
* See https://facebook.github.io/react-native/docs/image.html#style
|
||||
*/
|
||||
style: StyleSheetPropType(ImageStylePropTypes),
|
||||
/**
|
||||
* The image source (either a remote URL or a local file resource).
|
||||
*
|
||||
* This prop can also contain several remote URLs, specified together with
|
||||
* their width and height and potentially with scale/other URI arguments.
|
||||
* The native side will then choose the best `uri` to display based on the
|
||||
* measured size of the image container. A `cache` property can be added to
|
||||
* control how networked request interacts with the local cache.
|
||||
*
|
||||
* The currently supported formats are `png`, `jpg`, `jpeg`, `bmp`, `gif`,
|
||||
* `webp` (Android only), `psd` (iOS only).
|
||||
* See https://facebook.github.io/react-native/docs/image.html#source
|
||||
*/
|
||||
source: ImageSourcePropType,
|
||||
/**
|
||||
* A static image to display while loading the image source.
|
||||
*
|
||||
* - `uri` - a string representing the resource identifier for the image, which
|
||||
* should be either a local file path or the name of a static image resource
|
||||
* (which should be wrapped in the `require('./path/to/image.png')` function).
|
||||
* - `width`, `height` - can be specified if known at build time, in which case
|
||||
* these will be used to set the default `<Image/>` component dimensions.
|
||||
* - `scale` - used to indicate the scale factor of the image. Defaults to 1.0 if
|
||||
* unspecified, meaning that one image pixel equates to one display point / DIP.
|
||||
* - `number` - Opaque type returned by something like `require('./image.jpg')`.
|
||||
*
|
||||
* @platform ios
|
||||
* See https://facebook.github.io/react-native/docs/image.html#defaultsource
|
||||
*/
|
||||
defaultSource: PropTypes.oneOfType([
|
||||
// TODO: Tooling to support documenting these directly and having them display in the docs.
|
||||
PropTypes.shape({
|
||||
uri: PropTypes.string,
|
||||
width: PropTypes.number,
|
||||
@@ -180,106 +67,92 @@ const Image = createReactClass({
|
||||
]),
|
||||
/**
|
||||
* When true, indicates the image is an accessibility element.
|
||||
* @platform ios
|
||||
*
|
||||
* See https://facebook.github.io/react-native/docs/image.html#accessible
|
||||
*/
|
||||
accessible: PropTypes.bool,
|
||||
/**
|
||||
* The text that's read by the screen reader when the user interacts with
|
||||
* the image.
|
||||
* @platform ios
|
||||
*
|
||||
* See https://facebook.github.io/react-native/docs/image.html#accessibilitylabel
|
||||
*/
|
||||
accessibilityLabel: PropTypes.node,
|
||||
/**
|
||||
* blurRadius: the blur radius of the blur filter added to the image
|
||||
*/
|
||||
* blurRadius: the blur radius of the blur filter added to the image
|
||||
*
|
||||
* See https://facebook.github.io/react-native/docs/image.html#blurradius
|
||||
*/
|
||||
blurRadius: PropTypes.number,
|
||||
/**
|
||||
* When the image is resized, the corners of the size specified
|
||||
* by `capInsets` will stay a fixed size, but the center content and borders
|
||||
* of the image will be stretched. This is useful for creating resizable
|
||||
* rounded buttons, shadows, and other resizable assets. More info in the
|
||||
* [official Apple documentation](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIImage_Class/index.html#//apple_ref/occ/instm/UIImage/resizableImageWithCapInsets).
|
||||
*
|
||||
* @platform ios
|
||||
* See https://facebook.github.io/react-native/docs/image.html#capinsets
|
||||
*/
|
||||
capInsets: EdgeInsetsPropType,
|
||||
/**
|
||||
* The mechanism that should be used to resize the image when the image's dimensions
|
||||
* differ from the image view's dimensions. Defaults to `auto`.
|
||||
*
|
||||
* - `auto`: Use heuristics to pick between `resize` and `scale`.
|
||||
*
|
||||
* - `resize`: A software operation which changes the encoded image in memory before it
|
||||
* gets decoded. This should be used instead of `scale` when the image is much larger
|
||||
* than the view.
|
||||
*
|
||||
* - `scale`: The image gets drawn downscaled or upscaled. Compared to `resize`, `scale` is
|
||||
* faster (usually hardware accelerated) and produces higher quality images. This
|
||||
* should be used if the image is smaller than the view. It should also be used if the
|
||||
* image is slightly bigger than the view.
|
||||
*
|
||||
* More details about `resize` and `scale` can be found at http://frescolib.org/docs/resizing-rotating.html.
|
||||
*
|
||||
* @platform android
|
||||
* See https://facebook.github.io/react-native/docs/image.html#resizemethod
|
||||
*/
|
||||
resizeMethod: PropTypes.oneOf(['auto', 'resize', 'scale']),
|
||||
/**
|
||||
* Determines how to resize the image when the frame doesn't match the raw
|
||||
* image dimensions.
|
||||
*
|
||||
* - `cover`: Scale the image uniformly (maintain the image's aspect ratio)
|
||||
* so that both dimensions (width and height) of the image will be equal
|
||||
* to or larger than the corresponding dimension of the view (minus padding).
|
||||
*
|
||||
* - `contain`: Scale the image uniformly (maintain the image's aspect ratio)
|
||||
* so that both dimensions (width and height) of the image will be equal to
|
||||
* or less than the corresponding dimension of the view (minus padding).
|
||||
*
|
||||
* - `stretch`: Scale width and height independently, This may change the
|
||||
* aspect ratio of the src.
|
||||
*
|
||||
* - `repeat`: Repeat the image to cover the frame of the view. The
|
||||
* image will keep it's size and aspect ratio. (iOS only)
|
||||
* See https://facebook.github.io/react-native/docs/image.html#resizemode
|
||||
*/
|
||||
resizeMode: PropTypes.oneOf(['cover', 'contain', 'stretch', 'repeat', 'center']),
|
||||
resizeMode: PropTypes.oneOf([
|
||||
'cover',
|
||||
'contain',
|
||||
'stretch',
|
||||
'repeat',
|
||||
'center',
|
||||
]),
|
||||
/**
|
||||
* A unique identifier for this element to be used in UI Automation
|
||||
* testing scripts.
|
||||
*
|
||||
* See https://facebook.github.io/react-native/docs/image.html#testid
|
||||
*/
|
||||
testID: PropTypes.string,
|
||||
/**
|
||||
* Invoked on mount and layout changes with
|
||||
* `{nativeEvent: {layout: {x, y, width, height}}}`.
|
||||
*
|
||||
* See https://facebook.github.io/react-native/docs/image.html#onlayout
|
||||
*/
|
||||
onLayout: PropTypes.func,
|
||||
/**
|
||||
* Invoked on load start.
|
||||
*
|
||||
* e.g., `onLoadStart={(e) => this.setState({loading: true})}`
|
||||
* See https://facebook.github.io/react-native/docs/image.html#onloadstart
|
||||
*/
|
||||
onLoadStart: PropTypes.func,
|
||||
/**
|
||||
* Invoked on download progress with `{nativeEvent: {loaded, total}}`.
|
||||
* @platform ios
|
||||
*
|
||||
* See https://facebook.github.io/react-native/docs/image.html#onprogress
|
||||
*/
|
||||
onProgress: PropTypes.func,
|
||||
/**
|
||||
* Invoked on load error with `{nativeEvent: {error}}`.
|
||||
*
|
||||
* See https://facebook.github.io/react-native/docs/image.html#onerror
|
||||
*/
|
||||
onError: PropTypes.func,
|
||||
/**
|
||||
* Invoked when a partial load of the image is complete. The definition of
|
||||
* what constitutes a "partial load" is loader specific though this is meant
|
||||
* for progressive JPEG loads.
|
||||
* @platform ios
|
||||
* Invoked when a partial load of the image is complete.
|
||||
*
|
||||
* See https://facebook.github.io/react-native/docs/image.html#onpartialload
|
||||
*/
|
||||
onPartialLoad: PropTypes.func,
|
||||
/**
|
||||
* Invoked when load completes successfully.
|
||||
*
|
||||
* See https://facebook.github.io/react-native/docs/image.html#onload
|
||||
*/
|
||||
onLoad: PropTypes.func,
|
||||
/**
|
||||
* Invoked when load either succeeds or fails.
|
||||
*
|
||||
* See https://facebook.github.io/react-native/docs/image.html#onloadend
|
||||
*/
|
||||
onLoadEnd: PropTypes.func,
|
||||
},
|
||||
@@ -288,51 +161,36 @@ const Image = createReactClass({
|
||||
resizeMode: ImageResizeMode,
|
||||
/**
|
||||
* Retrieve the width and height (in pixels) of an image prior to displaying it.
|
||||
* This method can fail if the image cannot be found, or fails to download.
|
||||
*
|
||||
* In order to retrieve the image dimensions, the image may first need to be
|
||||
* loaded or downloaded, after which it will be cached. This means that in
|
||||
* principle you could use this method to preload images, however it is not
|
||||
* optimized for that purpose, and may in future be implemented in a way that
|
||||
* does not fully load/download the image data. A proper, supported way to
|
||||
* preload images will be provided as a separate API.
|
||||
*
|
||||
* Does not work for static image resources.
|
||||
*
|
||||
* @param uri The location of the image.
|
||||
* @param success The function that will be called if the image was successfully found and width
|
||||
* and height retrieved.
|
||||
* @param failure The function that will be called if there was an error, such as failing to
|
||||
* to retrieve the image.
|
||||
*
|
||||
* @returns void
|
||||
*
|
||||
* @platform ios
|
||||
* See https://facebook.github.io/react-native/docs/image.html#getsize
|
||||
*/
|
||||
getSize: function(
|
||||
uri: string,
|
||||
success: (width: number, height: number) => void,
|
||||
failure?: (error: any) => void,
|
||||
) {
|
||||
ImageViewManager.getSize(uri, success, failure || function() {
|
||||
console.warn('Failed to get size for image: ' + uri);
|
||||
});
|
||||
ImageViewManager.getSize(
|
||||
uri,
|
||||
success,
|
||||
failure ||
|
||||
function() {
|
||||
console.warn('Failed to get size for image: ' + uri);
|
||||
},
|
||||
);
|
||||
},
|
||||
/**
|
||||
* Prefetches a remote image for later use by downloading it to the disk
|
||||
* cache
|
||||
* cache.
|
||||
*
|
||||
* @param url The remote location of the image.
|
||||
*
|
||||
* @return The prefetched image.
|
||||
* See https://facebook.github.io/react-native/docs/image.html#prefetch
|
||||
*/
|
||||
prefetch(url: string) {
|
||||
return ImageViewManager.prefetchImage(url);
|
||||
},
|
||||
/**
|
||||
* Resolves an asset reference into an object which has the properties `uri`, `width`,
|
||||
* and `height`. The input may either be a number (opaque type returned by
|
||||
* require('./foo.png')) or an `ImageSource` like { uri: '<http location || file path>' }
|
||||
* Resolves an asset reference into an object.
|
||||
*
|
||||
* See https://facebook.github.io/react-native/docs/image.html#resolveassetsource
|
||||
*/
|
||||
resolveAssetSource: resolveAssetSource,
|
||||
},
|
||||
@@ -345,11 +203,15 @@ const Image = createReactClass({
|
||||
*/
|
||||
viewConfig: {
|
||||
uiViewClassName: 'UIView',
|
||||
validAttributes: ReactNativeViewAttributes.UIView
|
||||
validAttributes: ReactNativeViewAttributes.UIView,
|
||||
},
|
||||
|
||||
render: function() {
|
||||
const source = resolveAssetSource(this.props.source) || { uri: undefined, width: undefined, height: undefined };
|
||||
const source = resolveAssetSource(this.props.source) || {
|
||||
uri: undefined,
|
||||
width: undefined,
|
||||
height: undefined,
|
||||
};
|
||||
|
||||
let sources;
|
||||
let style;
|
||||
@@ -358,7 +220,8 @@ const Image = createReactClass({
|
||||
sources = source;
|
||||
} else {
|
||||
const {width, height, uri} = source;
|
||||
style = flattenStyle([{width, height}, styles.base, this.props.style]) || {};
|
||||
style =
|
||||
flattenStyle([{width, height}, styles.base, this.props.style]) || {};
|
||||
sources = [source];
|
||||
|
||||
if (uri === '') {
|
||||
@@ -366,15 +229,20 @@ const Image = createReactClass({
|
||||
}
|
||||
}
|
||||
|
||||
const resizeMode = this.props.resizeMode || (style || {}).resizeMode || 'cover'; // Workaround for flow bug t7737108
|
||||
const resizeMode =
|
||||
this.props.resizeMode || (style || {}).resizeMode || 'cover'; // Workaround for flow bug t7737108
|
||||
const tintColor = (style || {}).tintColor; // Workaround for flow bug t7737108
|
||||
|
||||
if (this.props.src) {
|
||||
console.warn('The <Image> component requires a `source` property rather than `src`.');
|
||||
console.warn(
|
||||
'The <Image> component requires a `source` property rather than `src`.',
|
||||
);
|
||||
}
|
||||
|
||||
if (this.props.children) {
|
||||
throw new Error('The <Image> component cannot contain children. If you want to render content on top of the image, consider using the <ImageBackground> component or absolute positioning.');
|
||||
throw new Error(
|
||||
'The <Image> component cannot contain children. If you want to render content on top of the image, consider using the <ImageBackground> component or absolute positioning.',
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
*
|
||||
* @providesModule ImageEditor
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
@@ -63,7 +64,7 @@ class ImageEditor {
|
||||
uri: string,
|
||||
cropData: ImageCropData,
|
||||
success: (uri: string) => void,
|
||||
failure: (error: Object) => void
|
||||
failure: (error: Object) => void,
|
||||
) {
|
||||
RCTImageEditingManager.cropImage(uri, cropData, success, failure);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
*
|
||||
* @providesModule ImageResizeMode
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
@@ -38,10 +39,10 @@ var ImageResizeMode = keyMirror({
|
||||
*/
|
||||
stretch: null,
|
||||
/**
|
||||
* center - The image will be scaled down such that it is completely visible,
|
||||
* if bigger than the area of the view.
|
||||
* The image will not be scaled up.
|
||||
*/
|
||||
* center - The image will be scaled down such that it is completely visible,
|
||||
* if bigger than the area of the view.
|
||||
* The image will not be scaled up.
|
||||
*/
|
||||
center: null,
|
||||
|
||||
/**
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
*
|
||||
* @providesModule ImageSource
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
*
|
||||
* @providesModule ImageSourcePropType
|
||||
* @no-flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
*
|
||||
* @providesModule ImageStore
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
@@ -18,7 +19,7 @@ class ImageStore {
|
||||
* Check if the ImageStore contains image data for the specified URI.
|
||||
* @platform ios
|
||||
*/
|
||||
static hasImageForTag(uri: string, callback: (hasImage: bool) => void) {
|
||||
static hasImageForTag(uri: string, callback: (hasImage: boolean) => void) {
|
||||
if (RCTImageStoreManager.hasImageForTag) {
|
||||
RCTImageStoreManager.hasImageForTag(uri, callback);
|
||||
} else {
|
||||
@@ -56,7 +57,7 @@ class ImageStore {
|
||||
static addImageFromBase64(
|
||||
base64ImageData: string,
|
||||
success: (uri: string) => void,
|
||||
failure: (error: any) => void
|
||||
failure: (error: any) => void,
|
||||
) {
|
||||
RCTImageStoreManager.addImageFromBase64(base64ImageData, success, failure);
|
||||
}
|
||||
@@ -75,7 +76,7 @@ class ImageStore {
|
||||
static getBase64ForTag(
|
||||
uri: string,
|
||||
success: (base64ImageData: string) => void,
|
||||
failure: (error: any) => void
|
||||
failure: (error: any) => void,
|
||||
) {
|
||||
RCTImageStoreManager.getBase64ForTag(uri, success, failure);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
*
|
||||
* @providesModule ImageStylePropTypes
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
@@ -51,7 +52,7 @@ var ImageStylePropTypes = {
|
||||
* http://frescolib.org/docs/rounded-corners-and-circles.html
|
||||
*
|
||||
* @platform android
|
||||
*/
|
||||
*/
|
||||
overlayColor: ReactPropTypes.string,
|
||||
|
||||
// Android-Specific styles
|
||||
|
||||
@@ -8,46 +8,62 @@
|
||||
*
|
||||
* @providesModule nativeImageSource
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const Platform = require('Platform');
|
||||
|
||||
type SourceSpec = {
|
||||
ios?: string,
|
||||
android?: string,
|
||||
// TODO: Change `nativeImageSource` to return this type.
|
||||
export type NativeImageSource = {|
|
||||
+deprecated: true,
|
||||
+height: number,
|
||||
+uri: string,
|
||||
+width: number,
|
||||
|};
|
||||
|
||||
type NativeImageSourceSpec = {|
|
||||
+android?: string,
|
||||
+ios?: string,
|
||||
|
||||
// For more details on width and height, see
|
||||
// http://facebook.github.io/react-native/docs/images.html#why-not-automatically-size-everything
|
||||
width: number,
|
||||
height: number,
|
||||
}
|
||||
+height: number,
|
||||
+width: number,
|
||||
|};
|
||||
|
||||
/**
|
||||
* In hybrid apps, use `nativeImageSource` to access images that are already available
|
||||
* on the native side, for example in Xcode Asset Catalogs or Android's drawable folder.
|
||||
* In hybrid apps, use `nativeImageSource` to access images that are already
|
||||
* available on the native side, for example in Xcode Asset Catalogs or
|
||||
* Android's drawable folder.
|
||||
*
|
||||
* However, keep in mind that React Native Packager does not guarantee that the image exists. If
|
||||
* the image is missing you'll get an empty box. When adding new images your app needs to be
|
||||
* recompiled.
|
||||
* However, keep in mind that React Native Packager does not guarantee that the
|
||||
* image exists. If the image is missing you'll get an empty box. When adding
|
||||
* new images your app needs to be recompiled.
|
||||
*
|
||||
* Prefer Static Image Resources system which provides more guarantees, automates measurements and
|
||||
* allows adding new images without rebuilding the native app. For more details visit:
|
||||
* Prefer Static Image Resources system which provides more guarantees,
|
||||
* automates measurements and allows adding new images without rebuilding the
|
||||
* native app. For more details visit:
|
||||
*
|
||||
* http://facebook.github.io/react-native/docs/images.html
|
||||
*
|
||||
*/
|
||||
function nativeImageSource(spec: SourceSpec): Object {
|
||||
const uri = Platform.select(spec);
|
||||
if (!uri) {
|
||||
console.warn(`No image name given for ${Platform.OS}: ${JSON.stringify(spec)}`);
|
||||
function nativeImageSource(spec: NativeImageSourceSpec): Object {
|
||||
let uri = Platform.select(spec);
|
||||
if (uri == null) {
|
||||
console.warn(
|
||||
'nativeImageSource(...): No image name supplied for `%s`:\n%s',
|
||||
Platform.OS,
|
||||
JSON.stringify(spec, null, 2),
|
||||
);
|
||||
uri = '';
|
||||
}
|
||||
|
||||
return {
|
||||
deprecated: true,
|
||||
height: spec.height,
|
||||
uri,
|
||||
width: spec.width,
|
||||
height: spec.height,
|
||||
deprecated: true,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -15,16 +15,15 @@
|
||||
|
||||
const AssetRegistry = require('AssetRegistry');
|
||||
const AssetSourceResolver = require('AssetSourceResolver');
|
||||
const NativeModules = require('NativeModules');
|
||||
|
||||
import type { ResolvedAssetSource } from 'AssetSourceResolver';
|
||||
|
||||
let _customSourceTransformer, _serverURL, _scriptURL;
|
||||
let _sourceCodeScriptURL: ?string;
|
||||
|
||||
function getDevServerURL(): ?string {
|
||||
if (_serverURL === undefined) {
|
||||
var scriptURL = NativeModules.SourceCode.scriptURL;
|
||||
var match = scriptURL && scriptURL.match(/^https?:\/\/.*?\//);
|
||||
const match = _sourceCodeScriptURL && _sourceCodeScriptURL.match(/^https?:\/\/.*?\//);
|
||||
if (match) {
|
||||
// jsBundle was loaded from network
|
||||
_serverURL = match[0];
|
||||
@@ -54,8 +53,7 @@ function _coerceLocalScriptURL(scriptURL: ?string): ?string {
|
||||
|
||||
function getScriptURL(): ?string {
|
||||
if (_scriptURL === undefined) {
|
||||
const scriptURL = NativeModules.SourceCode.scriptURL;
|
||||
_scriptURL = _coerceLocalScriptURL(scriptURL);
|
||||
_scriptURL = _coerceLocalScriptURL(_sourceCodeScriptURL);
|
||||
}
|
||||
return _scriptURL;
|
||||
}
|
||||
@@ -91,6 +89,13 @@ function resolveAssetSource(source: any): ?ResolvedAssetSource {
|
||||
return resolver.defaultAsset();
|
||||
}
|
||||
|
||||
let sourceCode = global.nativeExtensions && global.nativeExtensions.SourceCode;
|
||||
if (!sourceCode) {
|
||||
const NativeModules = require('NativeModules');
|
||||
sourceCode = NativeModules && NativeModules.SourceCode;
|
||||
}
|
||||
_sourceCodeScriptURL = sourceCode.scriptURL;
|
||||
|
||||
module.exports = resolveAssetSource;
|
||||
module.exports.pickScale = AssetSourceResolver.pickScale;
|
||||
module.exports.setCustomSourceTransformer = setCustomSourceTransformer;
|
||||
|
||||
@@ -92,7 +92,7 @@ class Inspector extends React.Component<{
|
||||
hook.off('react-devtools', this.attachToDevtools);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(newProps: Object) {
|
||||
UNSAFE_componentWillReceiveProps(newProps: Object) {
|
||||
this.setState({inspectedViewTag: newProps.inspectedViewTag});
|
||||
}
|
||||
|
||||
@@ -208,7 +208,7 @@ class Inspector extends React.Component<{
|
||||
});
|
||||
}
|
||||
|
||||
setTouchTargetting(val: bool) {
|
||||
setTouchTargeting(val: bool) {
|
||||
Touchable.TOUCH_TARGET_DEBUG = val;
|
||||
this.props.onRequestRerenderApp((inspectedViewTag) => {
|
||||
this.setState({inspectedViewTag});
|
||||
@@ -247,8 +247,8 @@ class Inspector extends React.Component<{
|
||||
hierarchy={this.state.hierarchy}
|
||||
selection={this.state.selection}
|
||||
setSelection={this.setSelection.bind(this)}
|
||||
touchTargetting={Touchable.TOUCH_TARGET_DEBUG}
|
||||
setTouchTargetting={this.setTouchTargetting.bind(this)}
|
||||
touchTargeting={Touchable.TOUCH_TARGET_DEBUG}
|
||||
setTouchTargeting={this.setTouchTargeting.bind(this)}
|
||||
networking={this.state.networking}
|
||||
setNetworking={this.setNetworking.bind(this)}
|
||||
/>
|
||||
|
||||
@@ -82,8 +82,8 @@ class InspectorPanel extends React.Component<$FlowFixMeProps> {
|
||||
onClick={this.props.setNetworking}
|
||||
/>
|
||||
<Button title={'Touchables'}
|
||||
pressed={this.props.touchTargetting}
|
||||
onClick={this.props.setTouchTargetting}
|
||||
pressed={this.props.touchTargeting}
|
||||
onClick={this.props.setTouchTargeting}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
@@ -98,8 +98,8 @@ InspectorPanel.propTypes = {
|
||||
inspected: PropTypes.object,
|
||||
perfing: PropTypes.bool,
|
||||
setPerfing: PropTypes.func,
|
||||
touchTargetting: PropTypes.bool,
|
||||
setTouchTargetting: PropTypes.func,
|
||||
touchTargeting: PropTypes.bool,
|
||||
setTouchTargeting: PropTypes.func,
|
||||
networking: PropTypes.bool,
|
||||
setNetworking: PropTypes.func,
|
||||
};
|
||||
|
||||
+10
-140
@@ -21,128 +21,10 @@ const LinkingManager = Platform.OS === 'android' ?
|
||||
NativeModules.IntentAndroid : NativeModules.LinkingManager;
|
||||
|
||||
/**
|
||||
* <div class="banner-crna-ejected">
|
||||
* <h3>Projects with Native Code Only</h3>
|
||||
* <p>
|
||||
* This section only applies to projects made with <code>react-native init</code>
|
||||
* or to those made with Create React Native App which have since ejected. For
|
||||
* more information about ejecting, please see
|
||||
* the <a href="https://github.com/react-community/create-react-native-app/blob/master/EJECTING.md" target="_blank">guide</a> on
|
||||
* the Create React Native App repository.
|
||||
* </p>
|
||||
* </div>
|
||||
*
|
||||
* `Linking` gives you a general interface to interact with both incoming
|
||||
* and outgoing app links.
|
||||
*
|
||||
* ### Basic Usage
|
||||
*
|
||||
* #### Handling deep links
|
||||
*
|
||||
* If your app was launched from an external url registered to your app you can
|
||||
* access and handle it from any component you want with
|
||||
*
|
||||
* ```
|
||||
* componentDidMount() {
|
||||
* Linking.getInitialURL().then((url) => {
|
||||
* if (url) {
|
||||
* console.log('Initial url is: ' + url);
|
||||
* }
|
||||
* }).catch(err => console.error('An error occurred', err));
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* NOTE: For instructions on how to add support for deep linking on Android,
|
||||
* refer to [Enabling Deep Links for App Content - Add Intent Filters for Your Deep Links](http://developer.android.com/training/app-indexing/deep-linking.html#adding-filters).
|
||||
*
|
||||
* If you wish to receive the intent in an existing instance of MainActivity,
|
||||
* you may set the `launchMode` of MainActivity to `singleTask` in
|
||||
* `AndroidManifest.xml`. See [`<activity>`](http://developer.android.com/guide/topics/manifest/activity-element.html)
|
||||
* documentation for more information.
|
||||
*
|
||||
* ```
|
||||
* <activity
|
||||
* android:name=".MainActivity"
|
||||
* android:launchMode="singleTask">
|
||||
* ```
|
||||
*
|
||||
* NOTE: On iOS, you'll need to link `RCTLinking` to your project by following
|
||||
* the steps described [here](docs/linking-libraries-ios.html#manual-linking).
|
||||
* If you also want to listen to incoming app links during your app's
|
||||
* execution, you'll need to add the following lines to your `*AppDelegate.m`:
|
||||
*
|
||||
* ```
|
||||
* // iOS 9.x or newer
|
||||
* #import <React/RCTLinkingManager.h>
|
||||
*
|
||||
* - (BOOL)application:(UIApplication *)application
|
||||
* openURL:(NSURL *)url
|
||||
* options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options
|
||||
* {
|
||||
* return [RCTLinkingManager application:application openURL:url options:options];
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* If you're targeting iOS 8.x or older, you can use the following code instead:
|
||||
*
|
||||
* ```
|
||||
* // iOS 8.x or older
|
||||
* #import <React/RCTLinkingManager.h>
|
||||
*
|
||||
* - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url
|
||||
* sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
|
||||
* {
|
||||
* return [RCTLinkingManager application:application openURL:url
|
||||
* sourceApplication:sourceApplication annotation:annotation];
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
*
|
||||
* // If your app is using [Universal Links](https://developer.apple.com/library/prerelease/ios/documentation/General/Conceptual/AppSearch/UniversalLinks.html),
|
||||
* you'll need to add the following code as well:
|
||||
*
|
||||
* ```
|
||||
* - (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity
|
||||
* restorationHandler:(void (^)(NSArray * _Nullable))restorationHandler
|
||||
* {
|
||||
* return [RCTLinkingManager application:application
|
||||
* continueUserActivity:userActivity
|
||||
* restorationHandler:restorationHandler];
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* And then on your React component you'll be able to listen to the events on
|
||||
* `Linking` as follows
|
||||
*
|
||||
* ```
|
||||
* componentDidMount() {
|
||||
* Linking.addEventListener('url', this._handleOpenURL);
|
||||
* },
|
||||
* componentWillUnmount() {
|
||||
* Linking.removeEventListener('url', this._handleOpenURL);
|
||||
* },
|
||||
* _handleOpenURL(event) {
|
||||
* console.log(event.url);
|
||||
* }
|
||||
* ```
|
||||
* #### Opening external links
|
||||
*
|
||||
* To start the corresponding activity for a link (web URL, email, contact etc.), call
|
||||
*
|
||||
* ```
|
||||
* Linking.openURL(url).catch(err => console.error('An error occurred', err));
|
||||
* ```
|
||||
*
|
||||
* If you want to check if any installed app can handle a given URL beforehand you can call
|
||||
* ```
|
||||
* Linking.canOpenURL(url).then(supported => {
|
||||
* if (!supported) {
|
||||
* console.log('Can\'t handle url: ' + url);
|
||||
* } else {
|
||||
* return Linking.openURL(url);
|
||||
* }
|
||||
* }).catch(err => console.error('An error occurred', err));
|
||||
* ```
|
||||
*
|
||||
* See https://facebook.github.io/react-native/docs/linking.html
|
||||
*/
|
||||
class Linking extends NativeEventEmitter {
|
||||
|
||||
@@ -153,13 +35,17 @@ class Linking extends NativeEventEmitter {
|
||||
/**
|
||||
* Add a handler to Linking changes by listening to the `url` event type
|
||||
* and providing the handler
|
||||
*
|
||||
* See https://facebook.github.io/react-native/docs/linking.html#addeventlistener
|
||||
*/
|
||||
addEventListener(type: string, handler: Function) {
|
||||
this.addListener(type, handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a handler by passing the `url` event type and the handler
|
||||
* Remove a handler by passing the `url` event type and the handler.
|
||||
*
|
||||
* See https://facebook.github.io/react-native/docs/linking.html#removeeventlistener
|
||||
*/
|
||||
removeEventListener(type: string, handler: Function ) {
|
||||
this.removeListener(type, handler);
|
||||
@@ -168,18 +54,7 @@ class Linking extends NativeEventEmitter {
|
||||
/**
|
||||
* Try to open the given `url` with any of the installed apps.
|
||||
*
|
||||
* You can use other URLs, like a location (e.g. "geo:37.484847,-122.148386" on Android
|
||||
* or "http://maps.apple.com/?ll=37.484847,-122.148386" on iOS), a contact,
|
||||
* or any other URL that can be opened with the installed apps.
|
||||
*
|
||||
* The method returns a `Promise` object. If the user confirms the open dialog or the
|
||||
* url automatically opens, the promise is resolved. If the user cancels the open dialog
|
||||
* or there are no registered applications for the url, the promise is rejected.
|
||||
*
|
||||
* NOTE: This method will fail if the system doesn't know how to open the specified URL.
|
||||
* If you're passing in a non-http(s) URL, it's best to check {@code canOpenURL} first.
|
||||
*
|
||||
* NOTE: For web URLs, the protocol ("http://", "https://") must be set accordingly!
|
||||
* See https://facebook.github.io/react-native/docs/linking.html#openurl
|
||||
*/
|
||||
openURL(url: string): Promise<any> {
|
||||
this._validateURL(url);
|
||||
@@ -189,12 +64,7 @@ class Linking extends NativeEventEmitter {
|
||||
/**
|
||||
* Determine whether or not an installed app can handle a given URL.
|
||||
*
|
||||
* NOTE: For web URLs, the protocol ("http://", "https://") must be set accordingly!
|
||||
*
|
||||
* NOTE: As of iOS 9, your app needs to provide the `LSApplicationQueriesSchemes` key
|
||||
* inside `Info.plist` or canOpenURL will always return false.
|
||||
*
|
||||
* @param URL the URL to open
|
||||
* See https://facebook.github.io/react-native/docs/linking.html#canopenurl
|
||||
*/
|
||||
canOpenURL(url: string): Promise<boolean> {
|
||||
this._validateURL(url);
|
||||
@@ -205,7 +75,7 @@ class Linking extends NativeEventEmitter {
|
||||
* If the app launch was triggered by an app link,
|
||||
* it will give the link url, otherwise it will give `null`
|
||||
*
|
||||
* NOTE: To support deep linking on Android, refer http://developer.android.com/training/app-indexing/deep-linking.html#handling-intents
|
||||
* See https://facebook.github.io/react-native/docs/linking.html#getinitialurl
|
||||
*/
|
||||
getInitialURL(): Promise<?string> {
|
||||
return LinkingManager.getInitialURL();
|
||||
|
||||
@@ -423,11 +423,11 @@ class FlatList<ItemT> extends React.PureComponent<Props<ItemT>, void> {
|
||||
}
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
UNSAFE_componentWillMount() {
|
||||
this._checkProps(this.props);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps: Props<ItemT>) {
|
||||
UNSAFE_componentWillReceiveProps(nextProps: Props<ItemT>) {
|
||||
invariant(
|
||||
nextProps.numColumns === this.props.numColumns,
|
||||
'Changing numColumns on the fly is not supported. Change the key prop on FlatList when ' +
|
||||
|
||||
@@ -354,7 +354,7 @@ var ListView = createReactClass({
|
||||
return this._scrollComponent.getInnerViewNode();
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
UNSAFE_componentWillMount: function() {
|
||||
// this data should never trigger a render pass, so don't put in state
|
||||
this.scrollProperties = {
|
||||
visibleLength: null,
|
||||
@@ -375,7 +375,7 @@ var ListView = createReactClass({
|
||||
});
|
||||
},
|
||||
|
||||
componentWillReceiveProps: function(nextProps: Object) {
|
||||
UNSAFE_componentWillReceiveProps: function(nextProps: Object) {
|
||||
if (
|
||||
this.props.dataSource !== nextProps.dataSource ||
|
||||
this.props.initialListSize !== nextProps.initialListSize
|
||||
|
||||
@@ -135,7 +135,7 @@ class MetroListView extends React.Component<Props, $FlowFixMeState> {
|
||||
}),
|
||||
sectionHeaderData: {},
|
||||
});
|
||||
componentWillReceiveProps(newProps: Props) {
|
||||
UNSAFE_componentWillReceiveProps(newProps: Props) {
|
||||
this.setState(state => this._computeState(newProps, state));
|
||||
}
|
||||
render() {
|
||||
|
||||
@@ -281,7 +281,7 @@ class SectionList<SectionT: SectionBase<any>> extends React.PureComponent<
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells the list an interaction has occured, which should trigger viewability calculations, e.g.
|
||||
* Tells the list an interaction has occurred, which should trigger viewability calculations, e.g.
|
||||
* if `waitForInteractions` is true and the user has not scrolled. This is typically called by
|
||||
* taps on items or by navigation actions.
|
||||
*/
|
||||
|
||||
@@ -125,7 +125,7 @@ function computeWindowedRenderLimits(
|
||||
};
|
||||
}
|
||||
|
||||
// Find the indices that correspond to the items at the render boundaries we're targetting.
|
||||
// Find the indices that correspond to the items at the render boundaries we're targeting.
|
||||
let [overscanFirst, first, last, overscanLast] = elementsThatOverlapOffsets(
|
||||
[overscanBegin, visibleBegin, visibleEnd, overscanEnd],
|
||||
props.getItemCount(props.data),
|
||||
|
||||
@@ -375,6 +375,9 @@ class VirtualizedList extends React.PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
recordInteraction() {
|
||||
this._nestedChildLists.forEach(childList => {
|
||||
childList.ref && childList.ref.recordInteraction();
|
||||
});
|
||||
this._viewabilityTuples.forEach(t => {
|
||||
t.viewabilityHelper.recordInteraction();
|
||||
});
|
||||
@@ -512,6 +515,10 @@ class VirtualizedList extends React.PureComponent<Props, State> {
|
||||
state: null,
|
||||
});
|
||||
|
||||
if (this._hasInteracted) {
|
||||
childList.ref.recordInteraction();
|
||||
}
|
||||
|
||||
return existingChildData && existingChildData.state;
|
||||
};
|
||||
|
||||
@@ -585,22 +592,6 @@ class VirtualizedList extends React.PureComponent<Props, State> {
|
||||
this.state = initialState;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (this.props.initialScrollIndex) {
|
||||
this._initialScrollIndexTimeout = setTimeout(
|
||||
() =>
|
||||
/* $FlowFixMe(>=0.63.0 site=react_native_fb) This comment suppresses
|
||||
* an error found when Flow v0.63 was deployed. To see the error
|
||||
* delete this comment and run Flow. */
|
||||
this.scrollToIndex({
|
||||
animated: false,
|
||||
index: this.props.initialScrollIndex,
|
||||
}),
|
||||
0,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this._isNestedWithSameOrientation()) {
|
||||
this.context.virtualizedList.unregisterAsNestedChild({
|
||||
@@ -618,13 +609,9 @@ class VirtualizedList extends React.PureComponent<Props, State> {
|
||||
tuple.viewabilityHelper.dispose();
|
||||
});
|
||||
this._fillRateHelper.deactivateAndFlush();
|
||||
/* $FlowFixMe(>=0.63.0 site=react_native_fb) This comment suppresses an
|
||||
* error found when Flow v0.63 was deployed. To see the error delete this
|
||||
* comment and run Flow. */
|
||||
clearTimeout(this._initialScrollIndexTimeout);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(newProps: Props) {
|
||||
UNSAFE_componentWillReceiveProps(newProps: Props) {
|
||||
const {data, extraData, getItemCount, maxToRenderPerBatch} = newProps;
|
||||
// first and last could be stale (e.g. if a new, shorter items props is passed in), so we make
|
||||
// sure we're rendering a reasonable range here.
|
||||
@@ -898,6 +885,7 @@ class VirtualizedList extends React.PureComponent<Props, State> {
|
||||
onScrollEndDrag: this._onScrollEndDrag,
|
||||
onMomentumScrollEnd: this._onMomentumScrollEnd,
|
||||
scrollEventThrottle: this.props.scrollEventThrottle, // TODO: Android support
|
||||
invertStickyHeaders: this.props.inverted,
|
||||
stickyHeaderIndices,
|
||||
};
|
||||
if (inversionStyle) {
|
||||
@@ -940,12 +928,13 @@ class VirtualizedList extends React.PureComponent<Props, State> {
|
||||
_frames = {};
|
||||
_footerLength = 0;
|
||||
_hasDataChangedSinceEndReached = true;
|
||||
_hasInteracted = false;
|
||||
_hasMore = false;
|
||||
_hasWarned = {};
|
||||
_highestMeasuredFrameIndex = 0;
|
||||
_headerLength = 0;
|
||||
_indicesToKeys: Map<number, string> = new Map();
|
||||
_initialScrollIndexTimeout = 0;
|
||||
_hasDoneInitialScroll = false;
|
||||
_nestedChildLists: Map<
|
||||
string,
|
||||
{ref: ?VirtualizedList, state: ?ChildListState},
|
||||
@@ -1197,6 +1186,19 @@ class VirtualizedList extends React.PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
_onContentSizeChange = (width: number, height: number) => {
|
||||
if (
|
||||
width > 0 &&
|
||||
height > 0 &&
|
||||
this.props.initialScrollIndex != null &&
|
||||
this.props.initialScrollIndex > 0 &&
|
||||
!this._hasDoneInitialScroll
|
||||
) {
|
||||
this.scrollToIndex({
|
||||
animated: false,
|
||||
index: this.props.initialScrollIndex,
|
||||
});
|
||||
this._hasDoneInitialScroll = true;
|
||||
}
|
||||
if (this.props.onContentSizeChange) {
|
||||
this.props.onContentSizeChange(width, height);
|
||||
}
|
||||
@@ -1333,9 +1335,13 @@ class VirtualizedList extends React.PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
_onScrollBeginDrag = (e): void => {
|
||||
this._nestedChildLists.forEach(childList => {
|
||||
childList.ref && childList.ref._onScrollBeginDrag(e);
|
||||
});
|
||||
this._viewabilityTuples.forEach(tuple => {
|
||||
tuple.viewabilityHelper.recordInteraction();
|
||||
});
|
||||
this._hasInteracted = true;
|
||||
this.props.onScrollBeginDrag && this.props.onScrollBeginDrag(e);
|
||||
};
|
||||
|
||||
|
||||
@@ -361,7 +361,7 @@ class VirtualizedSectionList<SectionT: SectionBase> extends React.PureComponent<
|
||||
this.state = this._computeState(props);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps: Props<SectionT>) {
|
||||
UNSAFE_componentWillReceiveProps(nextProps: Props<SectionT>) {
|
||||
this.setState(this._computeState(nextProps));
|
||||
}
|
||||
|
||||
@@ -446,7 +446,7 @@ class ItemWithSeparator extends React.Component<
|
||||
},
|
||||
};
|
||||
|
||||
componentWillReceiveProps(props: ItemWithSeparatorProps) {
|
||||
UNSAFE_componentWillReceiveProps(props: ItemWithSeparatorProps) {
|
||||
this.setState(state => ({
|
||||
separatorProps: {
|
||||
...this.state.separatorProps,
|
||||
|
||||
@@ -31,6 +31,7 @@ exports[`FlatList renders all the bells and whistles 1`] = `
|
||||
getItemLayout={[Function]}
|
||||
horizontal={false}
|
||||
initialNumToRender={10}
|
||||
invertStickyHeaders={undefined}
|
||||
keyExtractor={[Function]}
|
||||
maxToRenderPerBatch={10}
|
||||
numColumns={2}
|
||||
@@ -148,6 +149,7 @@ exports[`FlatList renders empty list 1`] = `
|
||||
getItemCount={[Function]}
|
||||
horizontal={false}
|
||||
initialNumToRender={10}
|
||||
invertStickyHeaders={undefined}
|
||||
keyExtractor={[Function]}
|
||||
maxToRenderPerBatch={10}
|
||||
numColumns={1}
|
||||
@@ -177,6 +179,7 @@ exports[`FlatList renders null list 1`] = `
|
||||
getItemCount={[Function]}
|
||||
horizontal={false}
|
||||
initialNumToRender={10}
|
||||
invertStickyHeaders={undefined}
|
||||
keyExtractor={[Function]}
|
||||
maxToRenderPerBatch={10}
|
||||
numColumns={1}
|
||||
@@ -218,6 +221,7 @@ exports[`FlatList renders simple list 1`] = `
|
||||
getItemCount={[Function]}
|
||||
horizontal={false}
|
||||
initialNumToRender={10}
|
||||
invertStickyHeaders={undefined}
|
||||
keyExtractor={[Function]}
|
||||
maxToRenderPerBatch={10}
|
||||
numColumns={1}
|
||||
|
||||
@@ -23,6 +23,7 @@ exports[`SectionList rendering empty section headers is fine 1`] = `
|
||||
getItemCount={[Function]}
|
||||
horizontal={false}
|
||||
initialNumToRender={10}
|
||||
invertStickyHeaders={undefined}
|
||||
keyExtractor={[Function]}
|
||||
maxToRenderPerBatch={10}
|
||||
onContentSizeChange={[Function]}
|
||||
@@ -105,6 +106,7 @@ exports[`SectionList renders a footer when there is no data 1`] = `
|
||||
getItemCount={[Function]}
|
||||
horizontal={false}
|
||||
initialNumToRender={10}
|
||||
invertStickyHeaders={undefined}
|
||||
keyExtractor={[Function]}
|
||||
maxToRenderPerBatch={10}
|
||||
onContentSizeChange={[Function]}
|
||||
@@ -173,6 +175,7 @@ exports[`SectionList renders a footer when there is no data and no header 1`] =
|
||||
getItemCount={[Function]}
|
||||
horizontal={false}
|
||||
initialNumToRender={10}
|
||||
invertStickyHeaders={undefined}
|
||||
keyExtractor={[Function]}
|
||||
maxToRenderPerBatch={10}
|
||||
onContentSizeChange={[Function]}
|
||||
@@ -272,6 +275,7 @@ exports[`SectionList renders all the bells and whistles 1`] = `
|
||||
getItemCount={[Function]}
|
||||
horizontal={false}
|
||||
initialNumToRender={Infinity}
|
||||
invertStickyHeaders={undefined}
|
||||
keyExtractor={[Function]}
|
||||
maxToRenderPerBatch={10}
|
||||
onContentSizeChange={[Function]}
|
||||
@@ -512,6 +516,7 @@ exports[`SectionList renders empty list 1`] = `
|
||||
getItemCount={[Function]}
|
||||
horizontal={false}
|
||||
initialNumToRender={10}
|
||||
invertStickyHeaders={undefined}
|
||||
keyExtractor={[Function]}
|
||||
maxToRenderPerBatch={10}
|
||||
onContentSizeChange={[Function]}
|
||||
|
||||
@@ -17,6 +17,7 @@ exports[`VirtualizedList handles nested lists 1`] = `
|
||||
getItemCount={[Function]}
|
||||
horizontal={false}
|
||||
initialNumToRender={10}
|
||||
invertStickyHeaders={undefined}
|
||||
keyExtractor={[Function]}
|
||||
maxToRenderPerBatch={10}
|
||||
onContentSizeChange={[Function]}
|
||||
@@ -53,6 +54,7 @@ exports[`VirtualizedList handles nested lists 1`] = `
|
||||
getItemCount={[Function]}
|
||||
horizontal={false}
|
||||
initialNumToRender={10}
|
||||
invertStickyHeaders={undefined}
|
||||
keyExtractor={[Function]}
|
||||
maxToRenderPerBatch={10}
|
||||
onContentSizeChange={[Function]}
|
||||
@@ -106,6 +108,7 @@ exports[`VirtualizedList handles nested lists 1`] = `
|
||||
getItemCount={[Function]}
|
||||
horizontal={true}
|
||||
initialNumToRender={10}
|
||||
invertStickyHeaders={undefined}
|
||||
keyExtractor={[Function]}
|
||||
maxToRenderPerBatch={10}
|
||||
onContentSizeChange={[Function]}
|
||||
@@ -180,6 +183,7 @@ exports[`VirtualizedList handles separators correctly 1`] = `
|
||||
getItemCount={[Function]}
|
||||
horizontal={false}
|
||||
initialNumToRender={10}
|
||||
invertStickyHeaders={undefined}
|
||||
keyExtractor={[Function]}
|
||||
maxToRenderPerBatch={10}
|
||||
onContentSizeChange={[Function]}
|
||||
@@ -261,6 +265,7 @@ exports[`VirtualizedList handles separators correctly 2`] = `
|
||||
getItemCount={[Function]}
|
||||
horizontal={false}
|
||||
initialNumToRender={10}
|
||||
invertStickyHeaders={undefined}
|
||||
keyExtractor={[Function]}
|
||||
maxToRenderPerBatch={10}
|
||||
onContentSizeChange={[Function]}
|
||||
@@ -342,6 +347,7 @@ exports[`VirtualizedList handles separators correctly 3`] = `
|
||||
getItemCount={[Function]}
|
||||
horizontal={false}
|
||||
initialNumToRender={10}
|
||||
invertStickyHeaders={undefined}
|
||||
keyExtractor={[Function]}
|
||||
maxToRenderPerBatch={10}
|
||||
onContentSizeChange={[Function]}
|
||||
@@ -434,6 +440,7 @@ exports[`VirtualizedList renders all the bells and whistles 1`] = `
|
||||
getItemLayout={[Function]}
|
||||
horizontal={false}
|
||||
initialNumToRender={10}
|
||||
invertStickyHeaders={true}
|
||||
inverted={true}
|
||||
keyExtractor={[Function]}
|
||||
maxToRenderPerBatch={10}
|
||||
@@ -622,6 +629,7 @@ exports[`VirtualizedList renders empty list 1`] = `
|
||||
getItemCount={[Function]}
|
||||
horizontal={false}
|
||||
initialNumToRender={10}
|
||||
invertStickyHeaders={undefined}
|
||||
keyExtractor={[Function]}
|
||||
maxToRenderPerBatch={10}
|
||||
onContentSizeChange={[Function]}
|
||||
@@ -652,6 +660,7 @@ exports[`VirtualizedList renders empty list with empty component 1`] = `
|
||||
getItemCount={[Function]}
|
||||
horizontal={false}
|
||||
initialNumToRender={10}
|
||||
invertStickyHeaders={undefined}
|
||||
keyExtractor={[Function]}
|
||||
maxToRenderPerBatch={10}
|
||||
onContentSizeChange={[Function]}
|
||||
@@ -705,6 +714,7 @@ exports[`VirtualizedList renders list with empty component 1`] = `
|
||||
getItemCount={[Function]}
|
||||
horizontal={false}
|
||||
initialNumToRender={10}
|
||||
invertStickyHeaders={undefined}
|
||||
keyExtractor={[Function]}
|
||||
maxToRenderPerBatch={10}
|
||||
onContentSizeChange={[Function]}
|
||||
@@ -741,6 +751,7 @@ exports[`VirtualizedList renders null list 1`] = `
|
||||
getItemCount={[Function]}
|
||||
horizontal={false}
|
||||
initialNumToRender={10}
|
||||
invertStickyHeaders={undefined}
|
||||
keyExtractor={[Function]}
|
||||
maxToRenderPerBatch={10}
|
||||
onContentSizeChange={[Function]}
|
||||
@@ -780,6 +791,7 @@ exports[`VirtualizedList renders simple list 1`] = `
|
||||
getItemCount={[Function]}
|
||||
horizontal={false}
|
||||
initialNumToRender={10}
|
||||
invertStickyHeaders={undefined}
|
||||
keyExtractor={[Function]}
|
||||
maxToRenderPerBatch={10}
|
||||
onContentSizeChange={[Function]}
|
||||
@@ -838,6 +850,7 @@ exports[`VirtualizedList test getItem functionality where data is not an Array 1
|
||||
getItemCount={[Function]}
|
||||
horizontal={false}
|
||||
initialNumToRender={10}
|
||||
invertStickyHeaders={undefined}
|
||||
keyExtractor={[Function]}
|
||||
maxToRenderPerBatch={10}
|
||||
onContentSizeChange={[Function]}
|
||||
|
||||
+33
-80
@@ -31,58 +31,8 @@ import type EmitterSubscription from 'EmitterSubscription';
|
||||
|
||||
/**
|
||||
* The Modal component is a simple way to present content above an enclosing view.
|
||||
*
|
||||
* _Note: If you need more control over how to present modals over the rest of your app,
|
||||
* then consider using a top-level Navigator._
|
||||
*
|
||||
* ```javascript
|
||||
* import React, { Component } from 'react';
|
||||
* import { Modal, Text, TouchableHighlight, View } from 'react-native';
|
||||
*
|
||||
* class ModalExample extends Component {
|
||||
*
|
||||
* state = {
|
||||
* modalVisible: false,
|
||||
* }
|
||||
*
|
||||
* setModalVisible(visible) {
|
||||
* this.setState({modalVisible: visible});
|
||||
* }
|
||||
*
|
||||
* render() {
|
||||
* return (
|
||||
* <View style={{marginTop: 22}}>
|
||||
* <Modal
|
||||
* animationType="slide"
|
||||
* transparent={false}
|
||||
* visible={this.state.modalVisible}
|
||||
* onRequestClose={() => {alert("Modal has been closed.")}}
|
||||
* >
|
||||
* <View style={{marginTop: 22}}>
|
||||
* <View>
|
||||
* <Text>Hello World!</Text>
|
||||
*
|
||||
* <TouchableHighlight onPress={() => {
|
||||
* this.setModalVisible(!this.state.modalVisible)
|
||||
* }}>
|
||||
* <Text>Hide Modal</Text>
|
||||
* </TouchableHighlight>
|
||||
*
|
||||
* </View>
|
||||
* </View>
|
||||
* </Modal>
|
||||
*
|
||||
* <TouchableHighlight onPress={() => {
|
||||
* this.setModalVisible(true)
|
||||
* }}>
|
||||
* <Text>Show Modal</Text>
|
||||
* </TouchableHighlight>
|
||||
*
|
||||
* </View>
|
||||
* );
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* See https://facebook.github.io/react-native/docs/modal.html
|
||||
*/
|
||||
|
||||
// In order to route onDismiss callbacks, we need to uniquely identifier each
|
||||
@@ -96,50 +46,54 @@ class Modal extends React.Component<Object> {
|
||||
/**
|
||||
* The `animationType` prop controls how the modal animates.
|
||||
*
|
||||
* - `slide` slides in from the bottom
|
||||
* - `fade` fades into view
|
||||
* - `none` appears without an animation
|
||||
*
|
||||
* Default is set to `none`.
|
||||
* See https://facebook.github.io/react-native/docs/modal.html#animationtype
|
||||
*/
|
||||
animationType: PropTypes.oneOf(['none', 'slide', 'fade']),
|
||||
/**
|
||||
* The `presentationStyle` prop controls how the modal appears (generally on larger devices such as iPad or plus-sized iPhones).
|
||||
* See https://developer.apple.com/reference/uikit/uimodalpresentationstyle for details.
|
||||
* @platform ios
|
||||
*
|
||||
* - `fullScreen` covers the screen completely
|
||||
* - `pageSheet` covers portrait-width view centered (only on larger devices)
|
||||
* - `formSheet` covers narrow-width view centered (only on larger devices)
|
||||
* - `overFullScreen` covers the screen completely, but allows transparency
|
||||
*
|
||||
* Default is set to `overFullScreen` or `fullScreen` depending on `transparent` property.
|
||||
* The `presentationStyle` prop controls how the modal appears.
|
||||
*
|
||||
* See https://facebook.github.io/react-native/docs/modal.html#presentationstyle
|
||||
*/
|
||||
presentationStyle: PropTypes.oneOf(['fullScreen', 'pageSheet', 'formSheet', 'overFullScreen']),
|
||||
/**
|
||||
* The `transparent` prop determines whether your modal will fill the entire view. Setting this to `true` will render the modal over a transparent background.
|
||||
* The `transparent` prop determines whether your modal will fill the
|
||||
* entire view.
|
||||
*
|
||||
* See https://facebook.github.io/react-native/docs/modal.html#transparent
|
||||
*/
|
||||
transparent: PropTypes.bool,
|
||||
/**
|
||||
* The `hardwareAccelerated` prop controls whether to force hardware acceleration for the underlying window.
|
||||
* @platform android
|
||||
* The `hardwareAccelerated` prop controls whether to force hardware
|
||||
* acceleration for the underlying window.
|
||||
*
|
||||
* See https://facebook.github.io/react-native/docs/modal.html#hardwareaccelerated
|
||||
*/
|
||||
hardwareAccelerated: PropTypes.bool,
|
||||
/**
|
||||
* The `visible` prop determines whether your modal is visible.
|
||||
*
|
||||
* See https://facebook.github.io/react-native/docs/modal.html#visible
|
||||
*/
|
||||
visible: PropTypes.bool,
|
||||
/**
|
||||
* The `onRequestClose` callback is called when the user taps the hardware back button on Android or the menu button on Apple TV.
|
||||
* The `onRequestClose` callback is called when the user taps the hardware
|
||||
* back button on Android or the menu button on Apple TV.
|
||||
*
|
||||
* See https://facebook.github.io/react-native/docs/modal.html#onrequestclose
|
||||
*/
|
||||
onRequestClose: (Platform.isTVOS || Platform.OS === 'android') ? PropTypes.func.isRequired : PropTypes.func,
|
||||
/**
|
||||
* The `onShow` prop allows passing a function that will be called once the modal has been shown.
|
||||
* The `onShow` prop allows passing a function that will be called once the
|
||||
* modal has been shown.
|
||||
*
|
||||
* See https://facebook.github.io/react-native/docs/modal.html#onshow
|
||||
*/
|
||||
onShow: PropTypes.func,
|
||||
/**
|
||||
* The `onDismiss` prop allows passing a function that will be called once the modal has been dismissed.
|
||||
* @platform ios
|
||||
* The `onDismiss` prop allows passing a function that will be called once
|
||||
* the modal has been dismissed.
|
||||
*
|
||||
* See https://facebook.github.io/react-native/docs/modal.html#ondismiss
|
||||
*/
|
||||
onDismiss: PropTypes.func,
|
||||
animated: deprecatedPropType(
|
||||
@@ -148,15 +102,14 @@ class Modal extends React.Component<Object> {
|
||||
),
|
||||
/**
|
||||
* The `supportedOrientations` prop allows the modal to be rotated to any of the specified orientations.
|
||||
* On iOS, the modal is still restricted by what's specified in your app's Info.plist's UISupportedInterfaceOrientations field.
|
||||
* When using `presentationStyle` of `pageSheet` or `formSheet`, this property will be ignored by iOS.
|
||||
* @platform ios
|
||||
*
|
||||
* See https://facebook.github.io/react-native/docs/modal.html#supportedorientations
|
||||
*/
|
||||
supportedOrientations: PropTypes.arrayOf(PropTypes.oneOf(['portrait', 'portrait-upside-down', 'landscape', 'landscape-left', 'landscape-right'])),
|
||||
/**
|
||||
* The `onOrientationChange` callback is called when the orientation changes while the modal is being displayed.
|
||||
* The orientation provided is only 'portrait' or 'landscape'. This callback is also called on initial render, regardless of the current orientation.
|
||||
* @platform ios
|
||||
*
|
||||
* See https://facebook.github.io/react-native/docs/modal.html#onorientationchange
|
||||
*/
|
||||
onOrientationChange: PropTypes.func,
|
||||
};
|
||||
@@ -198,7 +151,7 @@ class Modal extends React.Component<Object> {
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps: Object) {
|
||||
UNSAFE_componentWillReceiveProps(nextProps: Object) {
|
||||
Modal._confirmProps(nextProps);
|
||||
}
|
||||
|
||||
|
||||
@@ -115,7 +115,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
|
||||
return;
|
||||
}
|
||||
|
||||
// Do a linear remap of the two frames to safegaurd against variable framerates
|
||||
// Do a linear remap of the two frames to safeguard against variable framerates
|
||||
NSNumber *fromFrameValue = _frames[startIndex];
|
||||
NSNumber *toFrameValue = _frames[nextIndex];
|
||||
NSTimeInterval fromInterval = startIndex * RCTSingleFrameInterval;
|
||||
|
||||
+42
-129
@@ -81,131 +81,15 @@ function _isConnected(connection) {
|
||||
const _isConnectedSubscriptions = new Map();
|
||||
|
||||
/**
|
||||
* NetInfo exposes info about online/offline status
|
||||
*
|
||||
* ```
|
||||
* NetInfo.getConnectionInfo().then((connectionInfo) => {
|
||||
* console.log('Initial, type: ' + connectionInfo.type + ', effectiveType: ' + connectionInfo.effectiveType);
|
||||
* });
|
||||
* function handleFirstConnectivityChange(connectionInfo) {
|
||||
* console.log('First change, type: ' + connectionInfo.type + ', effectiveType: ' + connectionInfo.effectiveType);
|
||||
* NetInfo.removeEventListener(
|
||||
* 'connectionChange',
|
||||
* handleFirstConnectivityChange
|
||||
* );
|
||||
* }
|
||||
* NetInfo.addEventListener(
|
||||
* 'connectionChange',
|
||||
* handleFirstConnectivityChange
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* ### ConnectionType enum
|
||||
*
|
||||
* `ConnectionType` describes the type of connection the device is using to communicate with the network.
|
||||
*
|
||||
* Cross platform values for `ConnectionType`:
|
||||
* - `none` - device is offline
|
||||
* - `wifi` - device is online and connected via wifi, or is the iOS simulator
|
||||
* - `cellular` - device is connected via Edge, 3G, WiMax, or LTE
|
||||
* - `unknown` - error case and the network status is unknown
|
||||
*
|
||||
* Android-only values for `ConnectionType`:
|
||||
* - `bluetooth` - device is connected via Bluetooth
|
||||
* - `ethernet` - device is connected via Ethernet
|
||||
* - `wimax` - device is connected via WiMAX
|
||||
*
|
||||
* ### EffectiveConnectionType enum
|
||||
*
|
||||
* Cross platform values for `EffectiveConnectionType`:
|
||||
* - `2g`
|
||||
* - `3g`
|
||||
* - `4g`
|
||||
* - `unknown`
|
||||
*
|
||||
* ### Android
|
||||
*
|
||||
* To request network info, you need to add the following line to your
|
||||
* app's `AndroidManifest.xml`:
|
||||
*
|
||||
* `<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />`
|
||||
*
|
||||
* ### isConnectionExpensive
|
||||
*
|
||||
* Available on Android. Detect if the current active connection is metered or not. A network is
|
||||
* classified as metered when the user is sensitive to heavy data usage on that connection due to
|
||||
* monetary costs, data limitations or battery/performance issues.
|
||||
*
|
||||
* ```
|
||||
* NetInfo.isConnectionExpensive()
|
||||
* .then(isConnectionExpensive => {
|
||||
* console.log('Connection is ' + (isConnectionExpensive ? 'Expensive' : 'Not Expensive'));
|
||||
* })
|
||||
* .catch(error => {
|
||||
* console.error(error);
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* ### isConnected
|
||||
*
|
||||
* Available on all platforms. Asynchronously fetch a boolean to determine
|
||||
* internet connectivity.
|
||||
*
|
||||
* ```
|
||||
* NetInfo.isConnected.fetch().then(isConnected => {
|
||||
* console.log('First, is ' + (isConnected ? 'online' : 'offline'));
|
||||
* });
|
||||
* function handleFirstConnectivityChange(isConnected) {
|
||||
* console.log('Then, is ' + (isConnected ? 'online' : 'offline'));
|
||||
* NetInfo.isConnected.removeEventListener(
|
||||
* 'connectionChange',
|
||||
* handleFirstConnectivityChange
|
||||
* );
|
||||
* }
|
||||
* NetInfo.isConnected.addEventListener(
|
||||
* 'connectionChange',
|
||||
* handleFirstConnectivityChange
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* ### Connectivity Types (deprecated)
|
||||
*
|
||||
* The following connectivity types are deprecated. They're used by the deprecated APIs `fetch` and the `change` event.
|
||||
*
|
||||
* iOS connectivity types (deprecated):
|
||||
* - `none` - device is offline
|
||||
* - `wifi` - device is online and connected via wifi, or is the iOS simulator
|
||||
* - `cell` - device is connected via Edge, 3G, WiMax, or LTE
|
||||
* - `unknown` - error case and the network status is unknown
|
||||
*
|
||||
* Android connectivity types (deprecated).
|
||||
* - `NONE` - device is offline
|
||||
* - `BLUETOOTH` - The Bluetooth data connection.
|
||||
* - `DUMMY` - Dummy data connection.
|
||||
* - `ETHERNET` - The Ethernet data connection.
|
||||
* - `MOBILE` - The Mobile data connection.
|
||||
* - `MOBILE_DUN` - A DUN-specific Mobile data connection.
|
||||
* - `MOBILE_HIPRI` - A High Priority Mobile data connection.
|
||||
* - `MOBILE_MMS` - An MMS-specific Mobile data connection.
|
||||
* - `MOBILE_SUPL` - A SUPL-specific Mobile data connection.
|
||||
* - `VPN` - A virtual network using one or more native bearers. Requires API Level 21
|
||||
* - `WIFI` - The WIFI data connection.
|
||||
* - `WIMAX` - The WiMAX data connection.
|
||||
* - `UNKNOWN` - Unknown data connection.
|
||||
*
|
||||
* The rest of the connectivity types are hidden by the Android API, but can be used if necessary.
|
||||
* NetInfo exposes info about online/offline status.
|
||||
*
|
||||
* See https://facebook.github.io/react-native/docs/netinfo.html
|
||||
*/
|
||||
const NetInfo = {
|
||||
/**
|
||||
* Adds an event handler. Supported events:
|
||||
*
|
||||
* - `connectionChange`: Fires when the network status changes. The argument to the event
|
||||
* handler is an object with keys:
|
||||
* - `type`: A `ConnectionType` (listed above)
|
||||
* - `effectiveType`: An `EffectiveConnectionType` (listed above)
|
||||
* - `change`: This event is deprecated. Listen to `connectionChange` instead. Fires when
|
||||
* the network status changes. The argument to the event handler is one of the deprecated
|
||||
* connectivity types listed above.
|
||||
* Adds an event handler.
|
||||
*
|
||||
* See https://facebook.github.io/react-native/docs/netinfo.html#addeventlistener
|
||||
*/
|
||||
addEventListener(
|
||||
eventName: ChangeEventName,
|
||||
@@ -246,6 +130,8 @@ const NetInfo = {
|
||||
|
||||
/**
|
||||
* Removes the listener for network status changes.
|
||||
*
|
||||
* See https://facebook.github.io/react-native/docs/netinfo.html#removeeventlistener
|
||||
*/
|
||||
removeEventListener(
|
||||
eventName: ChangeEventName,
|
||||
@@ -260,8 +146,37 @@ const NetInfo = {
|
||||
},
|
||||
|
||||
/**
|
||||
* This function is deprecated. Use `getConnectionInfo` instead. Returns a promise that
|
||||
* resolves with one of the deprecated connectivity types listed above.
|
||||
* This function is deprecated. Use `getConnectionInfo` instead.
|
||||
* Returns a promise that resolves with one of the deprecated connectivity
|
||||
* types:
|
||||
*
|
||||
* The following connectivity types are deprecated. They're used by the
|
||||
* deprecated APIs `fetch` and the `change` event.
|
||||
*
|
||||
* iOS connectivity types (deprecated):
|
||||
* - `none` - device is offline
|
||||
* - `wifi` - device is online and connected via wifi, or is the iOS simulator
|
||||
* - `cell` - device is connected via Edge, 3G, WiMax, or LTE
|
||||
* - `unknown` - error case and the network status is unknown
|
||||
*
|
||||
* Android connectivity types (deprecated).
|
||||
* - `NONE` - device is offline
|
||||
* - `BLUETOOTH` - The Bluetooth data connection.
|
||||
* - `DUMMY` - Dummy data connection.
|
||||
* - `ETHERNET` - The Ethernet data connection.
|
||||
* - `MOBILE` - The Mobile data connection.
|
||||
* - `MOBILE_DUN` - A DUN-specific Mobile data connection.
|
||||
* - `MOBILE_HIPRI` - A High Priority Mobile data connection.
|
||||
* - `MOBILE_MMS` - An MMS-specific Mobile data connection.
|
||||
* - `MOBILE_SUPL` - A SUPL-specific Mobile data connection.
|
||||
* - `VPN` - A virtual network using one or more native bearers. Requires
|
||||
* API Level 21
|
||||
* - `WIFI` - The WIFI data connection.
|
||||
* - `WIMAX` - The WiMAX data connection.
|
||||
* - `UNKNOWN` - Unknown data connection.
|
||||
*
|
||||
* The rest of the connectivity types are hidden by the Android API, but can
|
||||
* be used if necessary.
|
||||
*/
|
||||
fetch(): Promise<any> {
|
||||
console.warn('NetInfo.fetch() is deprecated. Use NetInfo.getConnectionInfo() instead.');
|
||||
@@ -269,9 +184,7 @@ const NetInfo = {
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a promise that resolves to an object with `type` and `effectiveType` keys
|
||||
* whose values are a `ConnectionType` and an `EffectiveConnectionType`, (described above),
|
||||
* respectively.
|
||||
* See https://facebook.github.io/react-native/docs/netinfo.html#getconnectioninfo
|
||||
*/
|
||||
getConnectionInfo(): Promise<any> {
|
||||
return RCTNetInfo.getCurrentConnectivity().then(resp => {
|
||||
@@ -285,8 +198,8 @@ const NetInfo = {
|
||||
/**
|
||||
* An object with the same methods as above but the listener receives a
|
||||
* boolean which represents the internet connectivity.
|
||||
* Use this if you are only interested with whether the device has internet
|
||||
* connectivity.
|
||||
*
|
||||
* See https://facebook.github.io/react-native/docs/netinfo.html#isconnected
|
||||
*/
|
||||
isConnected: {
|
||||
addEventListener(
|
||||
|
||||
@@ -10,6 +10,22 @@
|
||||
#import <React/RCTEventEmitter.h>
|
||||
#import <React/RCTNetworkTask.h>
|
||||
|
||||
@protocol RCTNetworkingRequestHandler <NSObject>
|
||||
|
||||
// @lint-ignore FBOBJCUNTYPEDCOLLECTION1
|
||||
- (BOOL)canHandleNetworkingRequest:(NSDictionary *)data;
|
||||
// @lint-ignore FBOBJCUNTYPEDCOLLECTION1
|
||||
- (NSDictionary *)handleNetworkingRequest:(NSDictionary *)data;
|
||||
|
||||
@end
|
||||
|
||||
@protocol RCTNetworkingResponseHandler <NSObject>
|
||||
|
||||
- (BOOL)canHandleNetworkingResponse:(NSString *)responseType;
|
||||
- (id)handleNetworkingResponse:(NSURLResponse *)response data:(NSData *)data;
|
||||
|
||||
@end
|
||||
|
||||
@interface RCTNetworking : RCTEventEmitter
|
||||
|
||||
/**
|
||||
@@ -24,6 +40,14 @@
|
||||
- (RCTNetworkTask *)networkTaskWithRequest:(NSURLRequest *)request
|
||||
completionBlock:(RCTURLRequestCompletionBlock)completionBlock;
|
||||
|
||||
- (void)addRequestHandler:(id<RCTNetworkingRequestHandler>)handler;
|
||||
|
||||
- (void)addResponseHandler:(id<RCTNetworkingResponseHandler>)handler;
|
||||
|
||||
- (void)removeRequestHandler:(id<RCTNetworkingRequestHandler>)handler;
|
||||
|
||||
- (void)removeResponseHandler:(id<RCTNetworkingResponseHandler>)handler;
|
||||
|
||||
@end
|
||||
|
||||
@interface RCTBridge (RCTNetworking)
|
||||
|
||||
@@ -18,6 +18,8 @@ const convertRequestBody = require('convertRequestBody');
|
||||
|
||||
import type {RequestBody} from 'convertRequestBody';
|
||||
|
||||
import type { NativeResponseType } from './XMLHttpRequest';
|
||||
|
||||
class RCTNetworking extends NativeEventEmitter {
|
||||
|
||||
isAvailable: boolean = true;
|
||||
@@ -32,7 +34,7 @@ class RCTNetworking extends NativeEventEmitter {
|
||||
url: string,
|
||||
headers: Object,
|
||||
data: RequestBody,
|
||||
responseType: 'text' | 'base64',
|
||||
responseType: NativeResponseType,
|
||||
incrementalUpdates: boolean,
|
||||
timeout: number,
|
||||
callback: (requestId: number) => any,
|
||||
|
||||
@@ -131,12 +131,20 @@ static NSString *RCTGenerateFormBoundary()
|
||||
NSMutableDictionary<NSNumber *, RCTNetworkTask *> *_tasksByRequestID;
|
||||
std::mutex _handlersLock;
|
||||
NSArray<id<RCTURLRequestHandler>> *_handlers;
|
||||
NSMutableArray<id<RCTNetworkingRequestHandler>> *_requestHandlers;
|
||||
NSMutableArray<id<RCTNetworkingResponseHandler>> *_responseHandlers;
|
||||
}
|
||||
|
||||
@synthesize methodQueue = _methodQueue;
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
- (void)invalidate
|
||||
{
|
||||
_requestHandlers = nil;
|
||||
_responseHandlers = nil;
|
||||
}
|
||||
|
||||
- (NSArray<NSString *> *)supportedEvents
|
||||
{
|
||||
return @[@"didCompleteNetworkResponse",
|
||||
@@ -297,6 +305,8 @@ RCT_EXPORT_MODULE()
|
||||
*
|
||||
* - {"formData": [...]}: list of data payloads that will be combined into a multipart/form-data request
|
||||
*
|
||||
* - {"blob": {...}}: an object representing a blob
|
||||
*
|
||||
* If successful, the callback be called with a result dictionary containing the following (optional) keys:
|
||||
*
|
||||
* - @"body" (NSData): the body of the request
|
||||
@@ -312,6 +322,15 @@ RCT_EXPORT_MODULE()
|
||||
if (!query) {
|
||||
return callback(nil, nil);
|
||||
}
|
||||
for (id<RCTNetworkingRequestHandler> handler in _requestHandlers) {
|
||||
if ([handler canHandleNetworkingRequest:query]) {
|
||||
// @lint-ignore FBOBJCUNTYPEDCOLLECTION1
|
||||
NSDictionary *body = [handler handleNetworkingRequest:query];
|
||||
if (body) {
|
||||
return callback(nil, body);
|
||||
}
|
||||
}
|
||||
}
|
||||
NSData *body = [RCTConvert NSData:query[@"string"]];
|
||||
if (body) {
|
||||
return callback(nil, @{@"body": body});
|
||||
@@ -417,31 +436,40 @@ RCT_EXPORT_MODULE()
|
||||
|
||||
- (void)sendData:(NSData *)data
|
||||
responseType:(NSString *)responseType
|
||||
response:(NSURLResponse *)response
|
||||
forTask:(RCTNetworkTask *)task
|
||||
{
|
||||
RCTAssertThread(_methodQueue, @"sendData: must be called on method queue");
|
||||
|
||||
if (data.length == 0) {
|
||||
return;
|
||||
id responseData = nil;
|
||||
for (id<RCTNetworkingResponseHandler> handler in _responseHandlers) {
|
||||
if ([handler canHandleNetworkingResponse:responseType]) {
|
||||
responseData = [handler handleNetworkingResponse:response data:data];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
NSString *responseString;
|
||||
if ([responseType isEqualToString:@"text"]) {
|
||||
// No carry storage is required here because the entire data has been loaded.
|
||||
responseString = [RCTNetworking decodeTextData:data fromResponse:task.response withCarryData:nil];
|
||||
if (!responseString) {
|
||||
RCTLogWarn(@"Received data was not a string, or was not a recognised encoding.");
|
||||
if (!responseData) {
|
||||
if (data.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ([responseType isEqualToString:@"text"]) {
|
||||
// No carry storage is required here because the entire data has been loaded.
|
||||
responseData = [RCTNetworking decodeTextData:data fromResponse:task.response withCarryData:nil];
|
||||
if (!responseData) {
|
||||
RCTLogWarn(@"Received data was not a string, or was not a recognised encoding.");
|
||||
return;
|
||||
}
|
||||
} else if ([responseType isEqualToString:@"base64"]) {
|
||||
responseData = [data base64EncodedStringWithOptions:0];
|
||||
} else {
|
||||
RCTLogWarn(@"Invalid responseType: %@", responseType);
|
||||
return;
|
||||
}
|
||||
} else if ([responseType isEqualToString:@"base64"]) {
|
||||
responseString = [data base64EncodedStringWithOptions:0];
|
||||
} else {
|
||||
RCTLogWarn(@"Invalid responseType: %@", responseType);
|
||||
return;
|
||||
}
|
||||
|
||||
NSArray<id> *responseJSON = @[task.requestID, responseString];
|
||||
[self sendEventWithName:@"didReceiveNetworkData" body:responseJSON];
|
||||
[self sendEventWithName:@"didReceiveNetworkData" body:@[task.requestID, responseData]];
|
||||
}
|
||||
|
||||
- (void)sendRequest:(NSURLRequest *)request
|
||||
@@ -523,7 +551,10 @@ RCT_EXPORT_MODULE()
|
||||
// Unless we were sending incremental (text) chunks to JS, all along, now
|
||||
// is the time to send the request body to JS.
|
||||
if (!(incrementalUpdates && [responseType isEqualToString:@"text"])) {
|
||||
[strongSelf sendData:data responseType:responseType forTask:task];
|
||||
[strongSelf sendData:data
|
||||
responseType:responseType
|
||||
response:response
|
||||
forTask:task];
|
||||
}
|
||||
NSArray *responseJSON = @[task.requestID,
|
||||
RCTNullIfNil(error.localizedDescription),
|
||||
@@ -553,6 +584,32 @@ RCT_EXPORT_MODULE()
|
||||
|
||||
#pragma mark - Public API
|
||||
|
||||
- (void)addRequestHandler:(id<RCTNetworkingRequestHandler>)handler
|
||||
{
|
||||
if (!_requestHandlers) {
|
||||
_requestHandlers = [NSMutableArray new];
|
||||
}
|
||||
[_requestHandlers addObject:handler];
|
||||
}
|
||||
|
||||
- (void)addResponseHandler:(id<RCTNetworkingResponseHandler>)handler
|
||||
{
|
||||
if (!_responseHandlers) {
|
||||
_responseHandlers = [NSMutableArray new];
|
||||
}
|
||||
[_responseHandlers addObject:handler];
|
||||
}
|
||||
|
||||
- (void)removeRequestHandler:(id<RCTNetworkingRequestHandler>)handler
|
||||
{
|
||||
[_requestHandlers removeObject:handler];
|
||||
}
|
||||
|
||||
- (void)removeResponseHandler:(id<RCTNetworkingResponseHandler>)handler
|
||||
{
|
||||
[_responseHandlers removeObject:handler];
|
||||
}
|
||||
|
||||
- (RCTNetworkTask *)networkTaskWithRequest:(NSURLRequest *)request completionBlock:(RCTURLRequestCompletionBlock)completionBlock
|
||||
{
|
||||
id<RCTURLRequestHandler> handler = [self handlerForRequest:request];
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user