diff --git a/.eslintrc b/.eslintrc index d4739ac63..6867c22f6 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,10 +1,12 @@ { - "parser" : "babel-eslint", + "parser" : "@typescript-eslint/parser", "extends" : [ "standard", - "standard-react" + "standard-react", + "plugin:@typescript-eslint/recommended" ], "plugins": [ + "@typescript-eslint", "react" ], "env" : { @@ -14,12 +16,11 @@ "globals": { "__DEV__": false }, - "parserOptions": { - "ecmaVersion": 6, - "ecmaFeatures": { - "jsx": true + "settings": { + "react": { + "version": "detect" } - }, + }, "rules": { "generator-star-spacing": 0, "indent": [2, 4, { "ignoredNodes": ["JSXAttribute", "JSXSpreadAttribute"], "SwitchCase": 1 }], @@ -33,6 +34,8 @@ "react/jsx-indent-props": [2, 2], "react/jsx-boolean-value": [0, "never"], "react/jsx-curly-spacing": [0, "never"], - "react/jsx-indent": [2, 4] + "react/jsx-indent": [2, 4], + "@typescript-eslint/ban-ts-comment": "off", + "@typescript-eslint/explicit-module-boundary-types": "off" } -} +} \ No newline at end of file diff --git a/.gitignore b/.gitignore index c0a6a7d22..4f35f984b 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,6 @@ node_modules #LOCK package-lock.json -yarn.lock \ No newline at end of file +yarn.lock + +lib \ No newline at end of file diff --git a/.npmignore b/.npmignore index 00db75c8b..c4e81354c 100644 --- a/.npmignore +++ b/.npmignore @@ -1,4 +1,8 @@ -/example -/node_modules +example +node_modules +doc +ISSUE_TEMPLATE.md package-lock.json yarn.lock +.vscode +.eslintrc \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index a6fb97658..88721406e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,7 @@ { - "eslint.enable": true + "eslint.enable": true, + "editor.codeActionsOnSave": { + "source.fixAll": true + }, + "typescript.tsdk": "node_modules/typescript/lib" } \ No newline at end of file diff --git a/example/.eslintrc.js b/example/.eslintrc.js new file mode 100644 index 000000000..40c6dcd05 --- /dev/null +++ b/example/.eslintrc.js @@ -0,0 +1,4 @@ +module.exports = { + root: true, + extends: '@react-native-community', +}; diff --git a/example/.flowconfig b/example/.flowconfig index 4afc766a2..b274ad1d6 100644 --- a/example/.flowconfig +++ b/example/.flowconfig @@ -21,7 +21,7 @@ node_modules/warning/.* [include] [libs] -node_modules/react-native/Libraries/react-native/react-native-interface.js +node_modules/react-native/interface.js node_modules/react-native/flow/ [options] @@ -36,9 +36,8 @@ module.file_ext=.ios.js munge_underscores=true -module.name_mapper='^react-native$' -> '/node_modules/react-native/Libraries/react-native/react-native-implementation' module.name_mapper='^react-native/\(.*\)$' -> '/node_modules/react-native/\1' -module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> '/node_modules/react-native/Libraries/Image/RelativeImageStub' +module.name_mapper='^@?[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> '/node_modules/react-native/Libraries/Image/RelativeImageStub' suppress_type=$FlowIssue suppress_type=$FlowFixMe @@ -57,7 +56,6 @@ untyped-type-import=warn nonstrict-import=warn deprecated-type=warn unsafe-getters-setters=warn -inexact-spread=warn unnecessary-invariant=warn signature-verification-failure=warn deprecated-utility=error @@ -72,4 +70,4 @@ untyped-import untyped-type-import [version] -^0.105.0 +^0.122.0 diff --git a/example/__tests__/index.js b/example/__tests__/App-test.js similarity index 77% rename from example/__tests__/index.js rename to example/__tests__/App-test.js index c433b04e5..178476699 100644 --- a/example/__tests__/index.js +++ b/example/__tests__/App-test.js @@ -4,11 +4,11 @@ import 'react-native'; import React from 'react'; -import Root from '../src/index'; +import App from '../App'; // Note: test renderer must be required after react-native. import renderer from 'react-test-renderer'; it('renders correctly', () => { - renderer.create(); + renderer.create(); }); diff --git a/example/android/.project b/example/android/.project deleted file mode 100644 index 3964dd3f5..000000000 --- a/example/android/.project +++ /dev/null @@ -1,17 +0,0 @@ - - - android - Project android created by Buildship. - - - - - org.eclipse.buildship.core.gradleprojectbuilder - - - - - - org.eclipse.buildship.core.gradleprojectnature - - diff --git a/example/android/.settings/org.eclipse.buildship.core.prefs b/example/android/.settings/org.eclipse.buildship.core.prefs deleted file mode 100644 index e8895216f..000000000 --- a/example/android/.settings/org.eclipse.buildship.core.prefs +++ /dev/null @@ -1,2 +0,0 @@ -connection.project.dir= -eclipse.preferences.version=1 diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index 6ab488dc5..4005f7d38 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -15,10 +15,12 @@ import com.android.build.OutputFile * // the name of the generated asset file containing your JS bundle * bundleAssetName: "index.android.bundle", * - * // the entry file for bundle generation + * // the entry file for bundle generation. If none specified and + * // "index.android.js" exists, it will be used. Otherwise "index.js" is + * // default. Can be overridden with ENTRY_FILE environment variable. * entryFile: "index.android.js", * - * // https://facebook.github.io/react-native/docs/performance#enable-the-ram-format + * // https://reactnative.dev/docs/performance#enable-the-ram-format * bundleCommand: "ram-bundle", * * // whether to bundle JS and assets in debug mode @@ -76,7 +78,6 @@ import com.android.build.OutputFile */ project.ext.react = [ - entryFile: "index.js", enableHermes: false, // clean and rebuild if changing ] @@ -156,12 +157,13 @@ android { } release { // Caution! In production, you need to generate your own keystore file. - // see https://facebook.github.io/react-native/docs/signed-apk-android. + // see https://reactnative.dev/docs/signed-apk-android. signingConfig signingConfigs.debug minifyEnabled enableProguardInReleaseBuilds proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" } } + // applicationVariants are e.g. debug, release applicationVariants.all { variant -> variant.outputs.each { output -> @@ -180,8 +182,24 @@ android { dependencies { implementation fileTree(dir: "libs", include: ["*.jar"]) + //noinspection GradleDynamicVersion implementation "com.facebook.react:react-native:+" // From node_modules + implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0" + + debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") { + exclude group:'com.facebook.fbjni' + } + + debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") { + exclude group:'com.facebook.flipper' + exclude group:'com.squareup.okhttp3', module:'okhttp' + } + + debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}") { + exclude group:'com.facebook.flipper' + } + if (enableHermes) { def hermesPath = "../../node_modules/hermes-engine/android/"; debugImplementation files(hermesPath + "hermes-debug.aar") diff --git a/example/android/app/src/debug/java/com/example/ReactNativeFlipper.java b/example/android/app/src/debug/java/com/example/ReactNativeFlipper.java new file mode 100644 index 000000000..a1b70b8aa --- /dev/null +++ b/example/android/app/src/debug/java/com/example/ReactNativeFlipper.java @@ -0,0 +1,72 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + *

This source code is licensed under the MIT license found in the LICENSE file in the root + * directory of this source tree. + */ +package com.example; + +import android.content.Context; +import com.facebook.flipper.android.AndroidFlipperClient; +import com.facebook.flipper.android.utils.FlipperUtils; +import com.facebook.flipper.core.FlipperClient; +import com.facebook.flipper.plugins.crashreporter.CrashReporterPlugin; +import com.facebook.flipper.plugins.databases.DatabasesFlipperPlugin; +import com.facebook.flipper.plugins.fresco.FrescoFlipperPlugin; +import com.facebook.flipper.plugins.inspector.DescriptorMapping; +import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin; +import com.facebook.flipper.plugins.network.FlipperOkhttpInterceptor; +import com.facebook.flipper.plugins.network.NetworkFlipperPlugin; +import com.facebook.flipper.plugins.react.ReactFlipperPlugin; +import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin; +import com.facebook.react.ReactInstanceManager; +import com.facebook.react.bridge.ReactContext; +import com.facebook.react.modules.network.NetworkingModule; +import okhttp3.OkHttpClient; + +public class ReactNativeFlipper { + public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) { + if (FlipperUtils.shouldEnableFlipper(context)) { + final FlipperClient client = AndroidFlipperClient.getInstance(context); + + client.addPlugin(new InspectorFlipperPlugin(context, DescriptorMapping.withDefaults())); + client.addPlugin(new ReactFlipperPlugin()); + client.addPlugin(new DatabasesFlipperPlugin(context)); + client.addPlugin(new SharedPreferencesFlipperPlugin(context)); + client.addPlugin(CrashReporterPlugin.getInstance()); + + NetworkFlipperPlugin networkFlipperPlugin = new NetworkFlipperPlugin(); + NetworkingModule.setCustomClientBuilder( + new NetworkingModule.CustomClientBuilder() { + @Override + public void apply(OkHttpClient.Builder builder) { + builder.addNetworkInterceptor(new FlipperOkhttpInterceptor(networkFlipperPlugin)); + } + }); + client.addPlugin(networkFlipperPlugin); + client.start(); + + // Fresco Plugin needs to ensure that ImagePipelineFactory is initialized + // Hence we run if after all native modules have been initialized + ReactContext reactContext = reactInstanceManager.getCurrentReactContext(); + if (reactContext == null) { + reactInstanceManager.addReactInstanceEventListener( + new ReactInstanceManager.ReactInstanceEventListener() { + @Override + public void onReactContextInitialized(ReactContext reactContext) { + reactInstanceManager.removeReactInstanceEventListener(this); + reactContext.runOnNativeModulesQueueThread( + new Runnable() { + @Override + public void run() { + client.addPlugin(new FrescoFlipperPlugin()); + } + }); + } + }); + } else { + client.addPlugin(new FrescoFlipperPlugin()); + } + } + } +} diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml index 24fa5b450..6e27a2a31 100644 --- a/example/android/app/src/main/AndroidManifest.xml +++ b/example/android/app/src/main/AndroidManifest.xml @@ -13,7 +13,8 @@ diff --git a/example/android/app/src/main/java/com/example/MainApplication.java b/example/android/app/src/main/java/com/example/MainApplication.java index 567e0a671..fd8ec883d 100644 --- a/example/android/app/src/main/java/com/example/MainApplication.java +++ b/example/android/app/src/main/java/com/example/MainApplication.java @@ -4,6 +4,7 @@ import android.content.Context; import com.facebook.react.PackageList; import com.facebook.react.ReactApplication; +import com.facebook.react.ReactInstanceManager; import com.facebook.react.ReactNativeHost; import com.facebook.react.ReactPackage; import com.facebook.soloader.SoLoader; @@ -43,23 +44,28 @@ public ReactNativeHost getReactNativeHost() { public void onCreate() { super.onCreate(); SoLoader.init(this, /* native exopackage */ false); - initializeFlipper(this); // Remove this line if you don't want Flipper enabled + initializeFlipper(this, getReactNativeHost().getReactInstanceManager()); } /** - * Loads Flipper in React Native templates. + * Loads Flipper in React Native templates. Call this in the onCreate method with something like + * initializeFlipper(this, getReactNativeHost().getReactInstanceManager()); * * @param context + * @param reactInstanceManager */ - private static void initializeFlipper(Context context) { + private static void initializeFlipper( + Context context, ReactInstanceManager reactInstanceManager) { if (BuildConfig.DEBUG) { try { /* We use reflection here to pick up the class that initializes Flipper, since Flipper library is not available in release mode */ - Class aClass = Class.forName("com.facebook.flipper.ReactNativeFlipper"); - aClass.getMethod("initializeFlipper", Context.class).invoke(null, context); + Class aClass = Class.forName("com.example.ReactNativeFlipper"); + aClass + .getMethod("initializeFlipper", Context.class, ReactInstanceManager.class) + .invoke(null, context, reactInstanceManager); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { diff --git a/example/android/build.gradle b/example/android/build.gradle index 28f7ec645..ed5a56842 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -2,18 +2,17 @@ buildscript { ext { - buildToolsVersion = "28.0.3" + buildToolsVersion = "29.0.2" minSdkVersion = 16 - compileSdkVersion = 28 - targetSdkVersion = 28 + compileSdkVersion = 29 + targetSdkVersion = 29 } repositories { google() jcenter() } dependencies { - classpath("com.android.tools.build:gradle:3.4.2") - + classpath("com.android.tools.build:gradle:3.5.3") // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } @@ -33,6 +32,6 @@ allprojects { google() jcenter() - maven { url 'https://jitpack.io' } + maven { url 'https://www.jitpack.io' } } } diff --git a/example/android/gradle.properties b/example/android/gradle.properties index 027ef9db8..04ca0ef29 100644 --- a/example/android/gradle.properties +++ b/example/android/gradle.properties @@ -17,5 +17,12 @@ # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn android.useAndroidX=true +# Automatically convert third-party libraries to use AndroidX android.enableJetifier=true + +# Version of flipper SDK to use with React Native +FLIPPER_VERSION=0.37.0 diff --git a/example/android/gradle/wrapper/gradle-wrapper.jar b/example/android/gradle/wrapper/gradle-wrapper.jar index 5c2d1cf01..f3d88b1c2 100644 Binary files a/example/android/gradle/wrapper/gradle-wrapper.jar and b/example/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties index e0c4de36d..842267020 100644 --- a/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/example/android/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.5-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.2-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/example/android/gradlew b/example/android/gradlew index b0d6d0ab5..2fe81a7d9 100755 --- a/example/android/gradlew +++ b/example/android/gradlew @@ -7,7 +7,7 @@ # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, @@ -125,8 +125,8 @@ if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` JAVACMD=`cygpath --unix "$JAVACMD"` @@ -154,19 +154,19 @@ if $cygwin ; then else eval `echo args$i`="\"$arg\"" fi - i=$((i+1)) + i=`expr $i + 1` done case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi @@ -175,14 +175,9 @@ save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } -APP_ARGS=$(save "$@") +APP_ARGS=`save "$@"` # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" -fi - exec "$JAVACMD" "$@" diff --git a/example/android/gradlew.bat b/example/android/gradlew.bat index 15e1ee37a..62bd9b9cc 100644 --- a/example/android/gradlew.bat +++ b/example/android/gradlew.bat @@ -1,100 +1,103 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem http://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto init - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/example/ios/Podfile b/example/ios/Podfile index a52509089..f74e34522 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -1,45 +1,26 @@ -platform :ios, '9.0' +require_relative '../node_modules/react-native/scripts/react_native_pods' require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules' -target 'example' do - # Pods for example - pod 'FBLazyVector', :path => "../node_modules/react-native/Libraries/FBLazyVector" - pod 'FBReactNativeSpec', :path => "../node_modules/react-native/Libraries/FBReactNativeSpec" - pod 'RCTRequired', :path => "../node_modules/react-native/Libraries/RCTRequired" - pod 'RCTTypeSafety', :path => "../node_modules/react-native/Libraries/TypeSafety" - pod 'React', :path => '../node_modules/react-native/' - pod 'React-Core', :path => '../node_modules/react-native/' - pod 'React-CoreModules', :path => '../node_modules/react-native/React/CoreModules' - pod 'React-Core/DevSupport', :path => '../node_modules/react-native/' - pod 'React-RCTActionSheet', :path => '../node_modules/react-native/Libraries/ActionSheetIOS' - pod 'React-RCTAnimation', :path => '../node_modules/react-native/Libraries/NativeAnimation' - pod 'React-RCTBlob', :path => '../node_modules/react-native/Libraries/Blob' - pod 'React-RCTImage', :path => '../node_modules/react-native/Libraries/Image' - pod 'React-RCTLinking', :path => '../node_modules/react-native/Libraries/LinkingIOS' - pod 'React-RCTNetwork', :path => '../node_modules/react-native/Libraries/Network' - pod 'React-RCTSettings', :path => '../node_modules/react-native/Libraries/Settings' - pod 'React-RCTText', :path => '../node_modules/react-native/Libraries/Text' - pod 'React-RCTVibration', :path => '../node_modules/react-native/Libraries/Vibration' - pod 'React-Core/RCTWebSocket', :path => '../node_modules/react-native/' +platform :ios, '10.0' - pod 'React-cxxreact', :path => '../node_modules/react-native/ReactCommon/cxxreact' - pod 'React-jsi', :path => '../node_modules/react-native/ReactCommon/jsi' - pod 'React-jsiexecutor', :path => '../node_modules/react-native/ReactCommon/jsiexecutor' - pod 'React-jsinspector', :path => '../node_modules/react-native/ReactCommon/jsinspector' - pod 'ReactCommon/jscallinvoker', :path => "../node_modules/react-native/ReactCommon" - pod 'ReactCommon/turbomodule/core', :path => "../node_modules/react-native/ReactCommon" - pod 'Yoga', :path => '../node_modules/react-native/ReactCommon/yoga' +target 'example' do + config = use_native_modules! - pod 'DoubleConversion', :podspec => '../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec' - pod 'glog', :podspec => '../node_modules/react-native/third-party-podspecs/glog.podspec' - pod 'Folly', :podspec => '../node_modules/react-native/third-party-podspecs/Folly.podspec' + use_react_native!(:path => config["reactNativePath"]) target 'exampleTests' do - inherit! :search_paths + inherit! :complete # Pods for testing end - use_native_modules! + # Enables Flipper. + # + # Note that if you have use_frameworks! enabled, Flipper will not work and + # you should disable these next few lines. + use_flipper! + post_install do |installer| + flipper_post_install(installer) + end end target 'example-tvOS' do @@ -49,5 +30,4 @@ target 'example-tvOS' do inherit! :search_paths # Pods for testing end - end diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 828ecd42d..2234009c0 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -2,235 +2,336 @@ PODS: - boost-for-react-native (1.63.0) - BVLinearGradient (2.5.6): - React + - CocoaAsyncSocket (7.6.4) + - CocoaLibEvent (1.0.0) - DoubleConversion (1.1.6) - - FBLazyVector (0.61.5) - - FBReactNativeSpec (0.61.5): - - Folly (= 2018.10.22.00) - - RCTRequired (= 0.61.5) - - RCTTypeSafety (= 0.61.5) - - React-Core (= 0.61.5) - - React-jsi (= 0.61.5) - - ReactCommon/turbomodule/core (= 0.61.5) - - Folly (2018.10.22.00): + - FBLazyVector (0.63.2) + - FBReactNativeSpec (0.63.2): + - Folly (= 2020.01.13.00) + - RCTRequired (= 0.63.2) + - RCTTypeSafety (= 0.63.2) + - React-Core (= 0.63.2) + - React-jsi (= 0.63.2) + - ReactCommon/turbomodule/core (= 0.63.2) + - Flipper (0.41.5): + - Flipper-Folly (~> 2.2) + - Flipper-RSocket (~> 1.1) + - Flipper-DoubleConversion (1.1.7) + - Flipper-Folly (2.2.0): + - boost-for-react-native + - CocoaLibEvent (~> 1.0) + - Flipper-DoubleConversion + - Flipper-Glog + - OpenSSL-Universal (= 1.0.2.19) + - Flipper-Glog (0.3.6) + - Flipper-PeerTalk (0.0.4) + - Flipper-RSocket (1.1.0): + - Flipper-Folly (~> 2.2) + - FlipperKit (0.41.5): + - FlipperKit/Core (= 0.41.5) + - FlipperKit/Core (0.41.5): + - Flipper (~> 0.41.5) + - FlipperKit/CppBridge + - FlipperKit/FBCxxFollyDynamicConvert + - FlipperKit/FBDefines + - FlipperKit/FKPortForwarding + - FlipperKit/CppBridge (0.41.5): + - Flipper (~> 0.41.5) + - FlipperKit/FBCxxFollyDynamicConvert (0.41.5): + - Flipper-Folly (~> 2.2) + - FlipperKit/FBDefines (0.41.5) + - FlipperKit/FKPortForwarding (0.41.5): + - CocoaAsyncSocket (~> 7.6) + - Flipper-PeerTalk (~> 0.0.4) + - FlipperKit/FlipperKitHighlightOverlay (0.41.5) + - FlipperKit/FlipperKitLayoutPlugin (0.41.5): + - FlipperKit/Core + - FlipperKit/FlipperKitHighlightOverlay + - FlipperKit/FlipperKitLayoutTextSearchable + - YogaKit (~> 1.18) + - FlipperKit/FlipperKitLayoutTextSearchable (0.41.5) + - FlipperKit/FlipperKitNetworkPlugin (0.41.5): + - FlipperKit/Core + - FlipperKit/FlipperKitReactPlugin (0.41.5): + - FlipperKit/Core + - FlipperKit/FlipperKitUserDefaultsPlugin (0.41.5): + - FlipperKit/Core + - FlipperKit/SKIOSNetworkPlugin (0.41.5): + - FlipperKit/Core + - FlipperKit/FlipperKitNetworkPlugin + - Folly (2020.01.13.00): - boost-for-react-native - DoubleConversion - - Folly/Default (= 2018.10.22.00) + - Folly/Default (= 2020.01.13.00) - glog - - Folly/Default (2018.10.22.00): + - Folly/Default (2020.01.13.00): - boost-for-react-native - DoubleConversion - glog - glog (0.3.5) - - RCTRequired (0.61.5) - - RCTTypeSafety (0.61.5): - - FBLazyVector (= 0.61.5) - - Folly (= 2018.10.22.00) - - RCTRequired (= 0.61.5) - - React-Core (= 0.61.5) - - React (0.61.5): - - React-Core (= 0.61.5) - - React-Core/DevSupport (= 0.61.5) - - React-Core/RCTWebSocket (= 0.61.5) - - React-RCTActionSheet (= 0.61.5) - - React-RCTAnimation (= 0.61.5) - - React-RCTBlob (= 0.61.5) - - React-RCTImage (= 0.61.5) - - React-RCTLinking (= 0.61.5) - - React-RCTNetwork (= 0.61.5) - - React-RCTSettings (= 0.61.5) - - React-RCTText (= 0.61.5) - - React-RCTVibration (= 0.61.5) - - React-Core (0.61.5): - - Folly (= 2018.10.22.00) + - OpenSSL-Universal (1.0.2.19): + - OpenSSL-Universal/Static (= 1.0.2.19) + - OpenSSL-Universal/Static (1.0.2.19) + - RCTRequired (0.63.2) + - RCTTypeSafety (0.63.2): + - FBLazyVector (= 0.63.2) + - Folly (= 2020.01.13.00) + - RCTRequired (= 0.63.2) + - React-Core (= 0.63.2) + - React (0.63.2): + - React-Core (= 0.63.2) + - React-Core/DevSupport (= 0.63.2) + - React-Core/RCTWebSocket (= 0.63.2) + - React-RCTActionSheet (= 0.63.2) + - React-RCTAnimation (= 0.63.2) + - React-RCTBlob (= 0.63.2) + - React-RCTImage (= 0.63.2) + - React-RCTLinking (= 0.63.2) + - React-RCTNetwork (= 0.63.2) + - React-RCTSettings (= 0.63.2) + - React-RCTText (= 0.63.2) + - React-RCTVibration (= 0.63.2) + - React-callinvoker (0.63.2) + - React-Core (0.63.2): + - Folly (= 2020.01.13.00) - glog - - React-Core/Default (= 0.61.5) - - React-cxxreact (= 0.61.5) - - React-jsi (= 0.61.5) - - React-jsiexecutor (= 0.61.5) + - React-Core/Default (= 0.63.2) + - React-cxxreact (= 0.63.2) + - React-jsi (= 0.63.2) + - React-jsiexecutor (= 0.63.2) - Yoga - - React-Core/CoreModulesHeaders (0.61.5): - - Folly (= 2018.10.22.00) + - React-Core/CoreModulesHeaders (0.63.2): + - Folly (= 2020.01.13.00) - glog - React-Core/Default - - React-cxxreact (= 0.61.5) - - React-jsi (= 0.61.5) - - React-jsiexecutor (= 0.61.5) + - React-cxxreact (= 0.63.2) + - React-jsi (= 0.63.2) + - React-jsiexecutor (= 0.63.2) - Yoga - - React-Core/Default (0.61.5): - - Folly (= 2018.10.22.00) + - React-Core/Default (0.63.2): + - Folly (= 2020.01.13.00) - glog - - React-cxxreact (= 0.61.5) - - React-jsi (= 0.61.5) - - React-jsiexecutor (= 0.61.5) + - React-cxxreact (= 0.63.2) + - React-jsi (= 0.63.2) + - React-jsiexecutor (= 0.63.2) - Yoga - - React-Core/DevSupport (0.61.5): - - Folly (= 2018.10.22.00) + - React-Core/DevSupport (0.63.2): + - Folly (= 2020.01.13.00) - glog - - React-Core/Default (= 0.61.5) - - React-Core/RCTWebSocket (= 0.61.5) - - React-cxxreact (= 0.61.5) - - React-jsi (= 0.61.5) - - React-jsiexecutor (= 0.61.5) - - React-jsinspector (= 0.61.5) + - React-Core/Default (= 0.63.2) + - React-Core/RCTWebSocket (= 0.63.2) + - React-cxxreact (= 0.63.2) + - React-jsi (= 0.63.2) + - React-jsiexecutor (= 0.63.2) + - React-jsinspector (= 0.63.2) - Yoga - - React-Core/RCTActionSheetHeaders (0.61.5): - - Folly (= 2018.10.22.00) + - React-Core/RCTActionSheetHeaders (0.63.2): + - Folly (= 2020.01.13.00) - glog - React-Core/Default - - React-cxxreact (= 0.61.5) - - React-jsi (= 0.61.5) - - React-jsiexecutor (= 0.61.5) + - React-cxxreact (= 0.63.2) + - React-jsi (= 0.63.2) + - React-jsiexecutor (= 0.63.2) - Yoga - - React-Core/RCTAnimationHeaders (0.61.5): - - Folly (= 2018.10.22.00) + - React-Core/RCTAnimationHeaders (0.63.2): + - Folly (= 2020.01.13.00) - glog - React-Core/Default - - React-cxxreact (= 0.61.5) - - React-jsi (= 0.61.5) - - React-jsiexecutor (= 0.61.5) + - React-cxxreact (= 0.63.2) + - React-jsi (= 0.63.2) + - React-jsiexecutor (= 0.63.2) - Yoga - - React-Core/RCTBlobHeaders (0.61.5): - - Folly (= 2018.10.22.00) + - React-Core/RCTBlobHeaders (0.63.2): + - Folly (= 2020.01.13.00) - glog - React-Core/Default - - React-cxxreact (= 0.61.5) - - React-jsi (= 0.61.5) - - React-jsiexecutor (= 0.61.5) + - React-cxxreact (= 0.63.2) + - React-jsi (= 0.63.2) + - React-jsiexecutor (= 0.63.2) - Yoga - - React-Core/RCTImageHeaders (0.61.5): - - Folly (= 2018.10.22.00) + - React-Core/RCTImageHeaders (0.63.2): + - Folly (= 2020.01.13.00) - glog - React-Core/Default - - React-cxxreact (= 0.61.5) - - React-jsi (= 0.61.5) - - React-jsiexecutor (= 0.61.5) + - React-cxxreact (= 0.63.2) + - React-jsi (= 0.63.2) + - React-jsiexecutor (= 0.63.2) - Yoga - - React-Core/RCTLinkingHeaders (0.61.5): - - Folly (= 2018.10.22.00) + - React-Core/RCTLinkingHeaders (0.63.2): + - Folly (= 2020.01.13.00) - glog - React-Core/Default - - React-cxxreact (= 0.61.5) - - React-jsi (= 0.61.5) - - React-jsiexecutor (= 0.61.5) + - React-cxxreact (= 0.63.2) + - React-jsi (= 0.63.2) + - React-jsiexecutor (= 0.63.2) - Yoga - - React-Core/RCTNetworkHeaders (0.61.5): - - Folly (= 2018.10.22.00) + - React-Core/RCTNetworkHeaders (0.63.2): + - Folly (= 2020.01.13.00) - glog - React-Core/Default - - React-cxxreact (= 0.61.5) - - React-jsi (= 0.61.5) - - React-jsiexecutor (= 0.61.5) + - React-cxxreact (= 0.63.2) + - React-jsi (= 0.63.2) + - React-jsiexecutor (= 0.63.2) - Yoga - - React-Core/RCTSettingsHeaders (0.61.5): - - Folly (= 2018.10.22.00) + - React-Core/RCTSettingsHeaders (0.63.2): + - Folly (= 2020.01.13.00) - glog - React-Core/Default - - React-cxxreact (= 0.61.5) - - React-jsi (= 0.61.5) - - React-jsiexecutor (= 0.61.5) + - React-cxxreact (= 0.63.2) + - React-jsi (= 0.63.2) + - React-jsiexecutor (= 0.63.2) - Yoga - - React-Core/RCTTextHeaders (0.61.5): - - Folly (= 2018.10.22.00) + - React-Core/RCTTextHeaders (0.63.2): + - Folly (= 2020.01.13.00) - glog - React-Core/Default - - React-cxxreact (= 0.61.5) - - React-jsi (= 0.61.5) - - React-jsiexecutor (= 0.61.5) + - React-cxxreact (= 0.63.2) + - React-jsi (= 0.63.2) + - React-jsiexecutor (= 0.63.2) - Yoga - - React-Core/RCTVibrationHeaders (0.61.5): - - Folly (= 2018.10.22.00) + - React-Core/RCTVibrationHeaders (0.63.2): + - Folly (= 2020.01.13.00) - glog - React-Core/Default - - React-cxxreact (= 0.61.5) - - React-jsi (= 0.61.5) - - React-jsiexecutor (= 0.61.5) + - React-cxxreact (= 0.63.2) + - React-jsi (= 0.63.2) + - React-jsiexecutor (= 0.63.2) - Yoga - - React-Core/RCTWebSocket (0.61.5): - - Folly (= 2018.10.22.00) + - React-Core/RCTWebSocket (0.63.2): + - Folly (= 2020.01.13.00) - glog - - React-Core/Default (= 0.61.5) - - React-cxxreact (= 0.61.5) - - React-jsi (= 0.61.5) - - React-jsiexecutor (= 0.61.5) + - React-Core/Default (= 0.63.2) + - React-cxxreact (= 0.63.2) + - React-jsi (= 0.63.2) + - React-jsiexecutor (= 0.63.2) - Yoga - - React-CoreModules (0.61.5): - - FBReactNativeSpec (= 0.61.5) - - Folly (= 2018.10.22.00) - - RCTTypeSafety (= 0.61.5) - - React-Core/CoreModulesHeaders (= 0.61.5) - - React-RCTImage (= 0.61.5) - - ReactCommon/turbomodule/core (= 0.61.5) - - React-cxxreact (0.61.5): + - React-CoreModules (0.63.2): + - FBReactNativeSpec (= 0.63.2) + - Folly (= 2020.01.13.00) + - RCTTypeSafety (= 0.63.2) + - React-Core/CoreModulesHeaders (= 0.63.2) + - React-jsi (= 0.63.2) + - React-RCTImage (= 0.63.2) + - ReactCommon/turbomodule/core (= 0.63.2) + - React-cxxreact (0.63.2): - boost-for-react-native (= 1.63.0) - DoubleConversion - - Folly (= 2018.10.22.00) + - Folly (= 2020.01.13.00) - glog - - React-jsinspector (= 0.61.5) - - React-jsi (0.61.5): + - React-callinvoker (= 0.63.2) + - React-jsinspector (= 0.63.2) + - React-jsi (0.63.2): - boost-for-react-native (= 1.63.0) - DoubleConversion - - Folly (= 2018.10.22.00) + - Folly (= 2020.01.13.00) - glog - - React-jsi/Default (= 0.61.5) - - React-jsi/Default (0.61.5): + - React-jsi/Default (= 0.63.2) + - React-jsi/Default (0.63.2): - boost-for-react-native (= 1.63.0) - DoubleConversion - - Folly (= 2018.10.22.00) - - glog - - React-jsiexecutor (0.61.5): - - DoubleConversion - - Folly (= 2018.10.22.00) + - Folly (= 2020.01.13.00) - glog - - React-cxxreact (= 0.61.5) - - React-jsi (= 0.61.5) - - React-jsinspector (0.61.5) - - React-RCTActionSheet (0.61.5): - - React-Core/RCTActionSheetHeaders (= 0.61.5) - - React-RCTAnimation (0.61.5): - - React-Core/RCTAnimationHeaders (= 0.61.5) - - React-RCTBlob (0.61.5): - - React-Core/RCTBlobHeaders (= 0.61.5) - - React-Core/RCTWebSocket (= 0.61.5) - - React-jsi (= 0.61.5) - - React-RCTNetwork (= 0.61.5) - - React-RCTImage (0.61.5): - - React-Core/RCTImageHeaders (= 0.61.5) - - React-RCTNetwork (= 0.61.5) - - React-RCTLinking (0.61.5): - - React-Core/RCTLinkingHeaders (= 0.61.5) - - React-RCTNetwork (0.61.5): - - React-Core/RCTNetworkHeaders (= 0.61.5) - - React-RCTSettings (0.61.5): - - React-Core/RCTSettingsHeaders (= 0.61.5) - - React-RCTText (0.61.5): - - React-Core/RCTTextHeaders (= 0.61.5) - - React-RCTVibration (0.61.5): - - React-Core/RCTVibrationHeaders (= 0.61.5) - - ReactCommon/jscallinvoker (0.61.5): + - React-jsiexecutor (0.63.2): - DoubleConversion - - Folly (= 2018.10.22.00) + - Folly (= 2020.01.13.00) - glog - - React-cxxreact (= 0.61.5) - - ReactCommon/turbomodule/core (0.61.5): + - React-cxxreact (= 0.63.2) + - React-jsi (= 0.63.2) + - React-jsinspector (0.63.2) + - React-RCTActionSheet (0.63.2): + - React-Core/RCTActionSheetHeaders (= 0.63.2) + - React-RCTAnimation (0.63.2): + - FBReactNativeSpec (= 0.63.2) + - Folly (= 2020.01.13.00) + - RCTTypeSafety (= 0.63.2) + - React-Core/RCTAnimationHeaders (= 0.63.2) + - React-jsi (= 0.63.2) + - ReactCommon/turbomodule/core (= 0.63.2) + - React-RCTBlob (0.63.2): + - FBReactNativeSpec (= 0.63.2) + - Folly (= 2020.01.13.00) + - React-Core/RCTBlobHeaders (= 0.63.2) + - React-Core/RCTWebSocket (= 0.63.2) + - React-jsi (= 0.63.2) + - React-RCTNetwork (= 0.63.2) + - ReactCommon/turbomodule/core (= 0.63.2) + - React-RCTImage (0.63.2): + - FBReactNativeSpec (= 0.63.2) + - Folly (= 2020.01.13.00) + - RCTTypeSafety (= 0.63.2) + - React-Core/RCTImageHeaders (= 0.63.2) + - React-jsi (= 0.63.2) + - React-RCTNetwork (= 0.63.2) + - ReactCommon/turbomodule/core (= 0.63.2) + - React-RCTLinking (0.63.2): + - FBReactNativeSpec (= 0.63.2) + - React-Core/RCTLinkingHeaders (= 0.63.2) + - React-jsi (= 0.63.2) + - ReactCommon/turbomodule/core (= 0.63.2) + - React-RCTNetwork (0.63.2): + - FBReactNativeSpec (= 0.63.2) + - Folly (= 2020.01.13.00) + - RCTTypeSafety (= 0.63.2) + - React-Core/RCTNetworkHeaders (= 0.63.2) + - React-jsi (= 0.63.2) + - ReactCommon/turbomodule/core (= 0.63.2) + - React-RCTSettings (0.63.2): + - FBReactNativeSpec (= 0.63.2) + - Folly (= 2020.01.13.00) + - RCTTypeSafety (= 0.63.2) + - React-Core/RCTSettingsHeaders (= 0.63.2) + - React-jsi (= 0.63.2) + - ReactCommon/turbomodule/core (= 0.63.2) + - React-RCTText (0.63.2): + - React-Core/RCTTextHeaders (= 0.63.2) + - React-RCTVibration (0.63.2): + - FBReactNativeSpec (= 0.63.2) + - Folly (= 2020.01.13.00) + - React-Core/RCTVibrationHeaders (= 0.63.2) + - React-jsi (= 0.63.2) + - ReactCommon/turbomodule/core (= 0.63.2) + - ReactCommon/turbomodule/core (0.63.2): - DoubleConversion - - Folly (= 2018.10.22.00) + - Folly (= 2020.01.13.00) - glog - - React-Core (= 0.61.5) - - React-cxxreact (= 0.61.5) - - React-jsi (= 0.61.5) - - ReactCommon/jscallinvoker (= 0.61.5) + - React-callinvoker (= 0.63.2) + - React-Core (= 0.63.2) + - React-cxxreact (= 0.63.2) + - React-jsi (= 0.63.2) - Yoga (1.14.0) + - YogaKit (1.18.1): + - Yoga (~> 1.14) DEPENDENCIES: - BVLinearGradient (from `../node_modules/react-native-linear-gradient`) - DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`) - FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`) - FBReactNativeSpec (from `../node_modules/react-native/Libraries/FBReactNativeSpec`) + - Flipper (~> 0.41.1) + - Flipper-DoubleConversion (= 1.1.7) + - Flipper-Folly (~> 2.2) + - Flipper-Glog (= 0.3.6) + - Flipper-PeerTalk (~> 0.0.4) + - Flipper-RSocket (~> 1.1) + - FlipperKit (~> 0.41.1) + - FlipperKit/Core (~> 0.41.1) + - FlipperKit/CppBridge (~> 0.41.1) + - FlipperKit/FBCxxFollyDynamicConvert (~> 0.41.1) + - FlipperKit/FBDefines (~> 0.41.1) + - FlipperKit/FKPortForwarding (~> 0.41.1) + - FlipperKit/FlipperKitHighlightOverlay (~> 0.41.1) + - FlipperKit/FlipperKitLayoutPlugin (~> 0.41.1) + - FlipperKit/FlipperKitLayoutTextSearchable (~> 0.41.1) + - FlipperKit/FlipperKitNetworkPlugin (~> 0.41.1) + - FlipperKit/FlipperKitReactPlugin (~> 0.41.1) + - FlipperKit/FlipperKitUserDefaultsPlugin (~> 0.41.1) + - FlipperKit/SKIOSNetworkPlugin (~> 0.41.1) - Folly (from `../node_modules/react-native/third-party-podspecs/Folly.podspec`) - glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`) - RCTRequired (from `../node_modules/react-native/Libraries/RCTRequired`) - RCTTypeSafety (from `../node_modules/react-native/Libraries/TypeSafety`) - React (from `../node_modules/react-native/`) + - React-callinvoker (from `../node_modules/react-native/ReactCommon/callinvoker`) - React-Core (from `../node_modules/react-native/`) - React-Core/DevSupport (from `../node_modules/react-native/`) - React-Core/RCTWebSocket (from `../node_modules/react-native/`) @@ -248,13 +349,23 @@ DEPENDENCIES: - React-RCTSettings (from `../node_modules/react-native/Libraries/Settings`) - React-RCTText (from `../node_modules/react-native/Libraries/Text`) - React-RCTVibration (from `../node_modules/react-native/Libraries/Vibration`) - - ReactCommon/jscallinvoker (from `../node_modules/react-native/ReactCommon`) - ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`) - Yoga (from `../node_modules/react-native/ReactCommon/yoga`) SPEC REPOS: trunk: - boost-for-react-native + - CocoaAsyncSocket + - CocoaLibEvent + - Flipper + - Flipper-DoubleConversion + - Flipper-Folly + - Flipper-Glog + - Flipper-PeerTalk + - Flipper-RSocket + - FlipperKit + - OpenSSL-Universal + - YogaKit EXTERNAL SOURCES: BVLinearGradient: @@ -275,6 +386,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/Libraries/TypeSafety" React: :path: "../node_modules/react-native/" + React-callinvoker: + :path: "../node_modules/react-native/ReactCommon/callinvoker" React-Core: :path: "../node_modules/react-native/" React-CoreModules: @@ -313,32 +426,44 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: boost-for-react-native: 39c7adb57c4e60d6c5479dd8623128eb5b3f0f2c BVLinearGradient: e3aad03778a456d77928f594a649e96995f1c872 - DoubleConversion: 5805e889d232975c086db112ece9ed034df7a0b2 - FBLazyVector: aaeaf388755e4f29cd74acbc9e3b8da6d807c37f - FBReactNativeSpec: 118d0d177724c2d67f08a59136eb29ef5943ec75 - Folly: 30e7936e1c45c08d884aa59369ed951a8e68cf51 - glog: 1f3da668190260b06b429bb211bfbee5cd790c28 - RCTRequired: b153add4da6e7dbc44aebf93f3cf4fcae392ddf1 - RCTTypeSafety: 9aa1b91d7f9310fc6eadc3cf95126ffe818af320 - React: b6a59ef847b2b40bb6e0180a97d0ca716969ac78 - React-Core: 688b451f7d616cc1134ac95295b593d1b5158a04 - React-CoreModules: d04f8494c1a328b69ec11db9d1137d667f916dcb - React-cxxreact: d0f7bcafa196ae410e5300736b424455e7fb7ba7 - React-jsi: cb2cd74d7ccf4cffb071a46833613edc79cdf8f7 - React-jsiexecutor: d5525f9ed5f782fdbacb64b9b01a43a9323d2386 - React-jsinspector: fa0ecc501688c3c4c34f28834a76302233e29dc0 - React-RCTActionSheet: 600b4d10e3aea0913b5a92256d2719c0cdd26d76 - React-RCTAnimation: 791a87558389c80908ed06cc5dfc5e7920dfa360 - React-RCTBlob: d89293cc0236d9cb0933d85e430b0bbe81ad1d72 - React-RCTImage: 6b8e8df449eb7c814c99a92d6b52de6fe39dea4e - React-RCTLinking: 121bb231c7503cf9094f4d8461b96a130fabf4a5 - React-RCTNetwork: fb353640aafcee84ca8b78957297bd395f065c9a - React-RCTSettings: 8db258ea2a5efee381fcf7a6d5044e2f8b68b640 - React-RCTText: 9ccc88273e9a3aacff5094d2175a605efa854dbe - React-RCTVibration: a49a1f42bf8f5acf1c3e297097517c6b3af377ad - ReactCommon: 198c7c8d3591f975e5431bec1b0b3b581aa1c5dd - Yoga: f2a7cd4280bfe2cca5a7aed98ba0eb3d1310f18b + CocoaAsyncSocket: 694058e7c0ed05a9e217d1b3c7ded962f4180845 + CocoaLibEvent: 2fab71b8bd46dd33ddb959f7928ec5909f838e3f + DoubleConversion: cde416483dac037923206447da6e1454df403714 + FBLazyVector: 3ef4a7f62e7db01092f9d517d2ebc0d0677c4a37 + FBReactNativeSpec: dc7fa9088f0f2a998503a352b0554d69a4391c5a + Flipper: 33585e2d9810fe5528346be33bcf71b37bb7ae13 + Flipper-DoubleConversion: 38631e41ef4f9b12861c67d17cb5518d06badc41 + Flipper-Folly: c12092ea368353b58e992843a990a3225d4533c3 + Flipper-Glog: 1dfd6abf1e922806c52ceb8701a3599a79a200a6 + Flipper-PeerTalk: 116d8f857dc6ef55c7a5a75ea3ceaafe878aadc9 + Flipper-RSocket: 64e7431a55835eb953b0bf984ef3b90ae9fdddd7 + FlipperKit: bc68102cd4952a258a23c9c1b316c7bec1fecf83 + Folly: b73c3869541e86821df3c387eb0af5f65addfab4 + glog: 40a13f7840415b9a77023fbcae0f1e6f43192af3 + OpenSSL-Universal: 8b48cc0d10c1b2923617dfe5c178aa9ed2689355 + RCTRequired: f13f25e7b12f925f1f6a6a8c69d929a03c0129fe + RCTTypeSafety: 44982c5c8e43ff4141eb519a8ddc88059acd1f3a + React: e1c65dd41cb9db13b99f24608e47dd595f28ca9a + React-callinvoker: 552a6a6bc8b3bb794cf108ad59e5a9e2e3b4fc98 + React-Core: 9d341e725dc9cd2f49e4c49ad1fc4e8776aa2639 + React-CoreModules: 5335e168165da7f7083ce7147768d36d3e292318 + React-cxxreact: d3261ec5f7d11743fbf21e263a34ea51d1f13ebc + React-jsi: 54245e1d5f4b690dec614a73a3795964eeef13a8 + React-jsiexecutor: 8ca588cc921e70590820ce72b8789b02c67cce38 + React-jsinspector: b14e62ebe7a66e9231e9581279909f2fc3db6606 + React-RCTActionSheet: 910163b6b09685a35c4ebbc52b66d1bfbbe39fc5 + React-RCTAnimation: 9a883bbe1e9d2e158d4fb53765ed64c8dc2200c6 + React-RCTBlob: 39cf0ece1927996c4466510e25d2105f67010e13 + React-RCTImage: de355d738727b09ad3692f2a979affbd54b5f378 + React-RCTLinking: 8122f221d395a63364b2c0078ce284214bd04575 + React-RCTNetwork: 8f96c7b49ea6a0f28f98258f347b6ad218bc0830 + React-RCTSettings: 8a49622aff9c1925f5455fa340b6fe4853d64ab6 + React-RCTText: 1b6773e776e4b33f90468c20fe3b16ca3e224bb8 + React-RCTVibration: 4d2e726957f4087449739b595f107c0d4b6c2d2d + ReactCommon: a0a1edbebcac5e91338371b72ffc66aa822792ce + Yoga: 7740b94929bbacbddda59bf115b5317e9a161598 + YogaKit: f782866e155069a2cca2517aafea43200b01fd5a -PODFILE CHECKSUM: 79310af6b976c356911a8a833e9b99c3399fdda3 +PODFILE CHECKSUM: 311cf87a4a33d759b7ec994ec3735e03d4ededbf -COCOAPODS: 1.8.3 +COCOAPODS: 1.9.1 diff --git a/example/ios/example-tvOSTests/Info.plist b/example/ios/example-tvOSTests/Info.plist index 886825ccc..ba72822e8 100644 --- a/example/ios/example-tvOSTests/Info.plist +++ b/example/ios/example-tvOSTests/Info.plist @@ -7,7 +7,7 @@ CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier - org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier) + $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName diff --git a/example/ios/example.xcodeproj/project.pbxproj b/example/ios/example.xcodeproj/project.pbxproj index 9c3b1afc4..87a3710b4 100644 --- a/example/ios/example.xcodeproj/project.pbxproj +++ b/example/ios/example.xcodeproj/project.pbxproj @@ -9,17 +9,17 @@ /* Begin PBXBuildFile section */ 00E356F31AD99517003FC87E /* exampleTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* exampleTests.m */; }; 13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; }; - 13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB11A68108700A75B9A /* LaunchScreen.xib */; }; 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; - 29C871C0741732DA80D4A28B /* libPods-exampleTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4E829CA22C7590836F0BDC89 /* libPods-exampleTests.a */; }; + 14E69C44E603A2DABF87E9C7 /* libPods-example-tvOSTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 8AA817F492F434E8A1702ABF /* libPods-example-tvOSTests.a */; }; 2D02E4BC1E0B4A80006451C7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; }; 2D02E4BD1E0B4A84006451C7 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; 2D02E4BF1E0B4AB3006451C7 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; 2DCD954D1E0B4F2C00145EB5 /* exampleTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* exampleTests.m */; }; - 871938999323E775E1A601AA /* libPods-example-tvOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D7EE427033EF6DE8A18138AF /* libPods-example-tvOS.a */; }; - B4AC6785CB63F44B1476B739 /* libPods-example-tvOSTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D322FB106C50427D6E424A6B /* libPods-example-tvOSTests.a */; }; - F272BF3BCAF6E9F45485CA12 /* libPods-example.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 2A1084C799FFD8DE81B1B05A /* libPods-example.a */; }; + 392E28A89C8849C7123ADF53 /* libPods-example-tvOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 260CE5E4DEF527E7552C4F35 /* libPods-example-tvOS.a */; }; + 6A19D51D1CBD97B286B5C5A2 /* libPods-example.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7BFDCC05221B3C6B417CEEE3 /* libPods-example.a */; }; + 803B24D227A21AA7146EE469 /* libPods-example-exampleTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FFEB7F51062CCC33B9B3EB4A /* libPods-example-exampleTests.a */; }; + 81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -44,29 +44,29 @@ 00E356EE1AD99517003FC87E /* exampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = exampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 00E356F21AD99517003FC87E /* exampleTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = exampleTests.m; sourceTree = ""; }; - 0685F86383145C5CB4CDA68E /* Pods-exampleTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-exampleTests.release.xcconfig"; path = "Target Support Files/Pods-exampleTests/Pods-exampleTests.release.xcconfig"; sourceTree = ""; }; - 11B0F93180450FD943C626DD /* Pods-example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-example.release.xcconfig"; path = "Target Support Files/Pods-example/Pods-example.release.xcconfig"; sourceTree = ""; }; 13B07F961A680F5B00A75B9A /* example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = example/AppDelegate.h; sourceTree = ""; }; 13B07FB01A68108700A75B9A /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AppDelegate.m; path = example/AppDelegate.m; sourceTree = ""; }; - 13B07FB21A68108700A75B9A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = example/Images.xcassets; sourceTree = ""; }; 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = example/Info.plist; sourceTree = ""; }; 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = example/main.m; sourceTree = ""; }; - 2848D7646158208FFC51959F /* Pods-example-tvOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-example-tvOS.release.xcconfig"; path = "Target Support Files/Pods-example-tvOS/Pods-example-tvOS.release.xcconfig"; sourceTree = ""; }; - 2A1084C799FFD8DE81B1B05A /* libPods-example.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-example.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 146009DDA787721A5A10CA2A /* Pods-example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-example.debug.xcconfig"; path = "Target Support Files/Pods-example/Pods-example.debug.xcconfig"; sourceTree = ""; }; + 1A4DADB2ABBF6B72F39CB556 /* Pods-example-tvOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-example-tvOS.release.xcconfig"; path = "Target Support Files/Pods-example-tvOS/Pods-example-tvOS.release.xcconfig"; sourceTree = ""; }; + 260CE5E4DEF527E7552C4F35 /* libPods-example-tvOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-example-tvOS.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 2D02E47B1E0B4A5D006451C7 /* example-tvOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "example-tvOS.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 2D02E4901E0B4A5D006451C7 /* example-tvOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "example-tvOSTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; - 4E829CA22C7590836F0BDC89 /* libPods-exampleTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-exampleTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - 641D414720AFDDEC3EA337EC /* Pods-example-tvOSTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-example-tvOSTests.debug.xcconfig"; path = "Target Support Files/Pods-example-tvOSTests/Pods-example-tvOSTests.debug.xcconfig"; sourceTree = ""; }; - 66247520A490D5DB33BCE5DA /* Pods-example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-example.debug.xcconfig"; path = "Target Support Files/Pods-example/Pods-example.debug.xcconfig"; sourceTree = ""; }; - 81EBEEC75A4C26459B97B278 /* Pods-example-tvOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-example-tvOS.debug.xcconfig"; path = "Target Support Files/Pods-example-tvOS/Pods-example-tvOS.debug.xcconfig"; sourceTree = ""; }; - C67ED4B3CF798134625BA09B /* Pods-example-tvOSTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-example-tvOSTests.release.xcconfig"; path = "Target Support Files/Pods-example-tvOSTests/Pods-example-tvOSTests.release.xcconfig"; sourceTree = ""; }; - C903D82C7E072C46D3D68139 /* Pods-exampleTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-exampleTests.debug.xcconfig"; path = "Target Support Files/Pods-exampleTests/Pods-exampleTests.debug.xcconfig"; sourceTree = ""; }; - D322FB106C50427D6E424A6B /* libPods-example-tvOSTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-example-tvOSTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - D7EE427033EF6DE8A18138AF /* libPods-example-tvOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-example-tvOS.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 603B13E6FC2603CE77193B93 /* Pods-example-tvOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-example-tvOS.debug.xcconfig"; path = "Target Support Files/Pods-example-tvOS/Pods-example-tvOS.debug.xcconfig"; sourceTree = ""; }; + 652DBC55B51414CF52A2D6AB /* Pods-example-exampleTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-example-exampleTests.debug.xcconfig"; path = "Target Support Files/Pods-example-exampleTests/Pods-example-exampleTests.debug.xcconfig"; sourceTree = ""; }; + 69BC6E119CCD13F6743DBE12 /* Pods-example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-example.release.xcconfig"; path = "Target Support Files/Pods-example/Pods-example.release.xcconfig"; sourceTree = ""; }; + 77D6BE4E796D28131E3F4FCC /* Pods-example-tvOSTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-example-tvOSTests.debug.xcconfig"; path = "Target Support Files/Pods-example-tvOSTests/Pods-example-tvOSTests.debug.xcconfig"; sourceTree = ""; }; + 7BFDCC05221B3C6B417CEEE3 /* libPods-example.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-example.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = example/LaunchScreen.storyboard; sourceTree = ""; }; + 8AA817F492F434E8A1702ABF /* libPods-example-tvOSTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-example-tvOSTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + AE5B9C9387CB013D7D50E938 /* Pods-example-exampleTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-example-exampleTests.release.xcconfig"; path = "Target Support Files/Pods-example-exampleTests/Pods-example-exampleTests.release.xcconfig"; sourceTree = ""; }; ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; ED2971642150620600B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS12.0.sdk/System/Library/Frameworks/JavaScriptCore.framework; sourceTree = DEVELOPER_DIR; }; + F9FCEFB452A0FAD1291B6DF4 /* Pods-example-tvOSTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-example-tvOSTests.release.xcconfig"; path = "Target Support Files/Pods-example-tvOSTests/Pods-example-tvOSTests.release.xcconfig"; sourceTree = ""; }; + FFEB7F51062CCC33B9B3EB4A /* libPods-example-exampleTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-example-exampleTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -74,7 +74,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 29C871C0741732DA80D4A28B /* libPods-exampleTests.a in Frameworks */, + 803B24D227A21AA7146EE469 /* libPods-example-exampleTests.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -82,7 +82,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - F272BF3BCAF6E9F45485CA12 /* libPods-example.a in Frameworks */, + 6A19D51D1CBD97B286B5C5A2 /* libPods-example.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -90,7 +90,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 871938999323E775E1A601AA /* libPods-example-tvOS.a in Frameworks */, + 392E28A89C8849C7123ADF53 /* libPods-example-tvOS.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -98,7 +98,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - B4AC6785CB63F44B1476B739 /* libPods-example-tvOSTests.a in Frameworks */, + 14E69C44E603A2DABF87E9C7 /* libPods-example-tvOSTests.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -130,37 +130,21 @@ 13B07FB01A68108700A75B9A /* AppDelegate.m */, 13B07FB51A68108700A75B9A /* Images.xcassets */, 13B07FB61A68108700A75B9A /* Info.plist */, - 13B07FB11A68108700A75B9A /* LaunchScreen.xib */, + 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */, 13B07FB71A68108700A75B9A /* main.m */, ); name = example; sourceTree = ""; }; - 13F9C63D4CAF75CDA2D4380F /* Pods */ = { - isa = PBXGroup; - children = ( - 66247520A490D5DB33BCE5DA /* Pods-example.debug.xcconfig */, - 11B0F93180450FD943C626DD /* Pods-example.release.xcconfig */, - 81EBEEC75A4C26459B97B278 /* Pods-example-tvOS.debug.xcconfig */, - 2848D7646158208FFC51959F /* Pods-example-tvOS.release.xcconfig */, - 641D414720AFDDEC3EA337EC /* Pods-example-tvOSTests.debug.xcconfig */, - C67ED4B3CF798134625BA09B /* Pods-example-tvOSTests.release.xcconfig */, - C903D82C7E072C46D3D68139 /* Pods-exampleTests.debug.xcconfig */, - 0685F86383145C5CB4CDA68E /* Pods-exampleTests.release.xcconfig */, - ); - name = Pods; - path = Pods; - sourceTree = ""; - }; 2D16E6871FA4F8E400B85C8A /* Frameworks */ = { isa = PBXGroup; children = ( ED297162215061F000B7C4FE /* JavaScriptCore.framework */, ED2971642150620600B7C4FE /* JavaScriptCore.framework */, - 2A1084C799FFD8DE81B1B05A /* libPods-example.a */, - D7EE427033EF6DE8A18138AF /* libPods-example-tvOS.a */, - D322FB106C50427D6E424A6B /* libPods-example-tvOSTests.a */, - 4E829CA22C7590836F0BDC89 /* libPods-exampleTests.a */, + 7BFDCC05221B3C6B417CEEE3 /* libPods-example.a */, + FFEB7F51062CCC33B9B3EB4A /* libPods-example-exampleTests.a */, + 260CE5E4DEF527E7552C4F35 /* libPods-example-tvOS.a */, + 8AA817F492F434E8A1702ABF /* libPods-example-tvOSTests.a */, ); name = Frameworks; sourceTree = ""; @@ -180,7 +164,7 @@ 00E356EF1AD99517003FC87E /* exampleTests */, 83CBBA001A601CBA00E9B192 /* Products */, 2D16E6871FA4F8E400B85C8A /* Frameworks */, - 13F9C63D4CAF75CDA2D4380F /* Pods */, + B5A56750867168C9AE8E3E33 /* Pods */, ); indentWidth = 2; sourceTree = ""; @@ -198,6 +182,22 @@ name = Products; sourceTree = ""; }; + B5A56750867168C9AE8E3E33 /* Pods */ = { + isa = PBXGroup; + children = ( + 146009DDA787721A5A10CA2A /* Pods-example.debug.xcconfig */, + 69BC6E119CCD13F6743DBE12 /* Pods-example.release.xcconfig */, + 652DBC55B51414CF52A2D6AB /* Pods-example-exampleTests.debug.xcconfig */, + AE5B9C9387CB013D7D50E938 /* Pods-example-exampleTests.release.xcconfig */, + 603B13E6FC2603CE77193B93 /* Pods-example-tvOS.debug.xcconfig */, + 1A4DADB2ABBF6B72F39CB556 /* Pods-example-tvOS.release.xcconfig */, + 77D6BE4E796D28131E3F4FCC /* Pods-example-tvOSTests.debug.xcconfig */, + F9FCEFB452A0FAD1291B6DF4 /* Pods-example-tvOSTests.release.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -205,10 +205,11 @@ isa = PBXNativeTarget; buildConfigurationList = 00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget "exampleTests" */; buildPhases = ( - 21B1CB7811DC630A464E9976 /* [CP] Check Pods Manifest.lock */, + EC709FF54C99C02A3F981756 /* [CP] Check Pods Manifest.lock */, 00E356EA1AD99517003FC87E /* Sources */, 00E356EB1AD99517003FC87E /* Frameworks */, 00E356EC1AD99517003FC87E /* Resources */, + 8F0889A782D12B135E9B1E45 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -224,12 +225,13 @@ isa = PBXNativeTarget; buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "example" */; buildPhases = ( - 7B3F63962D923B5AC2EBFF6A /* [CP] Check Pods Manifest.lock */, + 76F257DEF09210F3D6EB278E /* [CP] Check Pods Manifest.lock */, FD10A7F022414F080027D42C /* Start Packager */, 13B07F871A680F5B00A75B9A /* Sources */, 13B07F8C1A680F5B00A75B9A /* Frameworks */, 13B07F8E1A680F5B00A75B9A /* Resources */, 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */, + FA858795E09158AE21118DBE /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -244,7 +246,7 @@ isa = PBXNativeTarget; buildConfigurationList = 2D02E4BA1E0B4A5E006451C7 /* Build configuration list for PBXNativeTarget "example-tvOS" */; buildPhases = ( - 71AFCCE0B3908DB29062379D /* [CP] Check Pods Manifest.lock */, + 19FCC5FA91CFB25FB6D5F933 /* [CP] Check Pods Manifest.lock */, FD10A7F122414F3F0027D42C /* Start Packager */, 2D02E4771E0B4A5D006451C7 /* Sources */, 2D02E4781E0B4A5D006451C7 /* Frameworks */, @@ -264,7 +266,7 @@ isa = PBXNativeTarget; buildConfigurationList = 2D02E4BB1E0B4A5E006451C7 /* Build configuration list for PBXNativeTarget "example-tvOSTests" */; buildPhases = ( - 6C1FB4F89E984853D9133472 /* [CP] Check Pods Manifest.lock */, + D7207153D50A8F40E8276DE5 /* [CP] Check Pods Manifest.lock */, 2D02E48C1E0B4A5D006451C7 /* Sources */, 2D02E48D1E0B4A5D006451C7 /* Frameworks */, 2D02E48E1E0B4A5D006451C7 /* Resources */, @@ -285,13 +287,15 @@ 83CBB9F71A601CBA00E9B192 /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0940; - ORGANIZATIONNAME = Facebook; + LastUpgradeCheck = 1130; TargetAttributes = { 00E356ED1AD99517003FC87E = { CreatedOnToolsVersion = 6.2; TestTargetID = 13B07F861A680F5B00A75B9A; }; + 13B07F861A680F5B00A75B9A = { + LastSwiftMigration = 1120; + }; 2D02E47A1E0B4A5D006451C7 = { CreatedOnToolsVersion = 8.2.1; ProvisioningStyle = Automatic; @@ -305,7 +309,7 @@ }; buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "example" */; compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; + developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, @@ -336,8 +340,8 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */, 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */, - 13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -373,7 +377,7 @@ shellPath = /bin/sh; shellScript = "export NODE_BINARY=node\n../node_modules/react-native/scripts/react-native-xcode.sh"; }; - 21B1CB7811DC630A464E9976 /* [CP] Check Pods Manifest.lock */ = { + 19FCC5FA91CFB25FB6D5F933 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -388,7 +392,7 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-exampleTests-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-example-tvOS-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -409,7 +413,7 @@ shellPath = /bin/sh; shellScript = "export NODE_BINARY=node\n../node_modules/react-native/scripts/react-native-xcode.sh"; }; - 6C1FB4F89E984853D9133472 /* [CP] Check Pods Manifest.lock */ = { + 76F257DEF09210F3D6EB278E /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -424,14 +428,32 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-example-tvOSTests-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-example-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 71AFCCE0B3908DB29062379D /* [CP] Check Pods Manifest.lock */ = { + 8F0889A782D12B135E9B1E45 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-example-exampleTests/Pods-example-exampleTests-resources.sh", + "${PODS_CONFIGURATION_BUILD_DIR}/React-Core/AccessibilityResources.bundle", + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AccessibilityResources.bundle", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-example-exampleTests/Pods-example-exampleTests-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + D7207153D50A8F40E8276DE5 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -446,14 +468,14 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-example-tvOS-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-example-tvOSTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 7B3F63962D923B5AC2EBFF6A /* [CP] Check Pods Manifest.lock */ = { + EC709FF54C99C02A3F981756 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -468,13 +490,31 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-example-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-example-exampleTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; + FA858795E09158AE21118DBE /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-resources.sh", + "${PODS_CONFIGURATION_BUILD_DIR}/React-Core/AccessibilityResources.bundle", + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AccessibilityResources.bundle", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; FD10A7F022414F080027D42C /* Start Packager */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -565,22 +605,10 @@ }; /* End PBXTargetDependency section */ -/* Begin PBXVariantGroup section */ - 13B07FB11A68108700A75B9A /* LaunchScreen.xib */ = { - isa = PBXVariantGroup; - children = ( - 13B07FB21A68108700A75B9A /* Base */, - ); - name = LaunchScreen.xib; - path = example; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - /* Begin XCBuildConfiguration section */ 00E356F61AD99517003FC87E /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = C903D82C7E072C46D3D68139 /* Pods-exampleTests.debug.xcconfig */; + baseConfigurationReference = 652DBC55B51414CF52A2D6AB /* Pods-example-exampleTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; GCC_PREPROCESSOR_DEFINITIONS = ( @@ -588,7 +616,7 @@ "$(inherited)", ); INFOPLIST_FILE = exampleTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; OTHER_LDFLAGS = ( "-ObjC", @@ -603,12 +631,12 @@ }; 00E356F71AD99517003FC87E /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 0685F86383145C5CB4CDA68E /* Pods-exampleTests.release.xcconfig */; + baseConfigurationReference = AE5B9C9387CB013D7D50E938 /* Pods-example-exampleTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; COPY_PHASE_STRIP = NO; INFOPLIST_FILE = exampleTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; OTHER_LDFLAGS = ( "-ObjC", @@ -623,11 +651,12 @@ }; 13B07F941A680F5B00A75B9A /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 66247520A490D5DB33BCE5DA /* Pods-example.debug.xcconfig */; + baseConfigurationReference = 146009DDA787721A5A10CA2A /* Pods-example.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = 1; - DEAD_CODE_STRIPPING = NO; + ENABLE_BITCODE = NO; INFOPLIST_FILE = example/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; OTHER_LDFLAGS = ( @@ -637,15 +666,18 @@ ); PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = example; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; }; name = Debug; }; 13B07F951A680F5B00A75B9A /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 11B0F93180450FD943C626DD /* Pods-example.release.xcconfig */; + baseConfigurationReference = 69BC6E119CCD13F6743DBE12 /* Pods-example.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = 1; INFOPLIST_FILE = example/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; @@ -656,13 +688,14 @@ ); PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = example; + SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; }; name = Release; }; 2D02E4971E0B4A5E006451C7 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 81EBEEC75A4C26459B97B278 /* Pods-example-tvOS.debug.xcconfig */; + baseConfigurationReference = 603B13E6FC2603CE77193B93 /* Pods-example-tvOS.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image"; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; @@ -680,17 +713,17 @@ "-ObjC", "-lc++", ); - PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.REACT.example-tvOS"; + PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.example-tvOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; TARGETED_DEVICE_FAMILY = 3; - TVOS_DEPLOYMENT_TARGET = 9.2; + TVOS_DEPLOYMENT_TARGET = 10.0; }; name = Debug; }; 2D02E4981E0B4A5E006451C7 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 2848D7646158208FFC51959F /* Pods-example-tvOS.release.xcconfig */; + baseConfigurationReference = 1A4DADB2ABBF6B72F39CB556 /* Pods-example-tvOS.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image"; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; @@ -708,17 +741,17 @@ "-ObjC", "-lc++", ); - PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.REACT.example-tvOS"; + PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.example-tvOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; TARGETED_DEVICE_FAMILY = 3; - TVOS_DEPLOYMENT_TARGET = 9.2; + TVOS_DEPLOYMENT_TARGET = 10.0; }; name = Release; }; 2D02E4991E0B4A5E006451C7 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 641D414720AFDDEC3EA337EC /* Pods-example-tvOSTests.debug.xcconfig */; + baseConfigurationReference = 77D6BE4E796D28131E3F4FCC /* Pods-example-tvOSTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ANALYZER_NONNULL = YES; @@ -735,7 +768,7 @@ "-ObjC", "-lc++", ); - PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.REACT.example-tvOSTests"; + PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.example-tvOSTests"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/example-tvOS.app/example-tvOS"; @@ -745,7 +778,7 @@ }; 2D02E49A1E0B4A5E006451C7 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = C67ED4B3CF798134625BA09B /* Pods-example-tvOSTests.release.xcconfig */; + baseConfigurationReference = F9FCEFB452A0FAD1291B6DF4 /* Pods-example-tvOSTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ANALYZER_NONNULL = YES; @@ -762,7 +795,7 @@ "-ObjC", "-lc++", ); - PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.REACT.example-tvOSTests"; + PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.example-tvOSTests"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/example-tvOS.app/example-tvOS"; @@ -774,6 +807,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; @@ -816,7 +850,13 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + LD_RUNPATH_SEARCH_PATHS = "/usr/lib/swift $(inherited)"; + LIBRARY_SEARCH_PATHS = ( + "\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"", + "\"$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)\"", + "\"$(inherited)\"", + ); MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -827,6 +867,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; @@ -862,7 +903,13 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + LD_RUNPATH_SEARCH_PATHS = "/usr/lib/swift $(inherited)"; + LIBRARY_SEARCH_PATHS = ( + "\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"", + "\"$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)\"", + "\"$(inherited)\"", + ); MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; diff --git a/example/ios/example.xcodeproj/xcshareddata/xcschemes/example-tvOS.xcscheme b/example/ios/example.xcodeproj/xcshareddata/xcschemes/example-tvOS.xcscheme index dde7377e1..9570230db 100644 --- a/example/ios/example.xcodeproj/xcshareddata/xcschemes/example-tvOS.xcscheme +++ b/example/ios/example.xcodeproj/xcshareddata/xcschemes/example-tvOS.xcscheme @@ -1,25 +1,11 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #import diff --git a/example/ios/example/AppDelegate.m b/example/ios/example/AppDelegate.m index 2e1908cb1..b6e642862 100644 --- a/example/ios/example/AppDelegate.m +++ b/example/ios/example/AppDelegate.m @@ -1,20 +1,36 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - #import "AppDelegate.h" #import #import #import +#ifdef FB_SONARKIT_ENABLED +#import +#import +#import +#import +#import +#import + +static void InitializeFlipper(UIApplication *application) { + FlipperClient *client = [FlipperClient sharedClient]; + SKDescriptorMapper *layoutDescriptorMapper = [[SKDescriptorMapper alloc] initWithDefaults]; + [client addPlugin:[[FlipperKitLayoutPlugin alloc] initWithRootNode:application withDescriptorMapper:layoutDescriptorMapper]]; + [client addPlugin:[[FKUserDefaultsPlugin alloc] initWithSuiteName:nil]]; + [client addPlugin:[FlipperKitReactPlugin new]]; + [client addPlugin:[[FlipperKitNetworkPlugin alloc] initWithNetworkAdapter:[SKIOSNetworkAdapter new]]]; + [client start]; +} +#endif + @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { +#ifdef FB_SONARKIT_ENABLED + InitializeFlipper(application); +#endif + RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions]; RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:@"example" diff --git a/example/ios/example/Base.lproj/LaunchScreen.xib b/example/ios/example/Base.lproj/LaunchScreen.xib deleted file mode 100644 index 9e04807a8..000000000 --- a/example/ios/example/Base.lproj/LaunchScreen.xib +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/example/ios/example/LaunchScreen.storyboard b/example/ios/example/LaunchScreen.storyboard new file mode 100644 index 000000000..e2b4b0638 --- /dev/null +++ b/example/ios/example/LaunchScreen.storyboard @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/example/ios/example/main.m b/example/ios/example/main.m index c316cf816..b1df44b95 100644 --- a/example/ios/example/main.m +++ b/example/ios/example/main.m @@ -1,10 +1,3 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - #import #import "AppDelegate.h" diff --git a/example/ios/exampleTests/exampleTests.m b/example/ios/exampleTests/exampleTests.m index 96b3481b7..9809b80c3 100644 --- a/example/ios/exampleTests/exampleTests.m +++ b/example/ios/exampleTests/exampleTests.m @@ -1,10 +1,3 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - #import #import @@ -59,7 +52,7 @@ - (void)testRendersWelcomeScreen return NO; }]; } - + #ifdef DEBUG RCTSetLogFunction(RCTDefaultLogFunction); #endif diff --git a/example/package.json b/example/package.json index 4c2c070ac..fa2ca03b5 100644 --- a/example/package.json +++ b/example/package.json @@ -1,26 +1,29 @@ { "name": "example", - "version": "0.61.5", + "version": "0.0.1", "private": true, "scripts": { "android": "react-native run-android", "ios": "react-native run-ios", "start": "react-native start", - "test": "jest" + "test": "jest", + "lint": "eslint ." }, "dependencies": { - "react": "16.9.0", - "react-native": "0.61.5", + "react": "16.13.1", + "react-native": "0.63.2", "react-native-linear-gradient": "2.5.6", - "react-native-snap-carousel": "file:../" + "react-native-snap-carousel": "../react-native-snap-carousel-v4.0.0-beta.5.tgz" }, "devDependencies": { - "@babel/core": "^7.6.2", - "@babel/runtime": "^7.6.2", - "babel-jest": "^24.9.0", - "jest": "^24.9.0", - "metro-react-native-babel-preset": "^0.56.0", - "react-test-renderer": "16.9.0" + "@babel/core": "^7.8.4", + "@babel/runtime": "^7.8.4", + "@react-native-community/eslint-config": "^1.1.0", + "babel-jest": "^25.1.0", + "eslint": "^6.5.1", + "jest": "^25.1.0", + "metro-react-native-babel-preset": "^0.59.0", + "react-test-renderer": "16.13.1" }, "jest": { "preset": "react-native" diff --git a/package.json b/package.json index 17f83e2b9..a55a77f1c 100644 --- a/package.json +++ b/package.json @@ -2,11 +2,19 @@ "name": "react-native-snap-carousel", "version": "4.0.0-beta.5", "description": "Swiper/carousel component for React Native with previews, multiple layouts, parallax images, performant handling of huge numbers of items, and more. Compatible with Android & iOS.", - "main": "src/index.js", + "main": "lib/commonjs/index", + "module": "lib/module/index", + "types": "lib/typescript/index.d.ts", + "react-native": "src/index", + "source": "src/index", "repository": { "type": "git", "url": "github.com/archriss/react-native-snap-carousel" }, + "scripts": { + "prepare": "bob build", + "lint": "eslint \"**/*.{js,ts,tsx}\"" + }, "keywords": [ "react", "native", @@ -36,7 +44,7 @@ "author": "Benoit Delmaire (github.com/bd-arc)", "license": "BSD-3-Clause", "dependencies": { - "prop-types": "15.7.2", + "@types/react-addons-shallow-compare": "^0.14.22", "react-addons-shallow-compare": "15.6.2" }, "peerDependencies": { @@ -44,15 +52,37 @@ "react-native": ">=0.58.0" }, "devDependencies": { + "@react-native-community/bob": "0.16.2", + "@types/react": "^16.9.46", + "@types/react-native": "^0.63.4", + "@typescript-eslint/eslint-plugin": "3.8.0", + "@typescript-eslint/parser": "3.8.0", "babel-eslint": "10.1.0", - "eslint": "6.8.0", + "eslint": "7.6.0", "eslint-config-standard": "14.1.1", "eslint-config-standard-react": "9.2.0", - "eslint-plugin-import": "2.20.2", + "eslint-plugin-import": "2.22.0", "eslint-plugin-node": "11.1.0", "eslint-plugin-promise": "4.2.1", - "eslint-plugin-react": "7.19.0", - "eslint-plugin-standard": "4.0.1" + "eslint-plugin-react": "7.20.5", + "eslint-plugin-standard": "4.0.1", + "react": "16.13.1", + "react-native": "0.63.2", + "typescript": "3.9.7" + }, + "eslintIgnore": [ + "node_modules/", + "lib/", + "example" + ], + "@react-native-community/bob": { + "source": "src", + "output": "lib", + "targets": [ + "commonjs", + "module", + "typescript" + ] }, "homepage": "https://github.com/archriss/react-native-snap-carousel", "bugs": { diff --git a/src/carousel/Carousel.js b/src/carousel/Carousel.js deleted file mode 100644 index 5ebdf7df7..000000000 --- a/src/carousel/Carousel.js +++ /dev/null @@ -1,1161 +0,0 @@ -import React, { Component } from 'react'; -import { Animated, FlatList, I18nManager, Platform, ScrollView, View, ViewPropTypes } from 'react-native'; -import PropTypes from 'prop-types'; -import shallowCompare from 'react-addons-shallow-compare'; -import { - defaultScrollInterpolator, stackScrollInterpolator, tinderScrollInterpolator, defaultAnimatedStyles, - shiftAnimatedStyles, stackAnimatedStyles, tinderAnimatedStyles -} from '../utils/animations'; - -// Metro doesn't support dynamic imports - i.e. require() done in the component itself -// But at the same time the following import will fail on Snack... -// TODO: find a way to get React Native's version without having to assume the file path -// import RN_PACKAGE from '../../../react-native/package.json'; - -const IS_ANDROID = Platform.OS === 'android'; - -// Native driver for scroll events -// See: https://facebook.github.io/react-native/blog/2017/02/14/using-native-driver-for-animated.html -const AnimatedFlatList = FlatList ? Animated.createAnimatedComponent(FlatList) : null; -const AnimatedScrollView = Animated.createAnimatedComponent(ScrollView); - -// React Native automatically handles RTL layouts; unfortunately, it's buggy with horizontal ScrollView -// See https://github.com/facebook/react-native/issues/11960 -// NOTE: the following variable is not declared in the constructor -// otherwise it is undefined at init, which messes with custom indexes -const IS_RTL = I18nManager.isRTL; - -export default class Carousel extends Component { - - static propTypes = { - data: PropTypes.array.isRequired, - renderItem: PropTypes.func.isRequired, - itemWidth: PropTypes.number, // required for horizontal carousel - itemHeight: PropTypes.number, // required for vertical carousel - sliderWidth: PropTypes.number, // required for horizontal carousel - sliderHeight: PropTypes.number, // required for vertical carousel - activeSlideAlignment: PropTypes.oneOf(['center', 'end', 'start']), - activeSlideOffset: PropTypes.number, - apparitionDelay: PropTypes.number, - autoplay: PropTypes.bool, - autoplayDelay: PropTypes.number, - autoplayInterval: PropTypes.number, - callbackOffsetMargin: PropTypes.number, - containerCustomStyle: ViewPropTypes ? ViewPropTypes.style : View.propTypes.style, - contentContainerCustomStyle: ViewPropTypes ? ViewPropTypes.style : View.propTypes.style, - enableSnap: PropTypes.bool, - firstItem: PropTypes.number, - hasParallaxImages: PropTypes.bool, - inactiveSlideOpacity: PropTypes.number, - inactiveSlideScale: PropTypes.number, - inactiveSlideShift: PropTypes.number, - layout: PropTypes.oneOf(['default', 'stack', 'tinder']), - layoutCardOffset: PropTypes.number, - loop: PropTypes.bool, - loopClonesPerSide: PropTypes.number, - scrollEnabled: PropTypes.bool, - scrollInterpolator: PropTypes.func, - slideInterpolatedStyle: PropTypes.func, - slideStyle: ViewPropTypes ? ViewPropTypes.style : View.propTypes.style, - shouldOptimizeUpdates: PropTypes.bool, - useExperimentalSnap: PropTypes.bool, - useScrollView: PropTypes.oneOfType([PropTypes.bool, PropTypes.func]), - vertical: PropTypes.bool, - onScrollIndexChanged: PropTypes.func, - onSnapToItem: PropTypes.func - }; - - static defaultProps = { - activeSlideAlignment: 'center', - activeSlideOffset: 20, - apparitionDelay: 0, - autoplay: false, - autoplayDelay: 1000, - autoplayInterval: 3000, - callbackOffsetMargin: 5, - containerCustomStyle: {}, - contentContainerCustomStyle: {}, - enableSnap: true, - firstItem: 0, - hasParallaxImages: false, - inactiveSlideOpacity: 0.7, - inactiveSlideScale: 0.9, - inactiveSlideShift: 0, - layout: 'default', - loop: false, - loopClonesPerSide: 3, - scrollEnabled: true, - slideStyle: {}, - shouldOptimizeUpdates: true, - useExperimentalSnap: false, - useScrollView: !AnimatedFlatList, - vertical: false - } - - constructor (props) { - super(props); - - this.state = { - hideCarousel: !!props.apparitionDelay, - interpolators: [] - }; - - // this._RNVersionCode = this._getRNVersionCode(); - - // The following values are not stored in the state because 'setState()' is asynchronous - // and this results in an absolutely crappy behavior on Android while swiping (see #156) - const initialActiveItem = this._getFirstItem(props.firstItem); - this._activeItem = initialActiveItem; - this._onScrollActiveItem = initialActiveItem; - this._previousFirstItem = initialActiveItem; - this._previousItemsLength = initialActiveItem; - - this._mounted = false; - this._positions = []; - this._currentScrollOffset = 0; // Store ScrollView's scroll position - this._scrollEnabled = props.scrollEnabled !== false; - - this._getCellRendererComponent = this._getCellRendererComponent.bind(this); - this._getItemLayout = this._getItemLayout.bind(this); - this._getKeyExtractor = this._getKeyExtractor.bind(this); - this._onLayout = this._onLayout.bind(this); - this._onScroll = this._onScroll.bind(this); - this._onMomentumScrollEnd = this._onMomentumScrollEnd.bind(this); - this._onTouchStart = this._onTouchStart.bind(this); - this._onTouchEnd = this._onTouchEnd.bind(this); - this._renderItem = this._renderItem.bind(this); - - // WARNING: call this AFTER binding _onScroll - this._setScrollHandler(props); - - // Display warnings - this._displayWarnings(props); - } - - componentDidMount () { - const { apparitionDelay, autoplay, firstItem } = this.props; - - this._mounted = true; - this._initPositionsAndInterpolators(); - - // Without 'requestAnimationFrame' or a `0` timeout, images will randomly not be rendered on Android... - this._initTimeout = setTimeout(() => { - if (!this._mounted) { - return; - } - - const apparitionCallback = () => { - if (apparitionDelay) { - this.setState({ hideCarousel: false }); - } - if (autoplay) { - this.startAutoplay(); - } - }; - - // FlatList will use its own built-in prop `initialScrollIndex` - if (this._needsScrollView()) { - const _firstItem = this._getFirstItem(firstItem); - this._snapToItem(_firstItem, false, false, true); - // this._hackActiveSlideAnimation(_firstItem); - } - - if (apparitionDelay) { - this._apparitionTimeout = setTimeout(() => { - apparitionCallback(); - }, apparitionDelay); - } else { - apparitionCallback(); - } - }, 1); - } - - shouldComponentUpdate (nextProps, nextState) { - if (this.props.shouldOptimizeUpdates === false) { - return true; - } else { - return shallowCompare(this, nextProps, nextState); - } - } - - componentDidUpdate (prevProps) { - const { interpolators } = this.state; - const { firstItem, itemHeight, itemWidth, scrollEnabled, sliderHeight, sliderWidth } = this.props; - const itemsLength = this._getCustomDataLength(this.props); - - if (!itemsLength) { - return; - } - - const nextFirstItem = this._getFirstItem(firstItem, this.props); - let nextActiveItem = typeof this._activeItem !== 'undefined' ? this._activeItem : nextFirstItem; - - const hasNewSliderWidth = sliderWidth && sliderWidth !== prevProps.sliderWidth; - const hasNewSliderHeight = sliderHeight && sliderHeight !== prevProps.sliderHeight; - const hasNewItemWidth = itemWidth && itemWidth !== prevProps.itemWidth; - const hasNewItemHeight = itemHeight && itemHeight !== prevProps.itemHeight; - const hasNewScrollEnabled = scrollEnabled !== prevProps.scrollEnabled; - - // Prevent issues with dynamically removed items - if (nextActiveItem > itemsLength - 1) { - nextActiveItem = itemsLength - 1; - } - - // Handle changing scrollEnabled independent of user -> carousel interaction - if (hasNewScrollEnabled) { - this._setScrollEnabled(scrollEnabled); - } - - if (interpolators.length !== itemsLength || hasNewSliderWidth || - hasNewSliderHeight || hasNewItemWidth || hasNewItemHeight) { - this._activeItem = nextActiveItem; - this._previousItemsLength = itemsLength; - - this._initPositionsAndInterpolators(this.props); - - // Handle scroll issue when dynamically removing items (see #133) - // This also fixes first item's active state on Android - // Because 'initialScrollIndex' apparently doesn't trigger scroll - if (this._previousItemsLength > itemsLength) { - this._hackActiveSlideAnimation(nextActiveItem); - } - - if (hasNewSliderWidth || hasNewSliderHeight || hasNewItemWidth || hasNewItemHeight) { - this._snapToItem(nextActiveItem, false, false, true); - } - } else if (nextFirstItem !== this._previousFirstItem && nextFirstItem !== this._activeItem) { - this._activeItem = nextFirstItem; - this._previousFirstItem = nextFirstItem; - this._snapToItem(nextFirstItem, false, true, true); - } - - if (this.props.onScroll !== prevProps.onScroll) { - this._setScrollHandler(this.props); - } - } - - componentWillUnmount () { - this._mounted = false; - this.stopAutoplay(); - clearTimeout(this._initTimeout); - clearTimeout(this._apparitionTimeout); - clearTimeout(this._hackSlideAnimationTimeout); - clearTimeout(this._enableAutoplayTimeout); - clearTimeout(this._autoplayTimeout); - clearTimeout(this._snapNoMomentumTimeout); - clearTimeout(this._androidRepositioningTimeout); - } - - get realIndex () { - return this._activeItem; - } - - get currentIndex () { - return this._getDataIndex(this._activeItem); - } - - get currentScrollPosition () { - return this._currentScrollOffset; - } - - _setScrollHandler (props) { - // Native driver for scroll events - const scrollEventConfig = { - listener: this._onScroll, - useNativeDriver: true - }; - this._scrollPos = new Animated.Value(0); - const argMapping = props.vertical ? - [{ nativeEvent: { contentOffset: { y: this._scrollPos } } }] : - [{ nativeEvent: { contentOffset: { x: this._scrollPos } } }]; - - if (props.onScroll && Array.isArray(props.onScroll._argMapping)) { - // Because of a react-native issue https://github.com/facebook/react-native/issues/13294 - argMapping.pop(); - const [argMap] = props.onScroll._argMapping; - if (argMap && argMap.nativeEvent && argMap.nativeEvent.contentOffset) { - // Shares the same animated value passed in props - this._scrollPos = - argMap.nativeEvent.contentOffset.x || - argMap.nativeEvent.contentOffset.y || - this._scrollPos; - } - argMapping.push(...props.onScroll._argMapping); - } - this._onScrollHandler = Animated.event( - argMapping, - scrollEventConfig - ); - } - - // This will return a future-proof version code number compatible with semantic versioning - // Examples: 0.59.3 -> 5903 / 0.61.4 -> 6104 / 0.62.12 -> 6212 / 1.0.2 -> 10002 - // _getRNVersionCode () { - // const version = RN_PACKAGE && RN_PACKAGE.version; - // if (!version) { - // return null; - // } - // const versionSplit = version.split('.'); - // if (!versionSplit || !versionSplit.length) { - // return null; - // } - // return versionSplit[0] * 10000 + - // (typeof versionSplit[1] !== 'undefined' ? versionSplit[1] * 100 : 0) + - // (typeof versionSplit[2] !== 'undefined' ? versionSplit[2] * 1 : 0); - // } - - _displayWarnings (props = this.props) { - const pluginName = 'react-native-snap-carousel'; - const removedProps = [ - 'activeAnimationType', - 'activeAnimationOptions', - 'enableMomentum', - 'lockScrollTimeoutDuration', - 'lockScrollWhileSnapping', - 'onBeforeSnapToItem', - 'swipeThreshold' - ]; - - // if (this._RNVersionCode && this._RNVersionCode < 5800) { - // console.error( - // `${pluginName}: Version 4+ of the plugin is based on React Native props that were introduced in version 0.58. ` + - // 'Please downgrade to version 3.x or update your version of React Native.' - // ); - // } - if (!props.vertical && (!props.sliderWidth || !props.itemWidth)) { - console.error(`${pluginName}: You need to specify both 'sliderWidth' and 'itemWidth' for horizontal carousels`); - } - if (props.vertical && (!props.sliderHeight || !props.itemHeight)) { - console.error(`${pluginName}: You need to specify both 'sliderHeight' and 'itemHeight' for vertical carousels`); - } - - removedProps.forEach((removedProp) => { - if (props[removedProp]) { - console.warn(`${pluginName}: Prop ${removedProp} has been removed in version 4 of the plugin`); - } - }); - } - - _needsScrollView () { - const { useScrollView } = this.props; - // Android's cell renderer is buggy and has a stange overflow - // TODO: a workaround might be to pass the custom animated styles directly to it - return IS_ANDROID ? - useScrollView || !AnimatedFlatList || this._shouldUseStackLayout() || this._shouldUseTinderLayout() : - useScrollView || !AnimatedFlatList; - } - - _needsRTLAdaptations () { - const { vertical } = this.props; - return IS_RTL && IS_ANDROID && !vertical; - } - - _enableLoop () { - const { data, enableSnap, loop } = this.props; - return enableSnap && loop && data && data.length && data.length > 1; - } - - _shouldAnimateSlides (props = this.props) { - const { inactiveSlideOpacity, inactiveSlideScale, scrollInterpolator, slideInterpolatedStyle } = props; - return inactiveSlideOpacity < 1 || - inactiveSlideScale < 1 || - !!scrollInterpolator || - !!slideInterpolatedStyle || - this._shouldUseShiftLayout() || - this._shouldUseStackLayout() || - this._shouldUseTinderLayout(); - } - - _shouldUseShiftLayout () { - const { inactiveSlideShift, layout } = this.props; - return layout === 'default' && inactiveSlideShift !== 0; - } - - _shouldUseStackLayout () { - return this.props.layout === 'stack'; - } - - _shouldUseTinderLayout () { - return this.props.layout === 'tinder'; - } - - _shouldRepositionScroll (index) { - const { data, enableSnap, loopClonesPerSide } = this.props; - const dataLength = data && data.length; - if (!enableSnap || !dataLength || !this._enableLoop() || - (index >= loopClonesPerSide && index < dataLength + loopClonesPerSide)) { - return false; - } - return true; - } - - _roundNumber (num, decimals = 1) { - // https://stackoverflow.com/a/41716722/ - const rounder = Math.pow(10, decimals); - return Math.round((num + Number.EPSILON) * rounder) / rounder; - } - - _isMultiple (x, y) { - // This prevents Javascript precision issues: https://stackoverflow.com/a/58440614/ - // Required because Android viewport size can return pretty complicated decimals numbers - return Math.round(Math.round(x / y) / (1 / y)) === Math.round(x); - } - - _getCustomData (props = this.props) { - const { data, loopClonesPerSide } = props; - const dataLength = data && data.length; - - if (!dataLength) { - return []; - } - - if (!this._enableLoop()) { - return data; - } - - let previousItems = []; - let nextItems = []; - - if (loopClonesPerSide > dataLength) { - const dataMultiplier = Math.floor(loopClonesPerSide / dataLength); - const remainder = loopClonesPerSide % dataLength; - - for (let i = 0; i < dataMultiplier; i++) { - previousItems.push(...data); - nextItems.push(...data); - } - - previousItems.unshift(...data.slice(-remainder)); - nextItems.push(...data.slice(0, remainder)); - } else { - previousItems = data.slice(-loopClonesPerSide); - nextItems = data.slice(0, loopClonesPerSide); - } - - return previousItems.concat(data, nextItems); - } - - _getCustomDataLength (props = this.props) { - const { data, loopClonesPerSide } = props; - const dataLength = data && data.length; - - if (!dataLength) { - return 0; - } - - return this._enableLoop() ? dataLength + (2 * loopClonesPerSide) : dataLength; - } - - _getCustomIndex (index, props = this.props) { - const itemsLength = this._getCustomDataLength(props); - - if (!itemsLength || typeof index === 'undefined') { - return 0; - } - - return this._needsRTLAdaptations() ? itemsLength - index - 1 : index; - } - - _getDataIndex (index) { - const { data, loopClonesPerSide } = this.props; - const dataLength = data && data.length; - - if (!this._enableLoop() || !dataLength) { - return index; - } - - if (index >= dataLength + loopClonesPerSide) { - return loopClonesPerSide > dataLength ? - (index - loopClonesPerSide) % dataLength : - index - dataLength - loopClonesPerSide; - } else if (index < loopClonesPerSide) { - // TODO: is there a simpler way of determining the interpolated index? - if (loopClonesPerSide > dataLength) { - const baseDataIndexes = []; - const dataIndexes = []; - const dataMultiplier = Math.floor(loopClonesPerSide / dataLength); - const remainder = loopClonesPerSide % dataLength; - - for (let i = 0; i < dataLength; i++) { - baseDataIndexes.push(i); - } - - for (let j = 0; j < dataMultiplier; j++) { - dataIndexes.push(...baseDataIndexes); - } - - dataIndexes.unshift(...baseDataIndexes.slice(-remainder)); - return dataIndexes[index]; - } else { - return index + dataLength - loopClonesPerSide; - } - } else { - return index - loopClonesPerSide; - } - } - - // Used with `snapToItem()` and 'PaginationDot' - _getPositionIndex (index) { - const { loop, loopClonesPerSide } = this.props; - return loop ? index + loopClonesPerSide : index; - } - - _getSnapOffsets (props = this.props) { - const offset = this._getItemMainDimension(); - return [...Array(this._getCustomDataLength(props))].map((_, i) => { - return i * offset; - }); - } - - _getFirstItem (index, props = this.props) { - const { loopClonesPerSide } = props; - const itemsLength = this._getCustomDataLength(props); - - if (!itemsLength || index > itemsLength - 1 || index < 0) { - return 0; - } - - return this._enableLoop() ? index + loopClonesPerSide : index; - } - - _getWrappedRef () { - // Starting with RN 0.62, we should no longer call `getNode()` on the ref of an Animated component - if (this._carouselRef && ( - (this._needsScrollView() && this._carouselRef.scrollTo) || - (!this._needsScrollView() && this._carouselRef.scrollToOffset) - )) { - return this._carouselRef; - } - // https://github.com/facebook/react-native/issues/10635 - // https://stackoverflow.com/a/48786374/8412141 - return this._carouselRef && this._carouselRef.getNode && this._carouselRef.getNode(); - } - - _getScrollEnabled () { - return this._scrollEnabled; - } - - _setScrollEnabled (scrollEnabled = true) { - const wrappedRef = this._getWrappedRef(); - - if (!wrappedRef || !wrappedRef.setNativeProps) { - return; - } - - // 'setNativeProps()' is used instead of 'setState()' because the latter - // really takes a toll on Android behavior when momentum is disabled - wrappedRef.setNativeProps({ scrollEnabled }); - this._scrollEnabled = scrollEnabled; - } - - _getItemMainDimension () { - const { itemWidth, itemHeight, vertical } = this.props; - return vertical ? itemHeight : itemWidth; - } - - _getItemScrollOffset (index) { - return this._positions && this._positions[index] && this._positions[index].start; - } - - _getItemLayout (_, index) { - const itemMainDimension = this._getItemMainDimension(); - return { - index, - length: itemMainDimension, - offset: itemMainDimension * index // + this._getContainerInnerMargin() - }; - } - - // This will allow us to have a proper zIndex even with a FlatList - // https://github.com/facebook/react-native/issues/18616#issuecomment-389444165 - _getCellRendererComponent ({ children, index, style, ...props }) { - const cellStyle = [ - style, - !IS_ANDROID ? { zIndex: this._getCustomDataLength() - index } : {} - ]; - - return ( - - {children} - - ); - } - - _getKeyExtractor (_, index) { - return this._needsScrollView() ? `scrollview-item-${index}` : `flatlist-item-${index}`; - } - - _getScrollOffset (event) { - const { vertical } = this.props; - return (event && event.nativeEvent && event.nativeEvent.contentOffset && - event.nativeEvent.contentOffset[vertical ? 'y' : 'x']) || 0; - } - - _getContainerInnerMargin (opposite = false) { - const { sliderWidth, sliderHeight, itemWidth, itemHeight, vertical, activeSlideAlignment } = this.props; - - if ((activeSlideAlignment === 'start' && !opposite) || - (activeSlideAlignment === 'end' && opposite)) { - return 0; - } else if ((activeSlideAlignment === 'end' && !opposite) || - (activeSlideAlignment === 'start' && opposite)) { - return vertical ? sliderHeight - itemHeight : sliderWidth - itemWidth; - } else { - return vertical ? (sliderHeight - itemHeight) / 2 : (sliderWidth - itemWidth) / 2; - } - } - - _getActiveSlideOffset () { - const { activeSlideOffset } = this.props; - const itemMainDimension = this._getItemMainDimension(); - const minOffset = 10; - // Make sure activeSlideOffset never prevents the active area from being at least 10 px wide - return itemMainDimension / 2 - activeSlideOffset >= minOffset ? activeSlideOffset : minOffset; - } - - _getActiveItem (offset) { - const itemMainDimension = this._getItemMainDimension(); - const center = offset + itemMainDimension / 2; - const activeSlideOffset = this._getActiveSlideOffset(); - const lastIndex = this._positions.length - 1; - let itemIndex; - - if (offset <= 0) { - return 0; - } - - if (this._positions[lastIndex] && offset >= this._positions[lastIndex].start) { - return lastIndex; - } - - for (let i = 0; i < this._positions.length; i++) { - const { start, end } = this._positions[i]; - if (center + activeSlideOffset >= start && center - activeSlideOffset <= end) { - itemIndex = i; - break; - } - } - - return itemIndex || 0; - } - - _getSlideInterpolatedStyle (index, animatedValue) { - const { layoutCardOffset, slideInterpolatedStyle } = this.props; - - if (slideInterpolatedStyle) { - return slideInterpolatedStyle(index, animatedValue, this.props); - } else if (this._shouldUseTinderLayout()) { - return tinderAnimatedStyles(index, animatedValue, this.props, layoutCardOffset); - } else if (this._shouldUseStackLayout()) { - return stackAnimatedStyles(index, animatedValue, this.props, layoutCardOffset); - } else if (this._shouldUseShiftLayout()) { - return shiftAnimatedStyles(index, animatedValue, this.props); - } else { - return defaultAnimatedStyles(index, animatedValue, this.props); - } - } - - _initPositionsAndInterpolators (props = this.props) { - const { data, scrollInterpolator } = props; - const itemMainDimension = this._getItemMainDimension(); - - if (!data || !data.length) { - return; - } - - const interpolators = []; - this._positions = []; - - this._getCustomData(props).forEach((itemData, index) => { - const _index = this._getCustomIndex(index, props); - let animatedValue; - - this._positions[index] = { - start: index * itemMainDimension, - end: index * itemMainDimension + itemMainDimension - }; - - if (!this._shouldAnimateSlides(props)) { - animatedValue = new Animated.Value(1); - } else { - let interpolator; - - if (scrollInterpolator) { - interpolator = scrollInterpolator(_index, props); - } else if (this._shouldUseStackLayout()) { - interpolator = stackScrollInterpolator(_index, props); - } else if (this._shouldUseTinderLayout()) { - interpolator = tinderScrollInterpolator(_index, props); - } - - if (!interpolator || !interpolator.inputRange || !interpolator.outputRange) { - interpolator = defaultScrollInterpolator(_index, props); - } - - animatedValue = this._scrollPos.interpolate({ - ...interpolator, - extrapolate: 'clamp' - }); - } - - interpolators.push(animatedValue); - }); - - this.setState({ interpolators }); - } - - _hackActiveSlideAnimation (index, scrollValue = 1) { - const offset = this._getItemScrollOffset(index); - - if (!this._mounted || !this._carouselRef || typeof offset === 'undefined') { - return; - } - - const multiplier = this._currentScrollOffset === 0 ? 1 : -1; - const scrollDelta = scrollValue * multiplier; - - this._scrollTo({ offset: offset + scrollDelta, animated: false }); - - clearTimeout(this._hackSlideAnimationTimeout); - this._hackSlideAnimationTimeout = setTimeout(() => { - this._scrollTo({ offset, animated: false }); - }, 1); // works randomly when set to '0' - } - - _repositionScroll (index, animated = false) { - const { data, loopClonesPerSide } = this.props; - const dataLength = data && data.length; - - if (typeof index === 'undefined' || !this._shouldRepositionScroll(index)) { - return; - } - - let repositionTo = index; - - if (index >= dataLength + loopClonesPerSide) { - repositionTo = index - dataLength; - } else if (index < loopClonesPerSide) { - repositionTo = index + dataLength; - } - - this._snapToItem(repositionTo, animated, false); - } - - _scrollTo ({ offset, index, animated = true }) { - const { vertical } = this.props; - const wrappedRef = this._getWrappedRef(); - if (!this._mounted || !wrappedRef || (typeof offset === 'undefined' && typeof index === 'undefined')) { - return; - } - - let scrollToOffset; - if (typeof index !== 'undefined') { - scrollToOffset = this._getItemScrollOffset(index); - } else { - scrollToOffset = offset; - } - - if (typeof scrollToOffset === 'undefined') { - return; - } - - const options = this._needsScrollView() ? { - x: vertical ? 0 : offset, - y: vertical ? offset : 0, - animated - } : { - offset, - animated - }; - - if (this._needsScrollView()) { - wrappedRef.scrollTo(options); - } else { - wrappedRef.scrollToOffset(options); - } - } - - _onTouchStart () { - const { onTouchStart } = this.props; - - // `onTouchStart` is fired even when `scrollEnabled` is set to `false` - if (this._getScrollEnabled() !== false && this._autoplaying) { - this.pauseAutoPlay(); - } - - onTouchStart && onTouchStart(); - } - - _onTouchEnd () { - const { onTouchEnd } = this.props; - - if (this._getScrollEnabled() !== false && this._autoplay && !this._autoplaying) { - // This event is buggy on Android, so a fallback is provided in _onMomentumScrollEnd() - this.startAutoplay(); - } - - onTouchEnd && onTouchEnd(); - } - - _onScroll (event) { - const { onScroll, onScrollIndexChanged } = this.props; - const scrollOffset = event ? this._getScrollOffset(event) : this._currentScrollOffset; - const nextActiveItem = this._getActiveItem(scrollOffset); - - this._currentScrollOffset = scrollOffset; - - if (nextActiveItem !== this._onScrollActiveItem) { - this._onScrollActiveItem = nextActiveItem; - onScrollIndexChanged && onScrollIndexChanged(this._getDataIndex(nextActiveItem)); - } - - if (typeof onScroll === 'function' && event) { - onScroll(event); - } - } - - _onMomentumScrollEnd (event) { - const { autoplayDelay, itemWidth, onMomentumScrollEnd, onSnapToItem } = this.props; - const scrollOffset = event ? this._getScrollOffset(event) : this._currentScrollOffset; - const nextActiveItem = this._getActiveItem(scrollOffset); - const hasSnapped = this._isMultiple(scrollOffset, itemWidth); - - // WARNING: everything in this condition will probably need to be called on _snapToItem as well because: - // 1. `onMomentumScrollEnd` won't be called if the scroll isn't animated - // 2. `onMomentumScrollEnd` won't be called at all on Android when scrolling programmatically - if (nextActiveItem !== this._activeItem) { - this._activeItem = nextActiveItem; - onSnapToItem && onSnapToItem(this._getDataIndex(nextActiveItem)); - - if (hasSnapped) { - this._repositionScroll(nextActiveItem); - } - } - - onMomentumScrollEnd && onMomentumScrollEnd(event); - - // The touchEnd event is buggy on Android, so this will serve as a fallback whenever needed - // https://github.com/facebook/react-native/issues/9439 - if (IS_ANDROID && this._autoplay && !this._autoplaying) { - clearTimeout(this._enableAutoplayTimeout); - this._enableAutoplayTimeout = setTimeout(() => { - this.startAutoplay(); - }, autoplayDelay); - } - - } - - _onLayout (event) { - const { onLayout } = this.props; - - // Prevent unneeded actions during the first 'onLayout' (triggered on init) - if (this._onLayoutInitDone) { - this._initPositionsAndInterpolators(); - this._snapToItem(this._activeItem, false, false, true); - } else { - this._onLayoutInitDone = true; - } - - onLayout && onLayout(event); - } - - _snapToItem (index, animated = true, fireCallback = true, forceScrollTo = false) { - const { onSnapToItem } = this.props; - const itemsLength = this._getCustomDataLength(); - const wrappedRef = this._getWrappedRef(); - - if (!itemsLength || !wrappedRef) { - return; - } - - if (!index || index < 0) { - index = 0; - } else if (itemsLength > 0 && index >= itemsLength) { - index = itemsLength - 1; - } - - if (index === this._activeItem && !forceScrollTo) { - return; - } - - const offset = this._getItemScrollOffset(index); - - if (offset === undefined) { - return; - } - - this._scrollTo({ offset, animated }); - - // On both platforms, `onMomentumScrollEnd` won't be triggered if the scroll isn't animated - // so we need to trigger the callback manually - // On Android `onMomentumScrollEnd` won't be triggered when scrolling programmatically - // Therefore everything critical needs to be manually called here as well, even though the timing might be off - const requiresManualTrigger = !animated || IS_ANDROID; - if (requiresManualTrigger) { - this._activeItem = index; - - if (fireCallback) { - onSnapToItem && onSnapToItem(this._getDataIndex(index)); - } - - // Repositioning on Android - if (IS_ANDROID && this._shouldRepositionScroll(index)) { - if (animated) { - this._androidRepositioningTimeout = setTimeout(() => { - // Without scroll animation, the behavior is completely buggy... - this._repositionScroll(index, true); - }, 400); // Approximate scroll duration on Android - } else { - this._repositionScroll(index); - } - } - } - } - - startAutoplay () { - const { autoplayInterval, autoplayDelay } = this.props; - this._autoplay = true; - - if (this._autoplaying) { - return; - } - - clearTimeout(this._autoplayTimeout); - this._autoplayTimeout = setTimeout(() => { - this._autoplaying = true; - this._autoplayInterval = setInterval(() => { - if (this._autoplaying) { - this.snapToNext(); - } - }, autoplayInterval); - }, autoplayDelay); - } - - pauseAutoPlay () { - this._autoplaying = false; - clearTimeout(this._autoplayTimeout); - clearTimeout(this._enableAutoplayTimeout); - clearInterval(this._autoplayInterval); - } - - stopAutoplay () { - this._autoplay = false; - this.pauseAutoPlay(); - } - - snapToItem (index, animated = true, fireCallback = true) { - if (!index || index < 0) { - index = 0; - } - - const positionIndex = this._getPositionIndex(index); - - if (positionIndex === this._activeItem) { - return; - } - - this._snapToItem(positionIndex, animated, fireCallback); - } - - snapToNext (animated = true, fireCallback = true) { - const itemsLength = this._getCustomDataLength(); - - let newIndex = this._activeItem + 1; - if (newIndex > itemsLength - 1) { - newIndex = 0; - } - this._snapToItem(newIndex, animated, fireCallback); - } - - snapToPrev (animated = true, fireCallback = true) { - const itemsLength = this._getCustomDataLength(); - - let newIndex = this._activeItem - 1; - if (newIndex < 0) { - newIndex = itemsLength - 1; - } - this._snapToItem(newIndex, animated, fireCallback); - } - - // https://github.com/facebook/react-native/issues/1831#issuecomment-231069668 - triggerRenderingHack (offset = 1) { - this._hackActiveSlideAnimation(this._activeItem, offset); - } - - _renderItem ({ item, index }) { - const { interpolators } = this.state; - const { - hasParallaxImages, itemWidth, itemHeight, keyExtractor, renderItem, - sliderHeight, sliderWidth, slideStyle, vertical - } = this.props; - const animatedValue = interpolators && interpolators[index]; - - if (typeof animatedValue === 'undefined') { - return null; - } - - const animate = this._shouldAnimateSlides(); - const Component = animate ? Animated.View : View; - const animatedStyle = animate ? this._getSlideInterpolatedStyle(index, animatedValue) : {}; - const dataIndex = this._getDataIndex(index); - - const parallaxProps = hasParallaxImages ? { - scrollPosition: this._scrollPos, - carouselRef: this._carouselRef, - vertical, - sliderWidth, - sliderHeight, - itemWidth, - itemHeight - } : undefined; - - const mainDimension = vertical ? { height: itemHeight } : { width: itemWidth }; - const specificProps = this._needsScrollView() ? { - key: keyExtractor ? keyExtractor(item, index) : this._getKeyExtractor(item, index) - } : {}; - - return ( - - { renderItem({ item, index, dataIndex }, parallaxProps) } - - ); - } - - _getComponentOverridableProps () { - const { hideCarousel } = this.state; - const { - itemWidth, itemHeight, loopClonesPerSide, - sliderWidth, sliderHeight, vertical - } = this.props; - const visibleItems = Math.ceil(vertical ? sliderHeight / itemHeight : sliderWidth / itemWidth) + 1; - const initialNumPerSide = this._enableLoop() ? loopClonesPerSide : 2; - const initialNumToRender = visibleItems + (initialNumPerSide * 2); - const maxToRenderPerBatch = initialNumToRender + (initialNumPerSide * 2); - const windowSize = maxToRenderPerBatch; - - const specificProps = !this._needsScrollView() ? { - initialNumToRender, - maxToRenderPerBatch, - windowSize - // updateCellsBatchingPeriod - } : {}; - - return { - ...specificProps, - automaticallyAdjustContentInsets: false, - decelerationRate: 'fast', - directionalLockEnabled: true, - disableScrollViewPanResponder: false, // If set to `true`, touch events will be triggered too easily - inverted: this._needsRTLAdaptations(), - overScrollMode: 'never', - pinchGestureEnabled: false, - pointerEvents: hideCarousel ? 'none' : 'auto', - // removeClippedSubviews: !this._needsScrollView(), - // renderToHardwareTextureAndroid: true, - scrollsToTop: false, - showsHorizontalScrollIndicator: false, - showsVerticalScrollIndicator: false - }; - } - - _getComponentStaticProps () { - const { hideCarousel } = this.state; - const { - activeSlideAlignment, CellRendererComponent, containerCustomStyle, - contentContainerCustomStyle, firstItem, getItemLayout, keyExtractor, - sliderWidth, sliderHeight, style, useExperimentalSnap, vertical - } = this.props; - - const containerStyle = [ - // { overflow: 'hidden' }, - containerCustomStyle || style || {}, - hideCarousel ? { opacity: 0 } : {}, - vertical ? - { height: sliderHeight, flexDirection: 'column' } : - // LTR hack; see https://github.com/facebook/react-native/issues/11960 - // and https://github.com/facebook/react-native/issues/13100#issuecomment-328986423 - { width: sliderWidth, flexDirection: this._needsRTLAdaptations() ? 'row-reverse' : 'row' } - ]; - - const innerMarginStyle = vertical ? { - paddingTop: this._getContainerInnerMargin(), - paddingBottom: this._getContainerInnerMargin(true) - } : { - paddingLeft: this._getContainerInnerMargin(), - paddingRight: this._getContainerInnerMargin(true) - }; - - const contentContainerStyle = [ - !useExperimentalSnap ? innerMarginStyle : {}, - contentContainerCustomStyle || {} - ]; - - // WARNING: `snapToAlignment` won't work as intended because of the following: - // https://github.com/facebook/react-native/blob/d0871d0a9a373e1d3ac35da46c85c0d0e793116d/React/Views/ScrollView/RCTScrollView.m#L751-L755 - // - Snap points will be off - // - Slide animations will be off - // - Last items won't be set as active (no `onSnapToItem` callback) - // Recommended only with large slides and `activeSlideAlignment` set to `start` for the time being - const snapProps = useExperimentalSnap ? { - // disableIntervalMomentum: true, // Slide ± one item at a time - snapToAlignment: activeSlideAlignment, - snapToInterval: this._getItemMainDimension() - } : { - snapToOffsets: this._getSnapOffsets() - }; - - // Flatlist specifics - const specificProps = !this._needsScrollView() ? { - CellRendererComponent: CellRendererComponent || this._getCellRendererComponent, - getItemLayout: getItemLayout || this._getItemLayout, - initialScrollIndex: this._getFirstItem(firstItem), - keyExtractor: keyExtractor || this._getKeyExtractor, - numColumns: 1, - renderItem: this._renderItem - } : {}; - - return { - ...specificProps, - ...snapProps, - ref: (c) => { this._carouselRef = c; }, - contentContainerStyle: contentContainerStyle, - data: this._getCustomData(), - horizontal: !vertical, - scrollEventThrottle: 1, - style: containerStyle, - onLayout: this._onLayout, - onMomentumScrollEnd: this._onMomentumScrollEnd, - onScroll: this._onScrollHandler, - onTouchStart: this._onTouchStart, - onTouchEnd: this._onTouchEnd - }; - } - - render () { - const { data, renderItem, useScrollView } = this.props; - - if (!data || !renderItem) { - return null; - } - - const props = { - ...this._getComponentOverridableProps(), - ...this.props, - ...this._getComponentStaticProps() - }; - - const ScrollViewComponent = typeof useScrollView === 'function' ? useScrollView : AnimatedScrollView; - - return this._needsScrollView() ? ( - - { - this._getCustomData().map((item, index) => { - return this._renderItem({ item, index }); - }) - } - - ) : ( - - ); - } -} diff --git a/src/carousel/Carousel.tsx b/src/carousel/Carousel.tsx new file mode 100644 index 000000000..e9daf5b1f --- /dev/null +++ b/src/carousel/Carousel.tsx @@ -0,0 +1,1342 @@ +import React, { PropsWithChildren } from 'react'; +import { + Animated, + FlatList, + I18nManager, + Platform, + ScrollView, + View, + StyleProp, + NativeSyntheticEvent, + NativeScrollEvent, + LayoutChangeEvent, + GestureResponderEvent, + ViewStyle +} from 'react-native'; +import shallowCompare from 'react-addons-shallow-compare'; +import { + defaultScrollInterpolator, + stackScrollInterpolator, + tinderScrollInterpolator, + defaultAnimatedStyles, + shiftAnimatedStyles, + stackAnimatedStyles, + tinderAnimatedStyles +} from '../utils/animations'; +import type { CarouselProps, CarouselState } from './types'; + +// Metro doesn't support dynamic imports - i.e. require() done in the component itself +// But at the same time the following import will fail on Snack... +// TODO: find a way to get React Native's version without having to assume the file path +// import RN_PACKAGE from '../../../react-native/package.json'; + +const IS_ANDROID = Platform.OS === 'android'; + +// React Native automatically handles RTL layouts; unfortunately, it's buggy with horizontal ScrollView +// See https://github.com/facebook/react-native/issues/11960 +// NOTE: the following variable is not declared in the constructor +// otherwise it is undefined at init, which messes with custom indexes +const IS_RTL = I18nManager.isRTL; + +export class Carousel extends React.Component< + CarouselProps, + CarouselState +> { + static defaultProps = { + activeSlideAlignment: 'center', + activeSlideOffset: 20, + apparitionDelay: 0, + autoplay: false, + autoplayDelay: 1000, + autoplayInterval: 3000, + callbackOffsetMargin: 5, + containerCustomStyle: {}, + contentContainerCustomStyle: {}, + enableSnap: true, + firstItem: 0, + hasParallaxImages: false, + inactiveSlideOpacity: 0.7, + inactiveSlideScale: 0.9, + inactiveSlideShift: 0, + layout: 'default', + loop: false, + loopClonesPerSide: 3, + scrollEnabled: true, + slideStyle: {}, + shouldOptimizeUpdates: true, + useExperimentalSnap: false, + useScrollView: !Animated.FlatList + }; + + _activeItem: number; + _onScrollActiveItem: number; + _previousFirstItem: number; + _previousItemsLength: number; + _mounted: boolean; + _positions: { start: number; end: number }[]; + _currentScrollOffset: number; + _scrollEnabled: boolean; + + _initTimeout?: ReturnType; + _apparitionTimeout?: ReturnType; + _hackSlideAnimationTimeout?: ReturnType; + _enableAutoplayTimeout?: ReturnType; + _autoplayTimeout?: ReturnType; + _snapNoMomentumTimeout?: ReturnType; + _androidRepositioningTimeout?: ReturnType; + _autoplayInterval?: ReturnType; + + _scrollPos?: Animated.Value; + + _onScrollHandler?: ReturnType; + + _carouselRef: ScrollView | FlatList | null = null; + + _autoplaying?: boolean; + _autoplay?: boolean; + + _onLayoutInitDone?: boolean; + + constructor (props: CarouselProps) { + super(props); + + this.state = { + hideCarousel: !!props.apparitionDelay, + interpolators: [] + }; + + // this._RNVersionCode = this._getRNVersionCode(); + + // The following values are not stored in the state because 'setState()' is asynchronous + // and this results in an absolutely crappy behavior on Android while swiping (see #156) + const initialActiveItem = this._getFirstItem(props.firstItem); + this._activeItem = initialActiveItem; + this._onScrollActiveItem = initialActiveItem; + this._previousFirstItem = initialActiveItem; + this._previousItemsLength = initialActiveItem; + + this._mounted = false; + this._positions = []; + this._currentScrollOffset = 0; // Store ScrollView's scroll position + this._scrollEnabled = props.scrollEnabled !== false; + + this._getCellRendererComponent = this._getCellRendererComponent.bind(this); + this._getItemLayout = this._getItemLayout.bind(this); + this._getKeyExtractor = this._getKeyExtractor.bind(this); + this._onLayout = this._onLayout.bind(this); + this._onScroll = this._onScroll.bind(this); + this._onMomentumScrollEnd = this._onMomentumScrollEnd.bind(this); + this._onTouchStart = this._onTouchStart.bind(this); + this._onTouchEnd = this._onTouchEnd.bind(this); + this._renderItem = this._renderItem.bind(this); + + // WARNING: call this AFTER binding _onScroll + this._setScrollHandler(props); + + // Display warnings + this._displayWarnings(props); + } + + componentDidMount () { + const { apparitionDelay, autoplay, firstItem } = this.props; + + this._mounted = true; + this._initPositionsAndInterpolators(); + + // Without 'requestAnimationFrame' or a `0` timeout, images will randomly not be rendered on Android... + this._initTimeout = setTimeout(() => { + if (!this._mounted) { + return; + } + + const apparitionCallback = () => { + if (apparitionDelay) { + this.setState({ hideCarousel: false }); + } + if (autoplay) { + this.startAutoplay(); + } + }; + + // FlatList will use its own built-in prop `initialScrollIndex` + if (this._needsScrollView()) { + const _firstItem = this._getFirstItem(firstItem); + this._snapToItem(_firstItem, false, false, true); + // this._hackActiveSlideAnimation(_firstItem); + } + + if (apparitionDelay) { + this._apparitionTimeout = setTimeout(() => { + apparitionCallback(); + }, apparitionDelay); + } else { + apparitionCallback(); + } + }, 1); + } + + shouldComponentUpdate ( + nextProps: CarouselProps, + nextState: CarouselState + ): boolean { + if (this.props.shouldOptimizeUpdates === false) { + return true; + } else { + return shallowCompare(this, nextProps, nextState); + } + } + + componentDidUpdate (prevProps: CarouselProps) { + const { interpolators } = this.state; + const { + firstItem, + scrollEnabled + } = this.props; + const itemsLength = this._getCustomDataLength(this.props); + + if (!itemsLength) { + return; + } + + const nextFirstItem = this._getFirstItem(firstItem, this.props); + let nextActiveItem = + typeof this._activeItem !== 'undefined' ? + this._activeItem : + nextFirstItem; + + const hasNewSize = this.props.vertical !== prevProps.vertical || + ( + this.props.vertical && prevProps.vertical && ( + prevProps.itemHeight !== this.props.itemHeight || prevProps.sliderHeight !== this.props.sliderHeight + ) + ) || ( + !this.props.vertical && !prevProps.vertical && ( + prevProps.itemWidth !== this.props.itemWidth || prevProps.sliderWidth !== this.props.sliderWidth + ) + ); + + // Prevent issues with dynamically removed items + if (nextActiveItem > itemsLength - 1) { + nextActiveItem = itemsLength - 1; + } + + // Handle changing scrollEnabled independent of user -> carousel interaction + if (scrollEnabled !== prevProps.scrollEnabled) { + this._setScrollEnabled(scrollEnabled); + } + + if ( + interpolators.length !== itemsLength || + hasNewSize + ) { + this._activeItem = nextActiveItem; + this._previousItemsLength = itemsLength; + + this._initPositionsAndInterpolators(this.props); + + // Handle scroll issue when dynamically removing items (see #133) + // This also fixes first item's active state on Android + // Because 'initialScrollIndex' apparently doesn't trigger scroll + if (this._previousItemsLength > itemsLength) { + this._hackActiveSlideAnimation(nextActiveItem); + } + + if (hasNewSize) { + this._snapToItem(nextActiveItem, false, false, true); + } + } else if ( + nextFirstItem !== this._previousFirstItem && + nextFirstItem !== this._activeItem + ) { + this._activeItem = nextFirstItem; + this._previousFirstItem = nextFirstItem; + this._snapToItem(nextFirstItem, false, true, true); + } + + if (this.props.onScroll !== prevProps.onScroll) { + this._setScrollHandler(this.props); + } + } + + componentWillUnmount () { + this._mounted = false; + this.stopAutoplay(); + // @ts-expect-error setTimeout / clearTiemout is buggy :/ + clearTimeout(this._initTimeout); + // @ts-expect-error setTimeout / clearTiemout is buggy :/ + clearTimeout(this._apparitionTimeout); + // @ts-expect-error setTimeout / clearTiemout is buggy :/ + clearTimeout(this._hackSlideAnimationTimeout); + // @ts-expect-error setTimeout / clearTiemout is buggy :/ + clearTimeout(this._enableAutoplayTimeout); + // @ts-expect-error setTimeout / clearTiemout is buggy :/ + clearTimeout(this._autoplayTimeout); + // @ts-expect-error setTimeout / clearTiemout is buggy :/ + clearTimeout(this._snapNoMomentumTimeout); + // @ts-expect-error setTimeout / clearTiemout is buggy :/ + clearTimeout(this._androidRepositioningTimeout); + } + + get realIndex () { + return this._activeItem; + } + + get currentIndex () { + return this._getDataIndex(this._activeItem); + } + + get currentScrollPosition () { + return this._currentScrollOffset; + } + + _setScrollHandler (props: CarouselProps) { + // Native driver for scroll events + const scrollEventConfig = { + listener: this._onScroll, + useNativeDriver: true + }; + this._scrollPos = new Animated.Value(0); + const argMapping = props.vertical ? + [{ nativeEvent: { contentOffset: { y: this._scrollPos } } }] : + [{ nativeEvent: { contentOffset: { x: this._scrollPos } } }]; + + // @ts-expect-error Let's ignore for now that trick + if (props.onScroll && Array.isArray(props.onScroll._argMapping)) { + // Because of a react-native issue https://github.com/facebook/react-native/issues/13294 + argMapping.pop(); + // @ts-expect-error Let's ignore for now that trick + const [argMap] = props.onScroll._argMapping; + if (argMap && argMap.nativeEvent && argMap.nativeEvent.contentOffset) { + // Shares the same animated value passed in props + this._scrollPos = + argMap.nativeEvent.contentOffset.x || + argMap.nativeEvent.contentOffset.y || + this._scrollPos; + } + // @ts-expect-error Let's ignore for now that trick + argMapping.push(...props.onScroll._argMapping); + } + this._onScrollHandler = Animated.event( + argMapping, + scrollEventConfig + ); + } + + // This will return a future-proof version code number compatible with semantic versioning + // Examples: 0.59.3 -> 5903 / 0.61.4 -> 6104 / 0.62.12 -> 6212 / 1.0.2 -> 10002 + // _getRNVersionCode () { + // const version = RN_PACKAGE && RN_PACKAGE.version; + // if (!version) { + // return null; + // } + // const versionSplit = version.split('.'); + // if (!versionSplit || !versionSplit.length) { + // return null; + // } + // return versionSplit[0] * 10000 + + // (typeof versionSplit[1] !== 'undefined' ? versionSplit[1] * 100 : 0) + + // (typeof versionSplit[2] !== 'undefined' ? versionSplit[2] * 1 : 0); + // } + + _displayWarnings (props: CarouselProps = this.props) { + const pluginName = 'react-native-snap-carousel'; + const removedProps = [ + 'activeAnimationType', + 'activeAnimationOptions', + 'enableMomentum', + 'lockScrollTimeoutDuration', + 'lockScrollWhileSnapping', + 'onBeforeSnapToItem', + 'swipeThreshold' + ] as const; + + // if (this._RNVersionCode && this._RNVersionCode < 5800) { + // console.error( + // `${pluginName}: Version 4+ of the plugin is based on React Native props that were introduced in version 0.58. ` + + // 'Please downgrade to version 3.x or update your version of React Native.' + // ); + // } + if (!props.vertical && (!props.sliderWidth || !props.itemWidth)) { + console.error( + `${pluginName}: You need to specify both 'sliderWidth' and 'itemWidth' for horizontal carousels` + ); + } + if (props.vertical && (!props.sliderHeight || !props.itemHeight)) { + console.error( + `${pluginName}: You need to specify both 'sliderHeight' and 'itemHeight' for vertical carousels` + ); + } + + removedProps.forEach((removedProp) => { + if (removedProp in props) { + console.warn( + `${pluginName}: Prop ${removedProp} has been removed in version 4 of the plugin` + ); + } + }); + } + + _needsScrollView () { + const { useScrollView } = this.props; + // Android's cell renderer is buggy and has a stange overflow + // TODO: a workaround might be to pass the custom animated styles directly to it + return IS_ANDROID ? + useScrollView || + !Animated.FlatList || + this._shouldUseStackLayout() || + this._shouldUseTinderLayout() : + useScrollView || !Animated.FlatList; + } + + _needsRTLAdaptations () { + const { vertical } = this.props; + return IS_RTL && IS_ANDROID && !vertical; + } + + _enableLoop () { + const { data, enableSnap, loop } = this.props; + return enableSnap && loop && data && data.length && data.length > 1; + } + + _shouldAnimateSlides (props: CarouselProps = this.props) { + const { + inactiveSlideOpacity, + inactiveSlideScale, + scrollInterpolator, + slideInterpolatedStyle + } = props; + return ( + inactiveSlideOpacity < 1 || + inactiveSlideScale < 1 || + !!scrollInterpolator || + !!slideInterpolatedStyle || + this._shouldUseShiftLayout() || + this._shouldUseStackLayout() || + this._shouldUseTinderLayout() + ); + } + + _shouldUseShiftLayout () { + const { inactiveSlideShift, layout } = this.props; + return layout === 'default' && inactiveSlideShift !== 0; + } + + _shouldUseStackLayout () { + return this.props.layout === 'stack'; + } + + _shouldUseTinderLayout () { + return this.props.layout === 'tinder'; + } + + _shouldRepositionScroll (index: number) { + const { data, enableSnap, loopClonesPerSide } = this.props; + const dataLength = data && data.length; + if ( + !enableSnap || + !dataLength || + !this._enableLoop() || + (index >= loopClonesPerSide && index < dataLength + loopClonesPerSide) + ) { + return false; + } + return true; + } + + _roundNumber (num: number, decimals = 1) { + // https://stackoverflow.com/a/41716722/ + const rounder = Math.pow(10, decimals); + return Math.round((num + Number.EPSILON) * rounder) / rounder; + } + + _isMultiple (x: number, y: number) { + // This prevents Javascript precision issues: https://stackoverflow.com/a/58440614/ + // Required because Android viewport size can return pretty complicated decimals numbers + return Math.round(Math.round(x / y) / (1 / y)) === Math.round(x); + } + + _getCustomData (props: CarouselProps = this.props) { + const { data, loopClonesPerSide } = props; + const dataLength = data && data.length; + + if (!dataLength) { + return []; + } + + if (!this._enableLoop()) { + return data; + } + + let previousItems = []; + let nextItems = []; + + if (loopClonesPerSide > dataLength) { + const dataMultiplier = Math.floor(loopClonesPerSide / dataLength); + const remainder = loopClonesPerSide % dataLength; + + for (let i = 0; i < dataMultiplier; i++) { + previousItems.push(...data); + nextItems.push(...data); + } + + previousItems.unshift(...data.slice(-remainder)); + nextItems.push(...data.slice(0, remainder)); + } else { + previousItems = data.slice(-loopClonesPerSide); + nextItems = data.slice(0, loopClonesPerSide); + } + + return previousItems.concat(data, nextItems); + } + + _getCustomDataLength (props: CarouselProps = this.props) { + const { data, loopClonesPerSide } = props; + const dataLength = data && data.length; + + if (!dataLength) { + return 0; + } + + return this._enableLoop() ? dataLength + 2 * loopClonesPerSide : dataLength; + } + + _getCustomIndex (index: number, props: CarouselProps = this.props) { + const itemsLength = this._getCustomDataLength(props); + + if (!itemsLength || typeof index === 'undefined') { + return 0; + } + + return this._needsRTLAdaptations() ? itemsLength - index - 1 : index; + } + + _getDataIndex (index: number) { + const { data, loopClonesPerSide } = this.props; + const dataLength = data && data.length; + + if (!this._enableLoop() || !dataLength) { + return index; + } + + if (index >= dataLength + loopClonesPerSide) { + return loopClonesPerSide > dataLength ? + (index - loopClonesPerSide) % dataLength : + index - dataLength - loopClonesPerSide; + } else if (index < loopClonesPerSide) { + // TODO: is there a simpler way of determining the interpolated index? + if (loopClonesPerSide > dataLength) { + const baseDataIndexes = []; + const dataIndexes = []; + const dataMultiplier = Math.floor(loopClonesPerSide / dataLength); + const remainder = loopClonesPerSide % dataLength; + + for (let i = 0; i < dataLength; i++) { + baseDataIndexes.push(i); + } + + for (let j = 0; j < dataMultiplier; j++) { + dataIndexes.push(...baseDataIndexes); + } + + dataIndexes.unshift(...baseDataIndexes.slice(-remainder)); + return dataIndexes[index]; + } else { + return index + dataLength - loopClonesPerSide; + } + } else { + return index - loopClonesPerSide; + } + } + + // Used with `snapToItem()` and 'PaginationDot' + _getPositionIndex (index: number) { + const { loop, loopClonesPerSide } = this.props; + return loop ? index + loopClonesPerSide : index; + } + + _getSnapOffsets (props: CarouselProps = this.props) { + const offset = this._getItemMainDimension(); + return [...Array(this._getCustomDataLength(props))].map((_, i) => { + return i * offset; + }); + } + + _getFirstItem (index: number, props: CarouselProps = this.props) { + const { loopClonesPerSide } = props; + const itemsLength = this._getCustomDataLength(props); + + if (!itemsLength || index > itemsLength - 1 || index < 0) { + return 0; + } + + return this._enableLoop() ? index + loopClonesPerSide : index; + } + + _getWrappedRef () { + // Starting with RN 0.62, we should no longer call `getNode()` on the ref of an Animated component + if ( + this._carouselRef && + ((this._needsScrollView() && + (this._carouselRef as ScrollView).scrollTo) || + (!this._needsScrollView() && + (this._carouselRef as FlatList).scrollToOffset)) + ) { + return this._carouselRef; + } + // https://github.com/facebook/react-native/issues/10635 + // https://stackoverflow.com/a/48786374/8412141 + return ( + this._carouselRef && + // @ts-expect-error This is for before 0.62 + this._carouselRef.getNode && + // @ts-expect-error This is for before 0.62 + this._carouselRef.getNode() + ); + } + + _getScrollEnabled () { + return this._scrollEnabled; + } + + _setScrollEnabled (scrollEnabled = true) { + const wrappedRef = this._getWrappedRef(); + + if (!wrappedRef || !wrappedRef.setNativeProps) { + return; + } + + // 'setNativeProps()' is used instead of 'setState()' because the latter + // really takes a toll on Android behavior when momentum is disabled + wrappedRef.setNativeProps({ scrollEnabled }); + this._scrollEnabled = scrollEnabled; + } + + _getItemMainDimension () { + return this.props.vertical ? this.props.itemHeight : this.props.itemWidth; + } + + _getItemScrollOffset (index: number) { + return ( + this._positions && this._positions[index] && this._positions[index].start + ); + } + + _getItemLayout (_: TData[], index: number) { + const itemMainDimension = this._getItemMainDimension(); + return { + index, + length: itemMainDimension, + offset: itemMainDimension * index // + this._getContainerInnerMargin() + }; + } + + // This will allow us to have a proper zIndex even with a FlatList + // https://github.com/facebook/react-native/issues/18616#issuecomment-389444165 + _getCellRendererComponent ({ + children, + index, + style, + ...props + }: PropsWithChildren<{ index: number; style: StyleProp }>) { + const cellStyle = [ + style, + !IS_ANDROID ? { zIndex: this._getCustomDataLength() - index } : {} + ]; + + return ( + + {children} + + ); + } + + _getKeyExtractor (_: TData, index: number) { + return this._needsScrollView() ? + `scrollview-item-${index}` : + `flatlist-item-${index}`; + } + + _getScrollOffset (event: NativeSyntheticEvent) { + const { vertical } = this.props; + return ( + (event && + event.nativeEvent && + event.nativeEvent.contentOffset && + event.nativeEvent.contentOffset[vertical ? 'y' : 'x']) || + 0 + ); + } + + _getContainerInnerMargin (opposite = false) { + const { activeSlideAlignment } = this.props; + + if ( + (activeSlideAlignment === 'start' && !opposite) || + (activeSlideAlignment === 'end' && opposite) + ) { + return 0; + } else if ( + (activeSlideAlignment === 'end' && !opposite) || + (activeSlideAlignment === 'start' && opposite) + ) { + return this.props.vertical ? + this.props.sliderHeight - this.props.itemHeight : + this.props.sliderWidth - this.props.itemWidth; + } else { + return this.props.vertical ? + (this.props.sliderHeight - this.props.itemHeight) / 2 : + (this.props.sliderWidth - this.props.itemWidth) / 2; + } + } + + _getActiveSlideOffset () { + const { activeSlideOffset } = this.props; + const itemMainDimension = this._getItemMainDimension(); + const minOffset = 10; + // Make sure activeSlideOffset never prevents the active area from being at least 10 px wide + return itemMainDimension / 2 - activeSlideOffset >= minOffset ? + activeSlideOffset : + minOffset; + } + + _getActiveItem (offset: number) { + const itemMainDimension = this._getItemMainDimension(); + const center = offset + itemMainDimension / 2; + const activeSlideOffset = this._getActiveSlideOffset(); + const lastIndex = this._positions.length - 1; + let itemIndex; + + if (offset <= 0) { + return 0; + } + + if ( + this._positions[lastIndex] && + offset >= this._positions[lastIndex].start + ) { + return lastIndex; + } + + for (let i = 0; i < this._positions.length; i++) { + const { start, end } = this._positions[i]; + if ( + center + activeSlideOffset >= start && + center - activeSlideOffset <= end + ) { + itemIndex = i; + break; + } + } + + return itemIndex || 0; + } + + _getSlideInterpolatedStyle (index: number, animatedValue: Animated.AnimatedInterpolation) { + const { layoutCardOffset, slideInterpolatedStyle } = this.props; + + if (slideInterpolatedStyle) { + return slideInterpolatedStyle(index, animatedValue, this.props); + } else if (this._shouldUseTinderLayout()) { + return tinderAnimatedStyles( + index, + animatedValue, + this.props, + layoutCardOffset + ); + } else if (this._shouldUseStackLayout()) { + return stackAnimatedStyles( + index, + animatedValue, + this.props, + layoutCardOffset + ); + } else if (this._shouldUseShiftLayout()) { + return shiftAnimatedStyles(index, animatedValue, this.props); + } else { + return defaultAnimatedStyles(index, animatedValue, this.props); + } + } + + _initPositionsAndInterpolators (props: CarouselProps = this.props) { + const { data, scrollInterpolator } = props; + const itemMainDimension = this._getItemMainDimension(); + + if (!data || !data.length) { + return; + } + + const interpolators: Animated.AnimatedInterpolation[] = []; + this._positions = []; + + this._getCustomData(props).forEach((_itemData, index) => { + const _index = this._getCustomIndex(index, props); + let animatedValue: Animated.AnimatedInterpolation; + + this._positions[index] = { + start: index * itemMainDimension, + end: index * itemMainDimension + itemMainDimension + }; + + if (!this._shouldAnimateSlides(props) || !this._scrollPos) { + animatedValue = new Animated.Value(1); + } else { + let interpolator; + + if (scrollInterpolator) { + interpolator = scrollInterpolator(_index, props); + } else if (this._shouldUseStackLayout()) { + interpolator = stackScrollInterpolator(_index, props); + } else if (this._shouldUseTinderLayout()) { + interpolator = tinderScrollInterpolator(_index, props); + } + + if ( + !interpolator || + !interpolator.inputRange || + !interpolator.outputRange + ) { + interpolator = defaultScrollInterpolator(_index, props); + } + + animatedValue = this._scrollPos.interpolate({ + ...interpolator, + extrapolate: 'clamp' + }); + } + + interpolators.push(animatedValue); + }); + + this.setState({ interpolators }); + } + + _hackActiveSlideAnimation (index: number, scrollValue = 1) { + const offset = this._getItemScrollOffset(index); + + if (!this._mounted || !this._carouselRef || typeof offset === 'undefined') { + return; + } + + const multiplier = this._currentScrollOffset === 0 ? 1 : -1; + const scrollDelta = scrollValue * multiplier; + + this._scrollTo({ offset: offset + scrollDelta, animated: false }); + + // @ts-expect-error setTimeout / clearTiemout is buggy :/ + clearTimeout(this._hackSlideAnimationTimeout); + this._hackSlideAnimationTimeout = setTimeout(() => { + this._scrollTo({ offset, animated: false }); + }, 1); // works randomly when set to '0' + } + + _repositionScroll (index: number, animated = false) { + const { data, loopClonesPerSide } = this.props; + const dataLength = data && data.length; + + if (typeof index === 'undefined' || !this._shouldRepositionScroll(index)) { + return; + } + + let repositionTo = index; + + if (index >= dataLength + loopClonesPerSide) { + repositionTo = index - dataLength; + } else if (index < loopClonesPerSide) { + repositionTo = index + dataLength; + } + + this._snapToItem(repositionTo, animated, false); + } + + _scrollTo ({ + offset, + index, + animated = true + }: { + offset: number; + index?: number; + animated: boolean; + }) { + const { vertical } = this.props; + const wrappedRef = this._getWrappedRef(); + if ( + !this._mounted || + !wrappedRef || + (typeof offset === 'undefined' && typeof index === 'undefined') + ) { + return; + } + + let scrollToOffset; + if (typeof index !== 'undefined') { + scrollToOffset = this._getItemScrollOffset(index); + } else { + scrollToOffset = offset; + } + + if (typeof scrollToOffset === 'undefined') { + return; + } + + const options = this._needsScrollView() ? + { + x: vertical ? 0 : offset, + y: vertical ? offset : 0, + animated + } : + { + offset, + animated + }; + + if (this._needsScrollView()) { + wrappedRef.scrollTo(options); + } else { + wrappedRef.scrollToOffset(options); + } + } + + _onTouchStart (event: GestureResponderEvent) { + const { onTouchStart } = this.props; + + // `onTouchStart` is fired even when `scrollEnabled` is set to `false` + if (this._getScrollEnabled() !== false && this._autoplaying) { + this.pauseAutoPlay(); + } + + onTouchStart && onTouchStart(event); + } + + _onTouchEnd (event: GestureResponderEvent) { + const { onTouchEnd } = this.props; + + if ( + this._getScrollEnabled() !== false && + this._autoplay && + !this._autoplaying + ) { + // This event is buggy on Android, so a fallback is provided in _onMomentumScrollEnd() + this.startAutoplay(); + } + + onTouchEnd && onTouchEnd(event); + } + + _onScroll (event: NativeSyntheticEvent) { + const { onScroll, onScrollIndexChanged } = this.props; + const scrollOffset = event ? + this._getScrollOffset(event) : + this._currentScrollOffset; + const nextActiveItem = this._getActiveItem(scrollOffset); + + this._currentScrollOffset = scrollOffset; + + if (nextActiveItem !== this._onScrollActiveItem) { + this._onScrollActiveItem = nextActiveItem; + onScrollIndexChanged && + onScrollIndexChanged(this._getDataIndex(nextActiveItem)); + } + + if (typeof onScroll === 'function' && event) { + onScroll(event); + } + } + + _onMomentumScrollEnd (event: NativeSyntheticEvent) { + const { autoplayDelay, onMomentumScrollEnd, onSnapToItem } = this.props; + const scrollOffset = event ? + this._getScrollOffset(event) : + this._currentScrollOffset; + const nextActiveItem = this._getActiveItem(scrollOffset); + const hasSnapped = this._isMultiple( + scrollOffset, + this.props.vertical ? this.props.itemHeight : this.props.itemWidth + ); + + // WARNING: everything in this condition will probably need to be called on _snapToItem as well because: + // 1. `onMomentumScrollEnd` won't be called if the scroll isn't animated + // 2. `onMomentumScrollEnd` won't be called at all on Android when scrolling programmatically + if (nextActiveItem !== this._activeItem) { + this._activeItem = nextActiveItem; + onSnapToItem && onSnapToItem(this._getDataIndex(nextActiveItem)); + + if (hasSnapped) { + this._repositionScroll(nextActiveItem); + } + } + + onMomentumScrollEnd && onMomentumScrollEnd(event); + + // The touchEnd event is buggy on Android, so this will serve as a fallback whenever needed + // https://github.com/facebook/react-native/issues/9439 + if (IS_ANDROID && this._autoplay && !this._autoplaying) { + // @ts-expect-error setTimeout / clearTiemout is buggy :/ + clearTimeout(this._enableAutoplayTimeout); + this._enableAutoplayTimeout = setTimeout(() => { + this.startAutoplay(); + }, autoplayDelay); + } + } + + _onLayout (event: LayoutChangeEvent) { + const { onLayout } = this.props; + + // Prevent unneeded actions during the first 'onLayout' (triggered on init) + if (this._onLayoutInitDone) { + this._initPositionsAndInterpolators(); + this._snapToItem(this._activeItem, false, false, true); + } else { + this._onLayoutInitDone = true; + } + + onLayout && onLayout(event); + } + + _snapToItem ( + index: number, + animated = true, + fireCallback = true, + forceScrollTo = false + ) { + const { onSnapToItem } = this.props; + const itemsLength = this._getCustomDataLength(); + const wrappedRef = this._getWrappedRef(); + + if (!itemsLength || !wrappedRef) { + return; + } + + if (!index || index < 0) { + index = 0; + } else if (itemsLength > 0 && index >= itemsLength) { + index = itemsLength - 1; + } + + if (index === this._activeItem && !forceScrollTo) { + return; + } + + const offset = this._getItemScrollOffset(index); + + if (offset === undefined) { + return; + } + + this._scrollTo({ offset, animated }); + + // On both platforms, `onMomentumScrollEnd` won't be triggered if the scroll isn't animated + // so we need to trigger the callback manually + // On Android `onMomentumScrollEnd` won't be triggered when scrolling programmatically + // Therefore everything critical needs to be manually called here as well, even though the timing might be off + const requiresManualTrigger = !animated || IS_ANDROID; + if (requiresManualTrigger) { + this._activeItem = index; + + if (fireCallback) { + onSnapToItem && onSnapToItem(this._getDataIndex(index)); + } + + // Repositioning on Android + if (IS_ANDROID && this._shouldRepositionScroll(index)) { + if (animated) { + this._androidRepositioningTimeout = setTimeout(() => { + // Without scroll animation, the behavior is completely buggy... + this._repositionScroll(index, true); + }, 400); // Approximate scroll duration on Android + } else { + this._repositionScroll(index); + } + } + } + } + + startAutoplay () { + const { autoplayInterval, autoplayDelay } = this.props; + this._autoplay = true; + + if (this._autoplaying) { + return; + } + + // @ts-expect-error setTimeout / clearTiemout is buggy :/ + clearTimeout(this._autoplayTimeout); + this._autoplayTimeout = setTimeout(() => { + this._autoplaying = true; + this._autoplayInterval = setInterval(() => { + if (this._autoplaying) { + this.snapToNext(); + } + }, autoplayInterval); + }, autoplayDelay); + } + + pauseAutoPlay () { + this._autoplaying = false; + // @ts-expect-error setTimeout / clearTiemout is buggy :/ + clearTimeout(this._autoplayTimeout); + // @ts-expect-error setTimeout / clearTiemout is buggy :/ + clearTimeout(this._enableAutoplayTimeout); + // @ts-expect-error setTimeout / clearTiemout is buggy :/ + clearInterval(this._autoplayInterval); + } + + stopAutoplay () { + this._autoplay = false; + this.pauseAutoPlay(); + } + + snapToItem (index: number, animated = true, fireCallback = true) { + if (!index || index < 0) { + index = 0; + } + + const positionIndex = this._getPositionIndex(index); + + if (positionIndex === this._activeItem) { + return; + } + + this._snapToItem(positionIndex, animated, fireCallback); + } + + snapToNext (animated = true, fireCallback = true) { + const itemsLength = this._getCustomDataLength(); + + let newIndex = this._activeItem + 1; + if (newIndex > itemsLength - 1) { + newIndex = 0; + } + this._snapToItem(newIndex, animated, fireCallback); + } + + snapToPrev (animated = true, fireCallback = true) { + const itemsLength = this._getCustomDataLength(); + + let newIndex = this._activeItem - 1; + if (newIndex < 0) { + newIndex = itemsLength - 1; + } + this._snapToItem(newIndex, animated, fireCallback); + } + + // https://github.com/facebook/react-native/issues/1831#issuecomment-231069668 + triggerRenderingHack (offset = 1) { + this._hackActiveSlideAnimation(this._activeItem, offset); + } + + _renderItem ({ item, index }: { item: TData; index: number }) { + const { interpolators } = this.state; + const { + keyExtractor, + slideStyle + } = this.props; + const animatedValue = interpolators && interpolators[index]; + + if (typeof animatedValue === 'undefined') { + return null; + } + + const animate = this._shouldAnimateSlides(); + const Component = animate ? Animated.View : View; + const animatedStyle = animate ? + this._getSlideInterpolatedStyle(index, animatedValue) : + {}; + const dataIndex = this._getDataIndex(index); + + const mainDimension = this.props.vertical ? + { height: this.props.itemHeight } : + { width: this.props.itemWidth }; + const specificProps = this._needsScrollView() ? + { + key: keyExtractor ? + keyExtractor(item, index) : + this._getKeyExtractor(item, index) + } : + {}; + + return ( + + {this.props.vertical ? this.props.renderItem({ item, index, dataIndex }, { + scrollPosition: this._scrollPos, + carouselRef: this._carouselRef, + vertical: this.props.vertical, + sliderHeight: this.props.sliderHeight, + itemHeight: this.props.itemHeight + }) : this.props.renderItem({ item, index, dataIndex }, { + scrollPosition: this._scrollPos, + carouselRef: this._carouselRef, + vertical: !!this.props.vertical, + sliderWidth: this.props.sliderWidth, + itemWidth: this.props.itemWidth + })} + + ); + } + + _getComponentOverridableProps () { + const { hideCarousel } = this.state; + const { loopClonesPerSide } = this.props; + const visibleItems = + Math.ceil( + this.props.vertical ? + this.props.sliderHeight / this.props.itemHeight : + this.props.sliderWidth / this.props.itemWidth + ) + 1; + const initialNumPerSide = this._enableLoop() ? loopClonesPerSide : 2; + const initialNumToRender = visibleItems + initialNumPerSide * 2; + const maxToRenderPerBatch = initialNumToRender + initialNumPerSide * 2; + const windowSize = maxToRenderPerBatch; + + const specificProps = !this._needsScrollView() ? + { + initialNumToRender, + maxToRenderPerBatch, + windowSize + // updateCellsBatchingPeriod + } : + {}; + + return { + ...specificProps, + automaticallyAdjustContentInsets: false, + decelerationRate: 'fast' as const, + directionalLockEnabled: true, + disableScrollViewPanResponder: false, // If set to `true`, touch events will be triggered too easily + inverted: this._needsRTLAdaptations(), + overScrollMode: 'never' as const, + pinchGestureEnabled: false, + pointerEvents: hideCarousel ? 'none' as const : 'auto' as const, + // removeClippedSubviews: !this._needsScrollView(), + // renderToHardwareTextureAndroid: true, + scrollsToTop: false, + showsHorizontalScrollIndicator: false, + showsVerticalScrollIndicator: false + }; + } + + _getComponentStaticProps () { + const { hideCarousel } = this.state; + const { + activeSlideAlignment, + CellRendererComponent, + containerCustomStyle, + contentContainerCustomStyle, + firstItem, + getItemLayout, + keyExtractor, + style, + useExperimentalSnap + } = this.props; + + const containerStyle = [ + // { overflow: 'hidden' }, + containerCustomStyle || style || {}, + hideCarousel ? { opacity: 0 } : {}, + this.props.vertical ? + { height: this.props.sliderHeight, flexDirection: 'column' as const } : // LTR hack; see https://github.com/facebook/react-native/issues/11960 + // and https://github.com/facebook/react-native/issues/13100#issuecomment-328986423 + { + width: this.props.sliderWidth, + flexDirection: this._needsRTLAdaptations() ? 'row-reverse' as const : 'row' as const + } + ]; + + const innerMarginStyle = this.props.vertical ? + { + paddingTop: this._getContainerInnerMargin(), + paddingBottom: this._getContainerInnerMargin(true) + } : + { + paddingLeft: this._getContainerInnerMargin(), + paddingRight: this._getContainerInnerMargin(true) + }; + + const contentContainerStyle = [ + !useExperimentalSnap ? innerMarginStyle : {}, + contentContainerCustomStyle || {} + ]; + + // WARNING: `snapToAlignment` won't work as intended because of the following: + // https://github.com/facebook/react-native/blob/d0871d0a9a373e1d3ac35da46c85c0d0e793116d/React/Views/ScrollView/RCTScrollView.m#L751-L755 + // - Snap points will be off + // - Slide animations will be off + // - Last items won't be set as active (no `onSnapToItem` callback) + // Recommended only with large slides and `activeSlideAlignment` set to `start` for the time being + const snapProps = useExperimentalSnap ? + { + // disableIntervalMomentum: true, // Slide ± one item at a time + snapToAlignment: activeSlideAlignment, + snapToInterval: this._getItemMainDimension() + } : + { + snapToOffsets: this._getSnapOffsets() + }; + + // Flatlist specifics + const specificProps = !this._needsScrollView() ? + { + CellRendererComponent: + CellRendererComponent || this._getCellRendererComponent, + getItemLayout: getItemLayout || this._getItemLayout, + initialScrollIndex: this._getFirstItem(firstItem), + keyExtractor: keyExtractor || this._getKeyExtractor, + numColumns: 1, + renderItem: this._renderItem + } : + {}; + + return { + ...specificProps, + ...snapProps, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ref: (c: any) => { + this._carouselRef = c as FlatList | ScrollView; + }, + contentContainerStyle: contentContainerStyle, + data: this._getCustomData(), + horizontal: !this.props.vertical, + scrollEventThrottle: 1, + style: containerStyle, + onLayout: this._onLayout, + onMomentumScrollEnd: this._onMomentumScrollEnd, + onScroll: this._onScrollHandler, + onTouchStart: this._onTouchStart, + onTouchEnd: this._onTouchEnd + }; + } + + render () { + const { data, renderItem, useScrollView } = this.props; + + if (!data || !renderItem) { + return null; + } + + const props = { + ...this._getComponentOverridableProps(), + ...this.props, + ...this._getComponentStaticProps() + }; + + const ScrollViewComponent = + typeof useScrollView === 'function' ? useScrollView : Animated.ScrollView; + + return this._needsScrollView() || !Animated.FlatList ? ( + + {this._getCustomData().map((item, index) => { + return this._renderItem({ item, index }); + })} + + ) : ( + // @ts-expect-error Seems complicated to make TS 100% happy, while sharing that many things between + // flatlist && scrollview implementation. I'll prob try to rewrite parts of the logic to overcome that. + + ); + } +} + +export default Carousel; diff --git a/src/carousel/types.ts b/src/carousel/types.ts new file mode 100644 index 000000000..46c1e5ee8 --- /dev/null +++ b/src/carousel/types.ts @@ -0,0 +1,103 @@ +import type { + StyleProp, + ViewStyle, + Animated, + NativeScrollEvent, + NativeSyntheticEvent, + FlatListProps, + ScrollView, + FlatList +} from 'react-native'; +import type { ReactNode } from 'react'; + +type CarouselBaseProps = { + data: TData[]; + activeSlideAlignment: 'center' | 'end' | 'start'; + activeSlideOffset: number; + apparitionDelay: number; + autoplay: boolean; + autoplayDelay: number; + autoplayInterval: number; + callbackOffsetMargin: number; + containerCustomStyle: StyleProp; + contentContainerCustomStyle: StyleProp; + enableSnap: boolean; + firstItem: number; + inactiveSlideOpacity: number; + inactiveSlideScale: number; + inactiveSlideShift: number; + layout: 'default' | 'stack' | 'tinder'; + layoutCardOffset?: number; + loop: boolean; + loopClonesPerSide: number; + scrollEnabled: boolean; + scrollInterpolator?: (index: number, props: CarouselBaseProps) => { + inputRange: number[]; + outputRange: number[]; + }; + slideInterpolatedStyle?: ( + index: number, + animatedValue: Animated.AnimatedInterpolation, + props: CarouselBaseProps + ) => StyleProp; + slideStyle?: Animated.WithAnimatedValue>; + shouldOptimizeUpdates: boolean; + useExperimentalSnap: boolean; + useScrollView: boolean | React.ComponentType; + onScroll?: (event: NativeSyntheticEvent) => void; + onScrollIndexChanged?: (index: number) => void; + onSnapToItem?: (index: number) => void; +}; + +type InheritedPropsFromFlatlist = Pick< + FlatListProps, + | 'onTouchStart' + | 'onTouchEnd' + | 'onMomentumScrollEnd' + | 'onLayout' + | 'keyExtractor' + | 'CellRendererComponent' + | 'getItemLayout' + | 'style' +>; + +type VerticalCarouselProps = { + vertical: true; + itemHeight: number; + sliderHeight: number; + renderItem: ( + baseData: { index: number; dataIndex: number; item: TData }, + parallaxData: { + scrollPosition: Animated.Value | undefined, + carouselRef: ScrollView | FlatList | null, + vertical: true, + itemHeight: number, + sliderHeight: number, + } + ) => ReactNode; +}; + +type HorizontalCarouselProps = { + vertical: false | undefined; + itemWidth: number; + sliderWidth: number; + renderItem: ( + baseData: { index: number; dataIndex: number; item: TData }, + parallaxData: { + scrollPosition: Animated.Value | undefined, + carouselRef: ScrollView | FlatList | null, + vertical: false, + itemWidth: number, + sliderWidth: number, + } + ) => ReactNode; +}; + +export type CarouselProps = CarouselBaseProps & + (HorizontalCarouselProps | VerticalCarouselProps) & + InheritedPropsFromFlatlist; + +export type CarouselState = { + hideCarousel: boolean; + interpolators: (Animated.Value | Animated.AnimatedInterpolation)[]; +}; diff --git a/src/index.js b/src/index.js deleted file mode 100644 index d93725329..000000000 --- a/src/index.js +++ /dev/null @@ -1,6 +0,0 @@ -import Carousel from './carousel/Carousel'; -import Pagination from './pagination/Pagination'; -import ParallaxImage from './parallaximage/ParallaxImage'; -import { getInputRangeFromIndexes } from './utils/animations'; - -export { Carousel as default, Pagination, ParallaxImage, getInputRangeFromIndexes }; diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 000000000..3e1a85498 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,13 @@ +import Carousel from './carousel/Carousel'; +import Pagination from './pagination/Pagination'; +import ParallaxImage, { ParallaxImageStatus } from './parallaximage/ParallaxImage'; +import { getInputRangeFromIndexes } from './utils/animations'; + +export { + Carousel as default, + Carousel, + Pagination, + ParallaxImage, + ParallaxImageStatus, + getInputRangeFromIndexes +}; diff --git a/src/pagination/Pagination.js b/src/pagination/Pagination.js deleted file mode 100644 index 30f817125..000000000 --- a/src/pagination/Pagination.js +++ /dev/null @@ -1,167 +0,0 @@ -import React, { PureComponent } from 'react'; -import { I18nManager, Platform, View, ViewPropTypes } from 'react-native'; -import PropTypes from 'prop-types'; -import PaginationDot from './PaginationDot'; -import styles from './Pagination.style'; - -const IS_IOS = Platform.OS === 'ios'; -const IS_RTL = I18nManager.isRTL; - -export default class Pagination extends PureComponent { - - static propTypes = { - activeDotIndex: PropTypes.number.isRequired, - dotsLength: PropTypes.number.isRequired, - activeOpacity: PropTypes.number, - carouselRef: PropTypes.object, - containerStyle: ViewPropTypes ? ViewPropTypes.style : View.propTypes.style, - dotColor: PropTypes.string, - dotContainerStyle: ViewPropTypes ? ViewPropTypes.style : View.propTypes.style, - dotElement: PropTypes.element, - dotStyle: ViewPropTypes ? ViewPropTypes.style : View.propTypes.style, - inactiveDotColor: PropTypes.string, - inactiveDotElement: PropTypes.element, - inactiveDotOpacity: PropTypes.number, - inactiveDotScale: PropTypes.number, - inactiveDotStyle: ViewPropTypes ? ViewPropTypes.style : View.propTypes.style, - renderDots: PropTypes.func, - tappableDots: PropTypes.bool, - vertical: PropTypes.bool, - accessibilityLabel: PropTypes.string, - animatedDuration: PropTypes.number, - animatedFriction: PropTypes.number, - animatedTension: PropTypes.number, - delayPressInDot: PropTypes.number - }; - - static defaultProps = { - inactiveDotOpacity: 0.5, - inactiveDotScale: 0.5, - tappableDots: false, - vertical: false, - animatedDuration: 250, - animatedFriction: 4, - animatedTension: 50, - delayPressInDot: 0 - } - - constructor (props) { - super(props); - - // Warnings - if ((props.dotColor && !props.inactiveDotColor) || (!props.dotColor && props.inactiveDotColor)) { - console.warn( - 'react-native-snap-carousel | Pagination: ' + - 'You need to specify both `dotColor` and `inactiveDotColor`' - ); - } - if ((props.dotElement && !props.inactiveDotElement) || (!props.dotElement && props.inactiveDotElement)) { - console.warn( - 'react-native-snap-carousel | Pagination: ' + - 'You need to specify both `dotElement` and `inactiveDotElement`' - ); - } - if (props.tappableDots && props.carouselRef === undefined) { - console.warn( - 'react-native-snap-carousel | Pagination: ' + - 'You must specify prop `carouselRef` when setting `tappableDots` to `true`' - ); - } - } - - _needsRTLAdaptations () { - const { vertical } = this.props; - return IS_RTL && !IS_IOS && !vertical; - } - - get _activeDotIndex () { - const { activeDotIndex, dotsLength } = this.props; - return this._needsRTLAdaptations() ? dotsLength - activeDotIndex - 1 : activeDotIndex; - } - - get dots () { - const { - activeOpacity, - carouselRef, - dotsLength, - dotColor, - dotContainerStyle, - dotElement, - dotStyle, - inactiveDotColor, - inactiveDotElement, - inactiveDotOpacity, - inactiveDotScale, - inactiveDotStyle, - renderDots, - tappableDots, - animatedDuration, - animatedFriction, - animatedTension, - delayPressInDot - } = this.props; - - if (renderDots) { - return renderDots(this._activeDotIndex, dotsLength, this); - } - - const DefaultDot = ; - - const dots = [...Array(dotsLength).keys()].map(i => { - const isActive = i === this._activeDotIndex; - return React.cloneElement( - (isActive ? dotElement : inactiveDotElement) || DefaultDot, - { - key: `pagination-dot-${i}`, - active: isActive, - index: i - } - ); - }); - - return dots; - } - - render () { - const { dotsLength, containerStyle, vertical, accessibilityLabel } = this.props; - - if (!dotsLength || dotsLength < 2) { - return false; - } - - const style = [ - styles.sliderPagination, - { flexDirection: vertical ? - 'column' : - (this._needsRTLAdaptations() ? 'row-reverse' : 'row') - }, - containerStyle || {} - ]; - - return ( - - { this.dots } - - ); - } -} diff --git a/src/pagination/Pagination.style.js b/src/pagination/Pagination.style.ts similarity index 100% rename from src/pagination/Pagination.style.js rename to src/pagination/Pagination.style.ts diff --git a/src/pagination/Pagination.tsx b/src/pagination/Pagination.tsx new file mode 100644 index 000000000..2447c48bb --- /dev/null +++ b/src/pagination/Pagination.tsx @@ -0,0 +1,194 @@ +import React, { PureComponent, ReactElement } from 'react'; +import { + I18nManager, + Platform, + View, + StyleProp, + ViewStyle +} from 'react-native'; +import PaginationDot from './PaginationDot'; +import styles from './Pagination.style'; +import type Carousel from 'src/carousel/Carousel'; + +const IS_IOS = Platform.OS === 'ios'; +const IS_RTL = I18nManager.isRTL; + +type PaginationProps = { + activeDotIndex: number; + dotsLength: number; + activeOpacity?: number; + carouselRef?: Carousel | null; + containerStyle?: StyleProp; + dotColor?: string; + dotContainerStyle?: StyleProp; + dotElement?: ReactElement; + dotStyle?: StyleProp; + inactiveDotColor?: string; + inactiveDotElement?: ReactElement; + inactiveDotOpacity: number; + inactiveDotScale: number; + inactiveDotStyle?: StyleProp; + renderDots?: ( + activeIndex: number, + length: number, + context: Pagination + ) => ReactElement; + tappableDots: boolean; + vertical: boolean; + accessibilityLabel?: string; + animatedDuration: number; + animatedFriction: number; + animatedTension: number; + delayPressInDot: number; +}; + +export default class Pagination extends PureComponent> { + static defaultProps = { + inactiveDotOpacity: 0.5, + inactiveDotScale: 0.5, + tappableDots: false, + vertical: false, + animatedDuration: 250, + animatedFriction: 4, + animatedTension: 50, + delayPressInDot: 0 + }; + + constructor (props: PaginationProps) { + super(props); + + // Warnings + if ( + (props.dotColor && !props.inactiveDotColor) || + (!props.dotColor && props.inactiveDotColor) + ) { + console.warn( + 'react-native-snap-carousel | Pagination: ' + + 'You need to specify both `dotColor` and `inactiveDotColor`' + ); + } + if ( + (props.dotElement && !props.inactiveDotElement) || + (!props.dotElement && props.inactiveDotElement) + ) { + console.warn( + 'react-native-snap-carousel | Pagination: ' + + 'You need to specify both `dotElement` and `inactiveDotElement`' + ); + } + if (props.tappableDots && props.carouselRef === undefined) { + console.warn( + 'react-native-snap-carousel | Pagination: ' + + 'You must specify prop `carouselRef` when setting `tappableDots` to `true`' + ); + } + } + + _needsRTLAdaptations () { + const { vertical } = this.props; + return IS_RTL && !IS_IOS && !vertical; + } + + get _activeDotIndex () { + const { activeDotIndex, dotsLength } = this.props; + return this._needsRTLAdaptations() ? + dotsLength - activeDotIndex - 1 : + activeDotIndex; + } + + get dots () { + const { + activeOpacity, + carouselRef, + dotsLength, + dotColor, + dotContainerStyle, + dotElement, + dotStyle, + inactiveDotColor, + inactiveDotElement, + inactiveDotOpacity, + inactiveDotScale, + inactiveDotStyle, + renderDots, + tappableDots, + animatedDuration, + animatedFriction, + animatedTension, + delayPressInDot + } = this.props; + + if (renderDots) { + return renderDots(this._activeDotIndex, dotsLength, this); + } + + const DefaultDot = ( + + ); + + const dots = [...Array(dotsLength).keys()].map((i) => { + const isActive = i === this._activeDotIndex; + return React.cloneElement( + (isActive ? dotElement : inactiveDotElement) || DefaultDot, + { + key: `pagination-dot-${i}`, + active: isActive, + index: i + } + ); + }); + + return dots; + } + + render () { + const { + dotsLength, + containerStyle, + vertical, + accessibilityLabel + } = this.props; + + if (!dotsLength || dotsLength < 2) { + return false; + } + + const style = [ + styles.sliderPagination, + { + flexDirection: vertical ? + ('column' as const) : + this._needsRTLAdaptations() ? + ('row-reverse' as const) : + ('row' as const) + }, + containerStyle || {} + ]; + + return ( + + {this.dots} + + ); + } +} diff --git a/src/pagination/PaginationDot.js b/src/pagination/PaginationDot.tsx similarity index 52% rename from src/pagination/PaginationDot.js rename to src/pagination/PaginationDot.tsx index 3b708b00c..8ea5c47bd 100644 --- a/src/pagination/PaginationDot.js +++ b/src/pagination/PaginationDot.tsx @@ -1,30 +1,44 @@ -import React, { PureComponent } from 'react'; -import { View, Animated, Easing, TouchableOpacity, ViewPropTypes } from 'react-native'; -import PropTypes from 'prop-types'; +import React, { PureComponent, RefObject } from 'react'; +import { + Animated, + Easing, + TouchableOpacity, + StyleProp, + ViewStyle +} from 'react-native'; import styles from './Pagination.style'; - -export default class PaginationDot extends PureComponent { - - static propTypes = { - inactiveOpacity: PropTypes.number.isRequired, - inactiveScale: PropTypes.number.isRequired, - active: PropTypes.bool, - activeOpacity: PropTypes.number, - animatedDuration: PropTypes.number, - animatedFriction: PropTypes.number, - animatedTension: PropTypes.number, - carouselRef: PropTypes.object, - color: PropTypes.string, - containerStyle: ViewPropTypes ? ViewPropTypes.style : View.propTypes.style, - delayPressInDot: PropTypes.number, - inactiveColor: PropTypes.string, - inactiveStyle: ViewPropTypes ? ViewPropTypes.style : View.propTypes.style, - index: PropTypes.number, - style: ViewPropTypes ? ViewPropTypes.style : View.propTypes.style, - tappable: PropTypes.bool - }; - - constructor (props) { +import type Carousel from 'src/carousel/Carousel'; + +type PaginationDotProps = { + inactiveOpacity: number; + inactiveScale: number; + active?: boolean; + activeOpacity?: number; + animatedDuration?: number; + animatedFriction?: number; + animatedTension?: number; + carouselRef?: Carousel | RefObject> | null; + color?: string; + containerStyle?: StyleProp; + delayPressInDot?: number; + inactiveColor?: string; + inactiveStyle?: StyleProp; + index?: number; + style?: StyleProp; + tappable?: boolean; +}; + +type PaginationDotState = { + animColor: Animated.Value; + animOpacity: Animated.Value; + animTransform: Animated.Value; +}; + +export default class PaginationDot extends PureComponent< + PaginationDotProps, + PaginationDotState +> { + constructor (props: PaginationDotProps) { super(props); this.state = { animColor: new Animated.Value(0), @@ -39,7 +53,7 @@ export default class PaginationDot extends PureComponent { } } - componentDidUpdate (prevProps) { + componentDidUpdate (prevProps: PaginationDotProps) { if (prevProps.active !== this.props.active) { this._animate(this.props.active ? 1 : 0); } @@ -55,7 +69,7 @@ export default class PaginationDot extends PureComponent { useNativeDriver: !this._shouldAnimateColor }; - let animations = [ + const animations = [ Animated.timing(animOpacity, { easing: Easing.linear, duration: animatedDuration, @@ -69,10 +83,12 @@ export default class PaginationDot extends PureComponent { ]; if (this._shouldAnimateColor) { - animations.push(Animated.timing(animColor, { - easing: Easing.linear, - ...commonProperties - })); + animations.push( + Animated.timing(animColor, { + easing: Easing.linear, + ...commonProperties + }) + ); } Animated.parallel(animations).start(); @@ -106,19 +122,24 @@ export default class PaginationDot extends PureComponent { inputRange: [0, 1], outputRange: [inactiveOpacity, 1] }), - transform: [{ - scale: animTransform.interpolate({ - inputRange: [0, 1], - outputRange: [inactiveScale, 1] - }) - }] + transform: [ + { + scale: animTransform.interpolate({ + inputRange: [0, 1], + outputRange: [inactiveScale, 1] + }) + } + ] }; - const animatedColor = this._shouldAnimateColor ? { - backgroundColor: animColor.interpolate({ - inputRange: [0, 1], - outputRange: [inactiveColor, color] - }) - } : {}; + const animatedColor = + this._shouldAnimateColor && inactiveColor && color ? + { + backgroundColor: animColor.interpolate({ + inputRange: [0, 1], + outputRange: [inactiveColor, color] + }) + } : + {}; const dotContainerStyle = [ styles.sliderPaginationDotContainer, @@ -133,17 +154,25 @@ export default class PaginationDot extends PureComponent { animatedColor ]; - const onPress = tappable ? () => { - try { - const currentRef = carouselRef.current || carouselRef; - currentRef._snapToItem(currentRef._getPositionIndex(index)); - } catch (error) { - console.warn( - 'react-native-snap-carousel | Pagination: ' + - '`carouselRef` has to be a Carousel ref.\n' + error - ); - } - } : undefined; + const onPress = + tappable && (!!index || index === 0) ? + () => { + try { + const currentRef = + carouselRef && 'current' in carouselRef ? + carouselRef.current : + carouselRef; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + currentRef!._snapToItem(currentRef!._getPositionIndex(index)); + } catch (error) { + console.warn( + 'react-native-snap-carousel | Pagination: ' + + '`carouselRef` has to be a Carousel ref.\n' + + error + ); + } + } : + undefined; return ( - itemHeight: PropTypes.number, // passed from - itemWidth: PropTypes.number, // passed from - scrollPosition: PropTypes.object, // passed from - sliderHeight: PropTypes.number, // passed from - sliderWidth: PropTypes.number, // passed from - vertical: PropTypes.bool, // passed from - containerStyle: ViewPropTypes ? ViewPropTypes.style : View.propTypes.style, - dimensions: PropTypes.shape({ - width: PropTypes.number, - height: PropTypes.number - }), - fadeDuration: PropTypes.number, - parallaxFactor: PropTypes.number, - showSpinner: PropTypes.bool, - spinnerColor: PropTypes.string, - AnimatedImageComponent: PropTypes.oneOfType([ - PropTypes.func, - PropTypes.object - ]) - }; - - static defaultProps = { - containerStyle: {}, - fadeDuration: 500, - parallaxFactor: 0.3, - showSpinner: true, - spinnerColor: 'rgba(0, 0, 0, 0.4)', - AnimatedImageComponent: Animated.Image - } - - constructor (props) { - super(props); - this.state = { - offset: 0, - width: 0, - height: 0, - status: 1, // 1 -> loading; 2 -> loaded // 3 -> transition finished; 4 -> error - animOpacity: new Animated.Value(0) - }; - this._onLoad = this._onLoad.bind(this); - this._onError = this._onError.bind(this); - this._measureLayout = this._measureLayout.bind(this); - } - - setNativeProps (nativeProps) { - this._container.setNativeProps(nativeProps); - } - - componentDidMount () { - this._mounted = true; - - setTimeout(() => { - this._measureLayout(); - }, 0); - } - - componentWillUnmount () { - this._mounted = false; - } - - _measureLayout () { - if (this._container) { - const { - dimensions, - vertical, - carouselRef, - sliderWidth, - sliderHeight, - itemWidth, - itemHeight - } = this.props; - - if (carouselRef) { - this._container.measureLayout( - findNodeHandle(carouselRef), - (x, y, width, height, pageX, pageY) => { - const offset = vertical ? - y - ((sliderHeight - itemHeight) / 2) : - x - ((sliderWidth - itemWidth) / 2); - - this.setState({ - offset: offset, - width: dimensions && dimensions.width ? - dimensions.width : - Math.ceil(width), - height: dimensions && dimensions.height ? - dimensions.height : - Math.ceil(height) - }); - } - ); - } - } - } - - _onLoad (event) { - const { animOpacity } = this.state; - const { fadeDuration, onLoad } = this.props; - - if (!this._mounted) { - return; - } - - this.setState({ status: 2 }); - - if (onLoad) { - onLoad(event); - } - - Animated.timing(animOpacity, { - toValue: 1, - duration: fadeDuration, - easing: Easing.out(Easing.quad), - isInteraction: false, - useNativeDriver: true - }).start(() => { - this.setState({ status: 3 }); - }); - } - - // If arg is missing from method signature, it just won't be called - _onError (event) { - const { onError } = this.props; - - this.setState({ status: 4 }); - - if (onError) { - onError(event); - } - } - - get image () { - const { status, animOpacity, offset, width, height } = this.state; - const { - scrollPosition, - dimensions, - vertical, - sliderWidth, - sliderHeight, - parallaxFactor, - style, - AnimatedImageComponent, - ...other - } = this.props; - - const parallaxPadding = (vertical ? height : width) * parallaxFactor; - const requiredStyles = { position: 'relative' }; - const dynamicStyles = { - width: vertical ? width : width + parallaxPadding * 2, - height: vertical ? height + parallaxPadding * 2 : height, - opacity: animOpacity, - transform: scrollPosition ? [ - { - translateX: !vertical ? scrollPosition.interpolate({ - inputRange: [offset - sliderWidth, offset + sliderWidth], - outputRange: [-parallaxPadding, parallaxPadding], - extrapolate: 'clamp' - }) : 0 - }, - { - translateY: vertical ? scrollPosition.interpolate({ - inputRange: [offset - sliderHeight, offset + sliderHeight], - outputRange: [-parallaxPadding, parallaxPadding], - extrapolate: 'clamp' - }) : 0 - } - ] : [] - }; - - return ( - - ); - } - - get spinner () { - const { status } = this.state; - const { showSpinner, spinnerColor } = this.props; - - return status === 1 && showSpinner ? ( - - - - ) : false; - } - - render () { - const { containerStyle } = this.props; - - return ( - { this._container = c; }} - pointerEvents={'none'} - style={[containerStyle, styles.container]} - onLayout={this._measureLayout} - > - { this.image } - { this.spinner } - - ); - } -} diff --git a/src/parallaximage/ParallaxImage.style.js b/src/parallaximage/ParallaxImage.style.ts similarity index 88% rename from src/parallaximage/ParallaxImage.style.js rename to src/parallaximage/ParallaxImage.style.ts index a71ea4757..6c0383617 100644 --- a/src/parallaximage/ParallaxImage.style.js +++ b/src/parallaximage/ParallaxImage.style.ts @@ -9,8 +9,8 @@ export default StyleSheet.create({ image: { position: 'relative', resizeMode: 'cover', - width: null, - height: null + width: undefined, + height: undefined }, loaderContainer: { ...StyleSheet.absoluteFillObject, diff --git a/src/parallaximage/ParallaxImage.tsx b/src/parallaximage/ParallaxImage.tsx new file mode 100644 index 000000000..bac86b263 --- /dev/null +++ b/src/parallaximage/ParallaxImage.tsx @@ -0,0 +1,264 @@ +// Parallax effect inspired by https://github.com/oblador/react-native-parallax/ + +import React, { Component } from 'react'; +import { + View, + Animated, + Easing, + ActivityIndicator, + findNodeHandle, + ImageProps, + StyleProp, + ViewStyle, + NativeSyntheticEvent, + ImageLoadEventData, + ImageErrorEventData +} from 'react-native'; +import styles from './ParallaxImage.style'; + +type VerticalProps = { + vertical: true; + sliderHeight: number; // passed from + itemHeight: number; // passed from +} +type HorizontalProps = { + vertical: false; + sliderWidth: number; // passed from + itemWidth: number; // passed from +} + +type ParallaxImageProps = { + carouselRef: Parameters[0]; // passed from + scrollPosition: Animated.Value | undefined; // passed from + containerStyle: StyleProp; + dimensions?: { + width: number; + height: number; + }; + fadeDuration: number; + parallaxFactor: number; + showSpinner: boolean; + spinnerColor: string; + AnimatedImageComponent: typeof Animated.Image; +} & ImageProps & (VerticalProps | HorizontalProps); + +export enum ParallaxImageStatus { + 'LOADING' = 1, + 'LOADED' = 2, + 'TRANSITION_FINISHED' = 3, + 'ERROR' = 4 +} + +type ParallaxImageState = { + offset: number; + width: number; + height: number; + status: ParallaxImageStatus; + animOpacity: Animated.Value; +}; + +export default class ParallaxImage extends Component< + ParallaxImageProps, + ParallaxImageState +> { + static defaultProps = { + containerStyle: {}, + fadeDuration: 500, + parallaxFactor: 0.3, + showSpinner: true, + spinnerColor: 'rgba(0, 0, 0, 0.4)', + AnimatedImageComponent: Animated.Image + }; + + _container?: View | null; + _mounted?: boolean; + + constructor (props: ParallaxImageProps) { + super(props); + this.state = { + offset: 0, + width: 0, + height: 0, + status: ParallaxImageStatus.LOADING, + animOpacity: new Animated.Value(0) + }; + this._onLoad = this._onLoad.bind(this); + this._onError = this._onError.bind(this); + this._measureLayout = this._measureLayout.bind(this); + } + + setNativeProps (nativeProps: { [key: string]: unknown }) { + this._container?.setNativeProps(nativeProps); + } + + componentDidMount () { + this._mounted = true; + + setTimeout(() => { + this._measureLayout(); + }, 0); + } + + componentWillUnmount () { + this._mounted = false; + } + + _measureLayout () { + if (this._container) { + const { + dimensions, + carouselRef + } = this.props; + + const nodeHandle = findNodeHandle(carouselRef); + + if (carouselRef && nodeHandle) { + this._container.measureLayout( + nodeHandle, + (x, y, width, height) => { + const offset = this.props.vertical ? + y - (this.props.sliderHeight - this.props.itemHeight) / 2 : + x - (this.props.sliderWidth - this.props.itemWidth) / 2; + + this.setState({ + offset: offset, + width: + dimensions && dimensions.width ? + dimensions.width : + Math.ceil(width), + height: + dimensions && dimensions.height ? + dimensions.height : + Math.ceil(height) + }); + }, + // eslint-disable-next-line @typescript-eslint/no-empty-function + () => {} + ); + } + } + } + + _onLoad (event: NativeSyntheticEvent) { + const { animOpacity } = this.state; + const { fadeDuration, onLoad } = this.props; + + if (!this._mounted) { + return; + } + + this.setState({ status: ParallaxImageStatus.LOADED }); + + if (onLoad) { + onLoad(event); + } + + Animated.timing(animOpacity, { + toValue: 1, + duration: fadeDuration, + easing: Easing.out(Easing.quad), + isInteraction: false, + useNativeDriver: true + }).start(() => { + this.setState({ status: ParallaxImageStatus.TRANSITION_FINISHED }); + }); + } + + // If arg is missing from method signature, it just won't be called + _onError (event: NativeSyntheticEvent) { + const { onError } = this.props; + + this.setState({ status: ParallaxImageStatus.ERROR }); + + if (onError) { + onError(event); + } + } + + get image () { + const { status, animOpacity, offset, width, height } = this.state; + const { + scrollPosition, + // False positive :( other doesn't have the dimension key + // eslint-disable-next-line @typescript-eslint/no-unused-vars + dimensions, + parallaxFactor, + style, + AnimatedImageComponent, + ...other + } = this.props; + const parallaxPadding = (this.props.vertical ? height : width) * parallaxFactor; + const requiredStyles = { position: 'relative' as const }; + const dynamicStyles = { + width: this.props.vertical ? width : width + parallaxPadding * 2, + height: this.props.vertical ? height + parallaxPadding * 2 : height, + opacity: animOpacity, + transform: scrollPosition ? + [ + { + translateX: !this.props.vertical ? + scrollPosition.interpolate({ + inputRange: [offset - this.props.sliderWidth, offset + this.props.sliderWidth], + outputRange: [-parallaxPadding, parallaxPadding], + extrapolate: 'clamp' + }) : + 0 + }, + { + translateY: this.props.vertical ? + scrollPosition.interpolate({ + inputRange: [offset - this.props.sliderHeight, offset + this.props.sliderHeight], + outputRange: [-parallaxPadding, parallaxPadding], + extrapolate: 'clamp' + }) : + 0 + } + ] : + [] + }; + + return ( + + ); + } + + get spinner () { + const { status } = this.state; + const { showSpinner, spinnerColor } = this.props; + + return status === ParallaxImageStatus.LOADING && showSpinner ? ( + + + + ) : ( + false + ); + } + + render () { + const { containerStyle } = this.props; + + return ( + { + this._container = c; + }} + pointerEvents='none' + style={[containerStyle, styles.container]} + onLayout={this._measureLayout} + > + {this.image} + {this.spinner} + + ); + } +} diff --git a/src/utils/animations.js b/src/utils/animations.js deleted file mode 100644 index bdb58b62d..000000000 --- a/src/utils/animations.js +++ /dev/null @@ -1,325 +0,0 @@ -import { Platform } from 'react-native'; - -const IS_ANDROID = Platform.OS === 'android'; - -// Get scroll interpolator's input range from an array of slide indexes -// Indexes are relative to the current active slide (index 0) -// For example, using [3, 2, 1, 0, -1] will return: -// [ -// (index - 3) * sizeRef, // active + 3 -// (index - 2) * sizeRef, // active + 2 -// (index - 1) * sizeRef, // active + 1 -// index * sizeRef, // active -// (index + 1) * sizeRef // active - 1 -// ] -export function getInputRangeFromIndexes (range, index, carouselProps) { - const sizeRef = carouselProps.vertical ? carouselProps.itemHeight : carouselProps.itemWidth; - let inputRange = []; - - for (let i = 0; i < range.length; i++) { - inputRange.push((index - range[i]) * sizeRef); - } - - return inputRange; -} - -// Default behavior -// Scale and/or opacity effect -// Based on props 'inactiveSlideOpacity' and 'inactiveSlideScale' -export function defaultScrollInterpolator (index, carouselProps) { - const range = [1, 0, -1]; - const inputRange = getInputRangeFromIndexes(range, index, carouselProps); - const outputRange = [0, 1, 0]; - - return { inputRange, outputRange }; -} -export function defaultAnimatedStyles (index, animatedValue, carouselProps) { - let animatedOpacity = {}; - let animatedScale = {}; - - if (carouselProps.inactiveSlideOpacity < 1) { - animatedOpacity = { - opacity: animatedValue.interpolate({ - inputRange: [0, 1], - outputRange: [carouselProps.inactiveSlideOpacity, 1] - }) - }; - } - - if (carouselProps.inactiveSlideScale < 1) { - animatedScale = { - transform: [{ - scale: animatedValue.interpolate({ - inputRange: [0, 1], - outputRange: [carouselProps.inactiveSlideScale, 1] - }) - }] - }; - } - - return { - ...animatedOpacity, - ...animatedScale - }; -} - -// Shift animation -// Same as the default one, but the active slide is also shifted up or down -// Based on prop 'inactiveSlideShift' -export function shiftAnimatedStyles (index, animatedValue, carouselProps) { - let animatedOpacity = {}; - let animatedScale = {}; - let animatedTranslate = {}; - - if (carouselProps.inactiveSlideOpacity < 1) { - animatedOpacity = { - opacity: animatedValue.interpolate({ - inputRange: [0, 1], - outputRange: [carouselProps.inactiveSlideOpacity, 1] - }) - }; - } - - if (carouselProps.inactiveSlideScale < 1) { - animatedScale = { - scale: animatedValue.interpolate({ - inputRange: [0, 1], - outputRange: [carouselProps.inactiveSlideScale, 1] - }) - }; - } - - if (carouselProps.inactiveSlideShift !== 0) { - const translateProp = carouselProps.vertical ? 'translateX' : 'translateY'; - animatedTranslate = { - [translateProp]: animatedValue.interpolate({ - inputRange: [0, 1], - outputRange: [carouselProps.inactiveSlideShift, 0] - }) - }; - } - - return { - ...animatedOpacity, - transform: [ - { ...animatedScale }, - { ...animatedTranslate } - ] - }; -} - -// Stack animation -// Imitate a deck/stack of cards (see #195) -// WARNING: The effect had to be visually inverted on Android because this OS doesn't honor the `zIndex`property -// This means that the item with the higher zIndex (and therefore the tap receiver) remains the one AFTER the currently active item -// The `elevation` property compensates for that only visually, which is not good enough -export function stackScrollInterpolator (index, carouselProps) { - const range = IS_ANDROID ? - [1, 0, -1, -2, -3] : - [3, 2, 1, 0, -1]; - const inputRange = getInputRangeFromIndexes(range, index, carouselProps); - const outputRange = range; - - return { inputRange, outputRange }; -} -export function stackAnimatedStyles (index, animatedValue, carouselProps, cardOffset) { - const sizeRef = carouselProps.vertical ? carouselProps.itemHeight : carouselProps.itemWidth; - const translateProp = carouselProps.vertical ? 'translateY' : 'translateX'; - - const card1Scale = 0.9; - const card2Scale = 0.8; - - cardOffset = !cardOffset && cardOffset !== 0 ? 18 : cardOffset; - - const getTranslateFromScale = (cardIndex, scale) => { - const centerFactor = 1 / scale * cardIndex; - const centeredPosition = -Math.round(sizeRef * centerFactor); - const edgeAlignment = Math.round((sizeRef - (sizeRef * scale)) / 2); - const offset = Math.round(cardOffset * Math.abs(cardIndex) / scale); - - return IS_ANDROID ? - centeredPosition - edgeAlignment - offset : - centeredPosition + edgeAlignment + offset; - }; - - const opacityOutputRange = carouselProps.inactiveSlideOpacity === 1 ? [1, 1, 1, 0] : [1, 0.75, 0.5, 0]; - - return IS_ANDROID ? { - // elevation: carouselProps.data.length - index, // fix zIndex bug visually, but not from a logic point of view - opacity: animatedValue.interpolate({ - inputRange: [-3, -2, -1, 0], - outputRange: opacityOutputRange.reverse(), - extrapolate: 'clamp' - }), - transform: [{ - scale: animatedValue.interpolate({ - inputRange: [-2, -1, 0, 1], - outputRange: [card2Scale, card1Scale, 1, card1Scale], - extrapolate: 'clamp' - }) - }, { - [translateProp]: animatedValue.interpolate({ - inputRange: [-3, -2, -1, 0, 1], - outputRange: [ - getTranslateFromScale(-3, card2Scale), - getTranslateFromScale(-2, card2Scale), - getTranslateFromScale(-1, card1Scale), - 0, - sizeRef * 0.5 - ], - extrapolate: 'clamp' - }) - }] - } : { - zIndex: carouselProps.data.length - index, - opacity: animatedValue.interpolate({ - inputRange: [0, 1, 2, 3], - outputRange: opacityOutputRange, - extrapolate: 'clamp' - }), - transform: [{ - scale: animatedValue.interpolate({ - inputRange: [-1, 0, 1, 2], - outputRange: [card1Scale, 1, card1Scale, card2Scale], - extrapolate: 'clamp' - }) - }, { - [translateProp]: animatedValue.interpolate({ - inputRange: [-1, 0, 1, 2, 3], - outputRange: [ - -sizeRef * 0.5, - 0, - getTranslateFromScale(1, card1Scale), - getTranslateFromScale(2, card2Scale), - getTranslateFromScale(3, card2Scale) - ], - extrapolate: 'clamp' - }) - }] - }; -} - -// Tinder animation -// Imitate the popular Tinder layout -// WARNING: The effect had to be visually inverted on Android because this OS doesn't honor the `zIndex`property -// This means that the item with the higher zIndex (and therefore the tap receiver) remains the one AFTER the currently active item -// The `elevation` property compensates for that only visually, which is not good enough -export function tinderScrollInterpolator (index, carouselProps) { - const range = IS_ANDROID ? - [1, 0, -1, -2, -3] : - [3, 2, 1, 0, -1]; - const inputRange = getInputRangeFromIndexes(range, index, carouselProps); - const outputRange = range; - - return { inputRange, outputRange }; -} -export function tinderAnimatedStyles (index, animatedValue, carouselProps, cardOffset) { - const sizeRef = carouselProps.vertical ? carouselProps.itemHeight : carouselProps.itemWidth; - const mainTranslateProp = carouselProps.vertical ? 'translateY' : 'translateX'; - const secondaryTranslateProp = carouselProps.vertical ? 'translateX' : 'translateY'; - - const card1Scale = 0.96; - const card2Scale = 0.92; - const card3Scale = 0.88; - - const peekingCardsOpacity = IS_ANDROID ? 0.92 : 1; - - cardOffset = !cardOffset && cardOffset !== 0 ? 9 : cardOffset; - - const getMainTranslateFromScale = (cardIndex, scale) => { - const centerFactor = 1 / scale * cardIndex; - return -Math.round(sizeRef * centerFactor); - }; - - const getSecondaryTranslateFromScale = (cardIndex, scale) => { - return Math.round(cardOffset * Math.abs(cardIndex) / scale); - }; - - return IS_ANDROID ? { - // elevation: carouselProps.data.length - index, // fix zIndex bug visually, but not from a logic point of view - opacity: animatedValue.interpolate({ - inputRange: [-3, -2, -1, 0, 1], - outputRange: [0, peekingCardsOpacity, peekingCardsOpacity, 1, 0], - extrapolate: 'clamp' - }), - transform: [{ - scale: animatedValue.interpolate({ - inputRange: [-3, -2, -1, 0], - outputRange: [card3Scale, card2Scale, card1Scale, 1], - extrapolate: 'clamp' - }) - }, { - rotate: animatedValue.interpolate({ - inputRange: [0, 1], - outputRange: ['0deg', '22deg'], - extrapolate: 'clamp' - }) - }, { - [mainTranslateProp]: animatedValue.interpolate({ - inputRange: [-3, -2, -1, 0, 1], - outputRange: [ - getMainTranslateFromScale(-3, card3Scale), - getMainTranslateFromScale(-2, card2Scale), - getMainTranslateFromScale(-1, card1Scale), - 0, - sizeRef * 1.1 - ], - extrapolate: 'clamp' - }) - }, { - [secondaryTranslateProp]: animatedValue.interpolate({ - inputRange: [-3, -2, -1, 0], - outputRange: [ - getSecondaryTranslateFromScale(-3, card3Scale), - getSecondaryTranslateFromScale(-2, card2Scale), - getSecondaryTranslateFromScale(-1, card1Scale), - 0 - ], - extrapolate: 'clamp' - }) - }] - } : { - zIndex: carouselProps.data.length - index, - opacity: animatedValue.interpolate({ - inputRange: [-1, 0, 1, 2, 3], - outputRange: [0, 1, peekingCardsOpacity, peekingCardsOpacity, 0], - extrapolate: 'clamp' - }), - transform: [{ - scale: animatedValue.interpolate({ - inputRange: [0, 1, 2, 3], - outputRange: [1, card1Scale, card2Scale, card3Scale], - extrapolate: 'clamp' - }) - }, { - rotate: animatedValue.interpolate({ - inputRange: [-1, 0], - outputRange: ['-22deg', '0deg'], - extrapolate: 'clamp' - }) - }, { - [mainTranslateProp]: animatedValue.interpolate({ - inputRange: [-1, 0, 1, 2, 3], - outputRange: [ - -sizeRef * 1.1, - 0, - getMainTranslateFromScale(1, card1Scale), - getMainTranslateFromScale(2, card2Scale), - getMainTranslateFromScale(3, card3Scale) - ], - extrapolate: 'clamp' - }) - }, { - [secondaryTranslateProp]: animatedValue.interpolate({ - inputRange: [0, 1, 2, 3], - outputRange: [ - 0, - getSecondaryTranslateFromScale(1, card1Scale), - getSecondaryTranslateFromScale(2, card2Scale), - getSecondaryTranslateFromScale(3, card3Scale) - ], - extrapolate: 'clamp' - }) - }] - }; -} diff --git a/src/utils/animations.ts b/src/utils/animations.ts new file mode 100644 index 000000000..bf1c50f71 --- /dev/null +++ b/src/utils/animations.ts @@ -0,0 +1,383 @@ +import { Platform, Animated } from 'react-native'; +import type { CarouselProps } from 'src/carousel/types'; + +const IS_ANDROID = Platform.OS === 'android'; + +// Get scroll interpolator's input range from an array of slide indexes +// Indexes are relative to the current active slide (index 0) +// For example, using [3, 2, 1, 0, -1] will return: +// [ +// (index - 3) * sizeRef, // active + 3 +// (index - 2) * sizeRef, // active + 2 +// (index - 1) * sizeRef, // active + 1 +// index * sizeRef, // active +// (index + 1) * sizeRef // active - 1 +// ] +export function getInputRangeFromIndexes ( + range: number[], + index: number, + carouselProps: CarouselProps +) { + const sizeRef = carouselProps.vertical ? + carouselProps.itemHeight : + carouselProps.itemWidth; + const inputRange = []; + + for (let i = 0; i < range.length; i++) { + inputRange.push((index - range[i]) * sizeRef); + } + + return inputRange; +} + +// Default behavior +// Scale and/or opacity effect +// Based on props 'inactiveSlideOpacity' and 'inactiveSlideScale' +export function defaultScrollInterpolator ( + index: number, + carouselProps: CarouselProps +) { + const range = [1, 0, -1]; + const inputRange = getInputRangeFromIndexes(range, index, carouselProps); + const outputRange = [0, 1, 0]; + + return { inputRange, outputRange }; +} +export function defaultAnimatedStyles ( + _index: number, + animatedValue: Animated.AnimatedInterpolation, + carouselProps: CarouselProps +) { + let animatedOpacity = {}; + let animatedScale = {}; + + if (carouselProps.inactiveSlideOpacity < 1) { + animatedOpacity = { + opacity: animatedValue.interpolate({ + inputRange: [0, 1], + outputRange: [carouselProps.inactiveSlideOpacity, 1] + }) + }; + } + + if (carouselProps.inactiveSlideScale < 1) { + animatedScale = { + transform: [ + { + scale: animatedValue.interpolate({ + inputRange: [0, 1], + outputRange: [carouselProps.inactiveSlideScale, 1] + }) + } + ] + }; + } + + return { + ...animatedOpacity, + ...animatedScale + }; +} + +// Shift animation +// Same as the default one, but the active slide is also shifted up or down +// Based on prop 'inactiveSlideShift' +export function shiftAnimatedStyles ( + _index: number, + animatedValue: Animated.AnimatedInterpolation, + carouselProps: CarouselProps +) { + let animatedOpacity = {}; + let animatedScale = {}; + let animatedTranslate = {}; + + if (carouselProps.inactiveSlideOpacity < 1) { + animatedOpacity = { + opacity: animatedValue.interpolate({ + inputRange: [0, 1], + outputRange: [carouselProps.inactiveSlideOpacity, 1] + }) + }; + } + + if (carouselProps.inactiveSlideScale < 1) { + animatedScale = { + scale: animatedValue.interpolate({ + inputRange: [0, 1], + outputRange: [carouselProps.inactiveSlideScale, 1] + }) + }; + } + + if (carouselProps.inactiveSlideShift !== 0) { + const translateProp = carouselProps.vertical ? 'translateX' : 'translateY'; + animatedTranslate = { + [translateProp]: animatedValue.interpolate({ + inputRange: [0, 1], + outputRange: [carouselProps.inactiveSlideShift, 0] + }) + }; + } + + return { + ...animatedOpacity, + transform: [{ ...animatedScale }, { ...animatedTranslate }] + }; +} + +// Stack animation +// Imitate a deck/stack of cards (see #195) +// WARNING: The effect had to be visually inverted on Android because this OS doesn't honor the `zIndex`property +// This means that the item with the higher zIndex (and therefore the tap receiver) remains the one AFTER the currently active item +// The `elevation` property compensates for that only visually, which is not good enough +export function stackScrollInterpolator ( + index: number, + carouselProps: CarouselProps +) { + const range = IS_ANDROID ? [1, 0, -1, -2, -3] : [3, 2, 1, 0, -1]; + const inputRange = getInputRangeFromIndexes(range, index, carouselProps); + const outputRange = range; + + return { inputRange, outputRange }; +} +export function stackAnimatedStyles ( + index: number, + animatedValue: Animated.AnimatedInterpolation, + carouselProps: CarouselProps, + cardOffset?: number +) { + const sizeRef = carouselProps.vertical ? + carouselProps.itemHeight : + carouselProps.itemWidth; + const translateProp = carouselProps.vertical ? 'translateY' : 'translateX'; + + const card1Scale = 0.9; + const card2Scale = 0.8; + + const newCardOffset = cardOffset ?? 18; + + const getTranslateFromScale = (cardIndex: number, scale: number) => { + const centerFactor = (1 / scale) * cardIndex; + const centeredPosition = -Math.round(sizeRef * centerFactor); + const edgeAlignment = Math.round((sizeRef - sizeRef * scale) / 2); + const offset = Math.round((newCardOffset * Math.abs(cardIndex)) / scale); + + return IS_ANDROID ? + centeredPosition - edgeAlignment - offset : + centeredPosition + edgeAlignment + offset; + }; + + const opacityOutputRange = + carouselProps.inactiveSlideOpacity === 1 ? [1, 1, 1, 0] : [1, 0.75, 0.5, 0]; + + return IS_ANDROID ? + { + // elevation: carouselProps.data.length - index, // fix zIndex bug visually, but not from a logic point of view + opacity: animatedValue.interpolate({ + inputRange: [-3, -2, -1, 0], + outputRange: opacityOutputRange.reverse(), + extrapolate: 'clamp' + }), + transform: [ + { + scale: animatedValue.interpolate({ + inputRange: [-2, -1, 0, 1], + outputRange: [card2Scale, card1Scale, 1, card1Scale], + extrapolate: 'clamp' + }) + }, + { + [translateProp]: animatedValue.interpolate({ + inputRange: [-3, -2, -1, 0, 1], + outputRange: [ + getTranslateFromScale(-3, card2Scale), + getTranslateFromScale(-2, card2Scale), + getTranslateFromScale(-1, card1Scale), + 0, + sizeRef * 0.5 + ], + extrapolate: 'clamp' + }) + } + ] + } : + { + zIndex: carouselProps.data.length - index, + opacity: animatedValue.interpolate({ + inputRange: [0, 1, 2, 3], + outputRange: opacityOutputRange, + extrapolate: 'clamp' + }), + transform: [ + { + scale: animatedValue.interpolate({ + inputRange: [-1, 0, 1, 2], + outputRange: [card1Scale, 1, card1Scale, card2Scale], + extrapolate: 'clamp' + }) + }, + { + [translateProp]: animatedValue.interpolate({ + inputRange: [-1, 0, 1, 2, 3], + outputRange: [ + -sizeRef * 0.5, + 0, + getTranslateFromScale(1, card1Scale), + getTranslateFromScale(2, card2Scale), + getTranslateFromScale(3, card2Scale) + ], + extrapolate: 'clamp' + }) + } + ] + }; +} + +// Tinder animation +// Imitate the popular Tinder layout +// WARNING: The effect had to be visually inverted on Android because this OS doesn't honor the `zIndex`property +// This means that the item with the higher zIndex (and therefore the tap receiver) remains the one AFTER the currently active item +// The `elevation` property compensates for that only visually, which is not good enough +export function tinderScrollInterpolator ( + index: number, + carouselProps: CarouselProps +) { + const range = IS_ANDROID ? [1, 0, -1, -2, -3] : [3, 2, 1, 0, -1]; + const inputRange = getInputRangeFromIndexes(range, index, carouselProps); + const outputRange = range; + + return { inputRange, outputRange }; +} +export function tinderAnimatedStyles ( + index: number, + animatedValue: Animated.AnimatedInterpolation, + carouselProps: CarouselProps, + cardOffset?: number +) { + const sizeRef = carouselProps.vertical ? + carouselProps.itemHeight : + carouselProps.itemWidth; + const mainTranslateProp = carouselProps.vertical ? + 'translateY' : + 'translateX'; + const secondaryTranslateProp = carouselProps.vertical ? + 'translateX' : + 'translateY'; + + const card1Scale = 0.96; + const card2Scale = 0.92; + const card3Scale = 0.88; + + const peekingCardsOpacity = IS_ANDROID ? 0.92 : 1; + + const newCardOffset = cardOffset ?? 9; + + const getMainTranslateFromScale = (cardIndex: number, scale: number) => { + const centerFactor = (1 / scale) * cardIndex; + return -Math.round(sizeRef * centerFactor); + }; + + const getSecondaryTranslateFromScale = (cardIndex: number, scale: number) => { + return Math.round((newCardOffset * Math.abs(cardIndex)) / scale); + }; + + return IS_ANDROID ? + { + // elevation: carouselProps.data.length - index, // fix zIndex bug visually, but not from a logic point of view + opacity: animatedValue.interpolate({ + inputRange: [-3, -2, -1, 0, 1], + outputRange: [0, peekingCardsOpacity, peekingCardsOpacity, 1, 0], + extrapolate: 'clamp' + }), + transform: [ + { + scale: animatedValue.interpolate({ + inputRange: [-3, -2, -1, 0], + outputRange: [card3Scale, card2Scale, card1Scale, 1], + extrapolate: 'clamp' + }) + }, + { + rotate: animatedValue.interpolate({ + inputRange: [0, 1], + outputRange: ['0deg', '22deg'], + extrapolate: 'clamp' + }) + }, + { + [mainTranslateProp]: animatedValue.interpolate({ + inputRange: [-3, -2, -1, 0, 1], + outputRange: [ + getMainTranslateFromScale(-3, card3Scale), + getMainTranslateFromScale(-2, card2Scale), + getMainTranslateFromScale(-1, card1Scale), + 0, + sizeRef * 1.1 + ], + extrapolate: 'clamp' + }) + }, + { + [secondaryTranslateProp]: animatedValue.interpolate({ + inputRange: [-3, -2, -1, 0], + outputRange: [ + getSecondaryTranslateFromScale(-3, card3Scale), + getSecondaryTranslateFromScale(-2, card2Scale), + getSecondaryTranslateFromScale(-1, card1Scale), + 0 + ], + extrapolate: 'clamp' + }) + } + ] + } : + { + zIndex: carouselProps.data.length - index, + opacity: animatedValue.interpolate({ + inputRange: [-1, 0, 1, 2, 3], + outputRange: [0, 1, peekingCardsOpacity, peekingCardsOpacity, 0], + extrapolate: 'clamp' + }), + transform: [ + { + scale: animatedValue.interpolate({ + inputRange: [0, 1, 2, 3], + outputRange: [1, card1Scale, card2Scale, card3Scale], + extrapolate: 'clamp' + }) + }, + { + rotate: animatedValue.interpolate({ + inputRange: [-1, 0], + outputRange: ['-22deg', '0deg'], + extrapolate: 'clamp' + }) + }, + { + [mainTranslateProp]: animatedValue.interpolate({ + inputRange: [-1, 0, 1, 2, 3], + outputRange: [ + -sizeRef * 1.1, + 0, + getMainTranslateFromScale(1, card1Scale), + getMainTranslateFromScale(2, card2Scale), + getMainTranslateFromScale(3, card3Scale) + ], + extrapolate: 'clamp' + }) + }, + { + [secondaryTranslateProp]: animatedValue.interpolate({ + inputRange: [0, 1, 2, 3], + outputRange: [ + 0, + getSecondaryTranslateFromScale(1, card1Scale), + getSecondaryTranslateFromScale(2, card2Scale), + getSecondaryTranslateFromScale(3, card3Scale) + ], + extrapolate: 'clamp' + }) + } + ] + }; +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 000000000..02d160622 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "paths": { + "react-native-snap-carousel": ["./src/index"] + }, + "allowUnreachableCode": false, + "allowUnusedLabels": false, + "esModuleInterop": true, + "importsNotUsedAsValues": "error", + "forceConsistentCasingInFileNames": true, + "jsx": "react", + "lib": ["esnext"], + "module": "esnext", + "moduleResolution": "node", + "noFallthroughCasesInSwitch": true, + "noImplicitReturns": true, + "noImplicitUseStrict": false, + "noStrictGenericChecks": false, + "noUnusedLocals": true, + "noUnusedParameters": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "strict": true, + "target": "esnext", + }, + "include": ["src/**/*"] +}