├── .gitignore ├── .npmignore ├── Example ├── .bundle │ └── config ├── .eslintrc.js ├── .gitignore ├── .prettierrc.js ├── .watchmanconfig ├── Gemfile ├── Gemfile.lock ├── README.md ├── __tests__ │ └── App.test.tsx ├── android │ ├── app │ │ ├── build.gradle │ │ ├── debug.keystore │ │ ├── proguard-rules.pro │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── java │ │ │ └── com │ │ │ │ └── iaphub │ │ │ │ └── example │ │ │ │ ├── MainActivity.kt │ │ │ │ └── MainApplication.kt │ │ │ └── res │ │ │ ├── drawable │ │ │ └── rn_edit_text_material.xml │ │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ └── values │ │ │ ├── strings.xml │ │ │ └── styles.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ └── settings.gradle ├── app.json ├── babel.config.js ├── gift.png ├── index.js ├── ios │ ├── .xcode.env │ ├── Example.xcodeproj │ │ ├── project.pbxproj │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Example.xcscheme │ ├── Example.xcworkspace │ │ └── contents.xcworkspacedata │ ├── Example │ │ ├── AppDelegate.h │ │ ├── AppDelegate.mm │ │ ├── Images.xcassets │ │ │ ├── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ │ └── Contents.json │ │ ├── Info.plist │ │ ├── LaunchScreen.storyboard │ │ ├── PrivacyInfo.xcprivacy │ │ └── main.m │ ├── ExampleTests │ │ ├── ExampleTests.m │ │ └── Info.plist │ ├── Podfile │ └── Podfile.lock ├── jest.config.js ├── metro.config.js ├── package-lock.json ├── package.json ├── src-data-provider │ └── App.js ├── src │ ├── App.js │ └── store.js └── tsconfig.json ├── LICENSE ├── README.md ├── package-lock.json ├── package.json └── src ├── i18n ├── de.js ├── en.js ├── es.js ├── fr.js ├── index.js ├── it.js ├── jp.js └── pt.js ├── iaphub-data-consumer └── index.js ├── iaphub-data-provider ├── context.js └── index.js ├── iaphub-data └── index.js ├── index.js ├── paywall-subscription-group ├── index.js ├── product-content-single-monthly-subscription.js ├── product-content.js ├── product-price-per-month.js ├── product-price.js ├── product-title.js └── product.js ├── paywall ├── active-product.js ├── active-products-wrapper.js ├── buy.js ├── index.js ├── intro-phase.js ├── intro-phases-wrapper.js ├── loading.js ├── paywall.js ├── product-price.js ├── product-title.js ├── product.js ├── products-error.js ├── products-wrapper.js ├── restore.js ├── subscription-terms.js └── touchable-product.js └── util ├── buy-with-alert.js ├── get-buy-alert-message.js ├── get-subscription-duration.js ├── get-subscription-price-duration.js ├── show-alert.js ├── style.js └── with-styles.js /.gitignore: -------------------------------------------------------------------------------- 1 | npm-debug.log* 2 | .DS_Store 3 | node_modules 4 | tasks.todo 5 | translations.txt -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | npm-debug.log* 2 | .DS_Store 3 | .npmrc 4 | node_modules 5 | *.todo 6 | *~ 7 | Example 8 | translations.txt -------------------------------------------------------------------------------- /Example/.bundle/config: -------------------------------------------------------------------------------- 1 | BUNDLE_PATH: "vendor/bundle" 2 | BUNDLE_FORCE_RUBY_PLATFORM: 1 3 | -------------------------------------------------------------------------------- /Example/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: '@react-native', 4 | }; 5 | -------------------------------------------------------------------------------- /Example/.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Xcode 6 | # 7 | build/ 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | *.xccheckout 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | *.xcuserstate 23 | **/.xcode.env.local 24 | 25 | # Android/IntelliJ 26 | # 27 | build/ 28 | .idea 29 | .gradle 30 | local.properties 31 | *.iml 32 | *.hprof 33 | .cxx/ 34 | *.keystore 35 | !debug.keystore 36 | 37 | # node.js 38 | # 39 | node_modules/ 40 | npm-debug.log 41 | yarn-error.log 42 | 43 | # fastlane 44 | # 45 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 46 | # screenshots whenever they are needed. 47 | # For more information about the recommended setup visit: 48 | # https://docs.fastlane.tools/best-practices/source-control/ 49 | 50 | **/fastlane/report.xml 51 | **/fastlane/Preview.html 52 | **/fastlane/screenshots 53 | **/fastlane/test_output 54 | 55 | # Bundle artifact 56 | *.jsbundle 57 | 58 | # Ruby / CocoaPods 59 | **/Pods/ 60 | /vendor/bundle/ 61 | 62 | # Temporary files created by Metro to check the health of the file watcher 63 | .metro-health-check* 64 | 65 | # testing 66 | /coverage 67 | 68 | # Yarn 69 | .yarn/* 70 | !.yarn/patches 71 | !.yarn/plugins 72 | !.yarn/releases 73 | !.yarn/sdks 74 | !.yarn/versions 75 | -------------------------------------------------------------------------------- /Example/.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | arrowParens: 'avoid', 3 | bracketSameLine: true, 4 | bracketSpacing: false, 5 | singleQuote: true, 6 | trailingComma: 'all', 7 | }; 8 | -------------------------------------------------------------------------------- /Example/.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /Example/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # You may use http://rbenv.org/ or https://rvm.io/ to install and use this version 4 | ruby ">= 2.6.10" 5 | 6 | # Exclude problematic versions of cocoapods and activesupport that causes build failures. 7 | gem 'cocoapods', '>= 1.13', '!= 1.15.0', '!= 1.15.1' 8 | gem 'activesupport', '>= 6.1.7.5', '!= 7.1.0' 9 | gem 'xcodeproj', '< 1.26.0' 10 | -------------------------------------------------------------------------------- /Example/Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | CFPropertyList (3.0.7) 5 | base64 6 | nkf 7 | rexml 8 | activesupport (7.1.5.1) 9 | base64 10 | benchmark (>= 0.3) 11 | bigdecimal 12 | concurrent-ruby (~> 1.0, >= 1.0.2) 13 | connection_pool (>= 2.2.5) 14 | drb 15 | i18n (>= 1.6, < 2) 16 | logger (>= 1.4.2) 17 | minitest (>= 5.1) 18 | mutex_m 19 | securerandom (>= 0.3) 20 | tzinfo (~> 2.0) 21 | addressable (2.8.7) 22 | public_suffix (>= 2.0.2, < 7.0) 23 | algoliasearch (1.27.5) 24 | httpclient (~> 2.8, >= 2.8.3) 25 | json (>= 1.5.1) 26 | atomos (0.1.3) 27 | base64 (0.2.0) 28 | benchmark (0.4.0) 29 | bigdecimal (3.1.8) 30 | claide (1.1.0) 31 | cocoapods (1.15.2) 32 | addressable (~> 2.8) 33 | claide (>= 1.0.2, < 2.0) 34 | cocoapods-core (= 1.15.2) 35 | cocoapods-deintegrate (>= 1.0.3, < 2.0) 36 | cocoapods-downloader (>= 2.1, < 3.0) 37 | cocoapods-plugins (>= 1.0.0, < 2.0) 38 | cocoapods-search (>= 1.0.0, < 2.0) 39 | cocoapods-trunk (>= 1.6.0, < 2.0) 40 | cocoapods-try (>= 1.1.0, < 2.0) 41 | colored2 (~> 3.1) 42 | escape (~> 0.0.4) 43 | fourflusher (>= 2.3.0, < 3.0) 44 | gh_inspector (~> 1.0) 45 | molinillo (~> 0.8.0) 46 | nap (~> 1.0) 47 | ruby-macho (>= 2.3.0, < 3.0) 48 | xcodeproj (>= 1.23.0, < 2.0) 49 | cocoapods-core (1.15.2) 50 | activesupport (>= 5.0, < 8) 51 | addressable (~> 2.8) 52 | algoliasearch (~> 1.0) 53 | concurrent-ruby (~> 1.1) 54 | fuzzy_match (~> 2.0.4) 55 | nap (~> 1.0) 56 | netrc (~> 0.11) 57 | public_suffix (~> 4.0) 58 | typhoeus (~> 1.0) 59 | cocoapods-deintegrate (1.0.5) 60 | cocoapods-downloader (2.1) 61 | cocoapods-plugins (1.0.0) 62 | nap 63 | cocoapods-search (1.0.1) 64 | cocoapods-trunk (1.6.0) 65 | nap (>= 0.8, < 2.0) 66 | netrc (~> 0.11) 67 | cocoapods-try (1.2.0) 68 | colored2 (3.1.2) 69 | concurrent-ruby (1.3.4) 70 | connection_pool (2.4.1) 71 | drb (2.2.1) 72 | escape (0.0.4) 73 | ethon (0.16.0) 74 | ffi (>= 1.15.0) 75 | ffi (1.17.0) 76 | fourflusher (2.3.1) 77 | fuzzy_match (2.0.4) 78 | gh_inspector (1.1.3) 79 | httpclient (2.8.3) 80 | i18n (1.14.6) 81 | concurrent-ruby (~> 1.0) 82 | json (2.9.1) 83 | logger (1.6.4) 84 | minitest (5.25.4) 85 | molinillo (0.8.0) 86 | mutex_m (0.3.0) 87 | nanaimo (0.3.0) 88 | nap (1.1.0) 89 | netrc (0.11.0) 90 | nkf (0.2.0) 91 | public_suffix (4.0.7) 92 | rexml (3.4.0) 93 | ruby-macho (2.5.1) 94 | securerandom (0.3.2) 95 | typhoeus (1.4.1) 96 | ethon (>= 0.9.0) 97 | tzinfo (2.0.6) 98 | concurrent-ruby (~> 1.0) 99 | xcodeproj (1.25.1) 100 | CFPropertyList (>= 2.3.3, < 4.0) 101 | atomos (~> 0.1.3) 102 | claide (>= 1.0.2, < 2.0) 103 | colored2 (~> 3.1) 104 | nanaimo (~> 0.3.0) 105 | rexml (>= 3.3.6, < 4.0) 106 | 107 | PLATFORMS 108 | ruby 109 | 110 | DEPENDENCIES 111 | activesupport (>= 6.1.7.5, != 7.1.0) 112 | cocoapods (>= 1.13, != 1.15.1, != 1.15.0) 113 | xcodeproj (< 1.26.0) 114 | 115 | RUBY VERSION 116 | ruby 3.0.0p0 117 | 118 | BUNDLED WITH 119 | 2.2.3 120 | -------------------------------------------------------------------------------- /Example/README.md: -------------------------------------------------------------------------------- 1 | This is a new [**React Native**](https://reactnative.dev) project, bootstrapped using [`@react-native-community/cli`](https://github.com/react-native-community/cli). 2 | 3 | # Getting Started 4 | 5 | >**Note**: Make sure you have completed the [React Native - Environment Setup](https://reactnative.dev/docs/environment-setup) instructions till "Creating a new application" step, before proceeding. 6 | 7 | ## Step 1: Start the Metro Server 8 | 9 | First, you will need to start **Metro**, the JavaScript _bundler_ that ships _with_ React Native. 10 | 11 | To start Metro, run the following command from the _root_ of your React Native project: 12 | 13 | ```bash 14 | # using npm 15 | npm start 16 | 17 | # OR using Yarn 18 | yarn start 19 | ``` 20 | 21 | ## Step 2: Start your Application 22 | 23 | Let Metro Bundler run in its _own_ terminal. Open a _new_ terminal from the _root_ of your React Native project. Run the following command to start your _Android_ or _iOS_ app: 24 | 25 | ### For Android 26 | 27 | ```bash 28 | # using npm 29 | npm run android 30 | 31 | # OR using Yarn 32 | yarn android 33 | ``` 34 | 35 | ### For iOS 36 | 37 | ```bash 38 | # using npm 39 | npm run ios 40 | 41 | # OR using Yarn 42 | yarn ios 43 | ``` 44 | 45 | If everything is set up _correctly_, you should see your new app running in your _Android Emulator_ or _iOS Simulator_ shortly provided you have set up your emulator/simulator correctly. 46 | 47 | This is one way to run your app — you can also run it directly from within Android Studio and Xcode respectively. 48 | 49 | ## Step 3: Modifying your App 50 | 51 | Now that you have successfully run the app, let's modify it. 52 | 53 | 1. Open `App.tsx` in your text editor of choice and edit some lines. 54 | 2. For **Android**: Press the R key twice or select **"Reload"** from the **Developer Menu** (Ctrl + M (on Window and Linux) or Cmd ⌘ + M (on macOS)) to see your changes! 55 | 56 | For **iOS**: Hit Cmd ⌘ + R in your iOS Simulator to reload the app and see your changes! 57 | 58 | ## Congratulations! :tada: 59 | 60 | You've successfully run and modified your React Native App. :partying_face: 61 | 62 | ### Now what? 63 | 64 | - If you want to add this new React Native code to an existing application, check out the [Integration guide](https://reactnative.dev/docs/integration-with-existing-apps). 65 | - If you're curious to learn more about React Native, check out the [Introduction to React Native](https://reactnative.dev/docs/getting-started). 66 | 67 | # Troubleshooting 68 | 69 | If you can't get this to work, see the [Troubleshooting](https://reactnative.dev/docs/troubleshooting) page. 70 | 71 | # Learn More 72 | 73 | To learn more about React Native, take a look at the following resources: 74 | 75 | - [React Native Website](https://reactnative.dev) - learn more about React Native. 76 | - [Getting Started](https://reactnative.dev/docs/environment-setup) - an **overview** of React Native and how setup your environment. 77 | - [Learn the Basics](https://reactnative.dev/docs/getting-started) - a **guided tour** of the React Native **basics**. 78 | - [Blog](https://reactnative.dev/blog) - read the latest official React Native **Blog** posts. 79 | - [`@facebook/react-native`](https://github.com/facebook/react-native) - the Open Source; GitHub **repository** for React Native. 80 | -------------------------------------------------------------------------------- /Example/__tests__/App.test.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * @format 3 | */ 4 | 5 | import 'react-native'; 6 | import React from 'react'; 7 | import App from '../App'; 8 | 9 | // Note: import explicitly to use the types shipped with jest. 10 | import {it} from '@jest/globals'; 11 | 12 | // Note: test renderer must be required after react-native. 13 | import renderer from 'react-test-renderer'; 14 | 15 | it('renders correctly', () => { 16 | renderer.create(); 17 | }); 18 | -------------------------------------------------------------------------------- /Example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: "com.android.application" 2 | apply plugin: "org.jetbrains.kotlin.android" 3 | apply plugin: "com.facebook.react" 4 | 5 | /** 6 | * This is the configuration block to customize your React Native Android app. 7 | * By default you don't need to apply any configuration, just uncomment the lines you need. 8 | */ 9 | react { 10 | /* Folders */ 11 | // The root of your project, i.e. where "package.json" lives. Default is '../..' 12 | // root = file("../../") 13 | // The folder where the react-native NPM package is. Default is ../../node_modules/react-native 14 | // reactNativeDir = file("../../node_modules/react-native") 15 | // The folder where the react-native Codegen package is. Default is ../../node_modules/@react-native/codegen 16 | // codegenDir = file("../../node_modules/@react-native/codegen") 17 | // The cli.js file which is the React Native CLI entrypoint. Default is ../../node_modules/react-native/cli.js 18 | // cliFile = file("../../node_modules/react-native/cli.js") 19 | 20 | /* Variants */ 21 | // The list of variants to that are debuggable. For those we're going to 22 | // skip the bundling of the JS bundle and the assets. By default is just 'debug'. 23 | // If you add flavors like lite, prod, etc. you'll have to list your debuggableVariants. 24 | // debuggableVariants = ["liteDebug", "prodDebug"] 25 | 26 | /* Bundling */ 27 | // A list containing the node command and its flags. Default is just 'node'. 28 | // nodeExecutableAndArgs = ["node"] 29 | // 30 | // The command to run when bundling. By default is 'bundle' 31 | // bundleCommand = "ram-bundle" 32 | // 33 | // The path to the CLI configuration file. Default is empty. 34 | // bundleConfig = file(../rn-cli.config.js) 35 | // 36 | // The name of the generated asset file containing your JS bundle 37 | // bundleAssetName = "MyApplication.android.bundle" 38 | // 39 | // The entry file for bundle generation. Default is 'index.android.js' or 'index.js' 40 | // entryFile = file("../js/MyApplication.android.js") 41 | // 42 | // A list of extra flags to pass to the 'bundle' commands. 43 | // See https://github.com/react-native-community/cli/blob/main/docs/commands.md#bundle 44 | // extraPackagerArgs = [] 45 | 46 | /* Hermes Commands */ 47 | // The hermes compiler command to run. By default it is 'hermesc' 48 | // hermesCommand = "$rootDir/my-custom-hermesc/bin/hermesc" 49 | // 50 | // The list of flags to pass to the Hermes compiler. By default is "-O", "-output-source-map" 51 | // hermesFlags = ["-O", "-output-source-map"] 52 | 53 | /* Autolinking */ 54 | autolinkLibrariesWithApp() 55 | } 56 | 57 | /** 58 | * Set this to true to Run Proguard on Release builds to minify the Java bytecode. 59 | */ 60 | def enableProguardInReleaseBuilds = false 61 | 62 | /** 63 | * The preferred build flavor of JavaScriptCore (JSC) 64 | * 65 | * For example, to use the international variant, you can use: 66 | * `def jscFlavor = 'org.webkit:android-jsc-intl:+'` 67 | * 68 | * The international variant includes ICU i18n library and necessary data 69 | * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that 70 | * give correct results when using with locales other than en-US. Note that 71 | * this variant is about 6MiB larger per architecture than default. 72 | */ 73 | def jscFlavor = 'org.webkit:android-jsc:+' 74 | 75 | android { 76 | ndkVersion rootProject.ext.ndkVersion 77 | buildToolsVersion rootProject.ext.buildToolsVersion 78 | compileSdk rootProject.ext.compileSdkVersion 79 | 80 | namespace "com.iaphub.example" 81 | defaultConfig { 82 | applicationId "com.iaphub.example" 83 | minSdkVersion rootProject.ext.minSdkVersion 84 | targetSdkVersion rootProject.ext.targetSdkVersion 85 | versionCode 1 86 | versionName "1.0" 87 | } 88 | signingConfigs { 89 | debug { 90 | storeFile file('debug.keystore') 91 | storePassword 'android' 92 | keyAlias 'androiddebugkey' 93 | keyPassword 'android' 94 | } 95 | } 96 | buildTypes { 97 | debug { 98 | signingConfig signingConfigs.debug 99 | } 100 | release { 101 | // Caution! In production, you need to generate your own keystore file. 102 | // see https://reactnative.dev/docs/signed-apk-android. 103 | signingConfig signingConfigs.debug 104 | minifyEnabled enableProguardInReleaseBuilds 105 | proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" 106 | } 107 | } 108 | } 109 | 110 | dependencies { 111 | // The version of react-native is set by the React Native Gradle Plugin 112 | implementation("com.facebook.react:react-android") 113 | 114 | if (hermesEnabled.toBoolean()) { 115 | implementation("com.facebook.react:hermes-android") 116 | } else { 117 | implementation jscFlavor 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /Example/android/app/debug.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iaphub/react-native-iaphub-ui/99cab2d5472f682db6caacafedb71765ad0c3512/Example/android/app/debug.keystore -------------------------------------------------------------------------------- /Example/android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | -------------------------------------------------------------------------------- /Example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | 10 | -------------------------------------------------------------------------------- /Example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 13 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Example/android/app/src/main/java/com/iaphub/example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.iaphub.example 2 | 3 | import com.facebook.react.ReactActivity 4 | import com.facebook.react.ReactActivityDelegate 5 | import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled 6 | import com.facebook.react.defaults.DefaultReactActivityDelegate 7 | 8 | class MainActivity : ReactActivity() { 9 | 10 | /** 11 | * Returns the name of the main component registered from JavaScript. This is used to schedule 12 | * rendering of the component. 13 | */ 14 | override fun getMainComponentName(): String = "Example" 15 | 16 | /** 17 | * Returns the instance of the [ReactActivityDelegate]. We use [DefaultReactActivityDelegate] 18 | * which allows you to enable New Architecture with a single boolean flags [fabricEnabled] 19 | */ 20 | override fun createReactActivityDelegate(): ReactActivityDelegate = 21 | DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled) 22 | } 23 | -------------------------------------------------------------------------------- /Example/android/app/src/main/java/com/iaphub/example/MainApplication.kt: -------------------------------------------------------------------------------- 1 | package com.iaphub.example 2 | 3 | import android.app.Application 4 | import com.facebook.react.PackageList 5 | import com.facebook.react.ReactApplication 6 | import com.facebook.react.ReactHost 7 | import com.facebook.react.ReactNativeHost 8 | import com.facebook.react.ReactPackage 9 | import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load 10 | import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost 11 | import com.facebook.react.defaults.DefaultReactNativeHost 12 | import com.facebook.react.soloader.OpenSourceMergedSoMapping 13 | import com.facebook.soloader.SoLoader 14 | 15 | class MainApplication : Application(), ReactApplication { 16 | 17 | override val reactNativeHost: ReactNativeHost = 18 | object : DefaultReactNativeHost(this) { 19 | override fun getPackages(): List = 20 | PackageList(this).packages.apply { 21 | // Packages that cannot be autolinked yet can be added manually here, for example: 22 | // add(MyReactNativePackage()) 23 | } 24 | 25 | override fun getJSMainModuleName(): String = "index" 26 | 27 | override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG 28 | 29 | override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED 30 | override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED 31 | } 32 | 33 | override val reactHost: ReactHost 34 | get() = getDefaultReactHost(applicationContext, reactNativeHost) 35 | 36 | override fun onCreate() { 37 | super.onCreate() 38 | SoLoader.init(this, OpenSourceMergedSoMapping) 39 | if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { 40 | // If you opted-in for the New Architecture, we load the native entry point for this app. 41 | load() 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Example/android/app/src/main/res/drawable/rn_edit_text_material.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 22 | 23 | 24 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /Example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iaphub/react-native-iaphub-ui/99cab2d5472f682db6caacafedb71765ad0c3512/Example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /Example/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iaphub/react-native-iaphub-ui/99cab2d5472f682db6caacafedb71765ad0c3512/Example/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /Example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iaphub/react-native-iaphub-ui/99cab2d5472f682db6caacafedb71765ad0c3512/Example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /Example/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iaphub/react-native-iaphub-ui/99cab2d5472f682db6caacafedb71765ad0c3512/Example/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /Example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iaphub/react-native-iaphub-ui/99cab2d5472f682db6caacafedb71765ad0c3512/Example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /Example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iaphub/react-native-iaphub-ui/99cab2d5472f682db6caacafedb71765ad0c3512/Example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /Example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iaphub/react-native-iaphub-ui/99cab2d5472f682db6caacafedb71765ad0c3512/Example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /Example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iaphub/react-native-iaphub-ui/99cab2d5472f682db6caacafedb71765ad0c3512/Example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /Example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iaphub/react-native-iaphub-ui/99cab2d5472f682db6caacafedb71765ad0c3512/Example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /Example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iaphub/react-native-iaphub-ui/99cab2d5472f682db6caacafedb71765ad0c3512/Example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /Example/android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Example 3 | 4 | -------------------------------------------------------------------------------- /Example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /Example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext { 3 | buildToolsVersion = "35.0.0" 4 | minSdkVersion = 24 5 | compileSdkVersion = 35 6 | targetSdkVersion = 34 7 | ndkVersion = "26.1.10909125" 8 | kotlinVersion = "1.9.24" 9 | } 10 | repositories { 11 | google() 12 | mavenCentral() 13 | } 14 | dependencies { 15 | classpath("com.android.tools.build:gradle") 16 | classpath("com.facebook.react:react-native-gradle-plugin") 17 | classpath("org.jetbrains.kotlin:kotlin-gradle-plugin") 18 | } 19 | } 20 | 21 | apply plugin: "com.facebook.react.rootproject" 22 | -------------------------------------------------------------------------------- /Example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx512m -XX:MaxMetaspaceSize=256m 13 | org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true 19 | 20 | # AndroidX package structure to make it clearer which packages are bundled with the 21 | # Android operating system, and which are packaged with your app's APK 22 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 23 | android.useAndroidX=true 24 | 25 | # Use this property to specify which architecture you want to build. 26 | # You can also override it from the CLI using 27 | # ./gradlew -PreactNativeArchitectures=x86_64 28 | reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64 29 | 30 | # Use this property to enable support to the new architecture. 31 | # This will allow you to use TurboModules and the Fabric render in 32 | # your application. You should enable this flag either if you want 33 | # to write custom TurboModules/Fabric components OR use libraries that 34 | # are providing them. 35 | newArchEnabled=true 36 | 37 | # Use this property to enable or disable the Hermes JS engine. 38 | # If set to false, you will be using JSC instead. 39 | hermesEnabled=true 40 | -------------------------------------------------------------------------------- /Example/android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iaphub/react-native-iaphub-ui/99cab2d5472f682db6caacafedb71765ad0c3512/Example/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /Example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /Example/android/gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # SPDX-License-Identifier: Apache-2.0 19 | # 20 | 21 | ############################################################################## 22 | # 23 | # Gradle start up script for POSIX generated by Gradle. 24 | # 25 | # Important for running: 26 | # 27 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 28 | # noncompliant, but you have some other compliant shell such as ksh or 29 | # bash, then to run this script, type that shell name before the whole 30 | # command line, like: 31 | # 32 | # ksh Gradle 33 | # 34 | # Busybox and similar reduced shells will NOT work, because this script 35 | # requires all of these POSIX shell features: 36 | # * functions; 37 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 38 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 39 | # * compound commands having a testable exit status, especially «case»; 40 | # * various built-in commands including «command», «set», and «ulimit». 41 | # 42 | # Important for patching: 43 | # 44 | # (2) This script targets any POSIX shell, so it avoids extensions provided 45 | # by Bash, Ksh, etc; in particular arrays are avoided. 46 | # 47 | # The "traditional" practice of packing multiple parameters into a 48 | # space-separated string is a well documented source of bugs and security 49 | # problems, so this is (mostly) avoided, by progressively accumulating 50 | # options in "$@", and eventually passing that to Java. 51 | # 52 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 53 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 54 | # see the in-line comments for details. 55 | # 56 | # There are tweaks for specific operating systems such as AIX, CygWin, 57 | # Darwin, MinGW, and NonStop. 58 | # 59 | # (3) This script is generated from the Groovy template 60 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 61 | # within the Gradle project. 62 | # 63 | # You can find Gradle at https://github.com/gradle/gradle/. 64 | # 65 | ############################################################################## 66 | 67 | # Attempt to set APP_HOME 68 | 69 | # Resolve links: $0 may be a link 70 | app_path=$0 71 | 72 | # Need this for daisy-chained symlinks. 73 | while 74 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 75 | [ -h "$app_path" ] 76 | do 77 | ls=$( ls -ld "$app_path" ) 78 | link=${ls#*' -> '} 79 | case $link in #( 80 | /*) app_path=$link ;; #( 81 | *) app_path=$APP_HOME$link ;; 82 | esac 83 | done 84 | 85 | # This is normally unused 86 | # shellcheck disable=SC2034 87 | APP_BASE_NAME=${0##*/} 88 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 89 | APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s 90 | ' "$PWD" ) || exit 91 | 92 | # Use the maximum available, or set MAX_FD != -1 to use that value. 93 | MAX_FD=maximum 94 | 95 | warn () { 96 | echo "$*" 97 | } >&2 98 | 99 | die () { 100 | echo 101 | echo "$*" 102 | echo 103 | exit 1 104 | } >&2 105 | 106 | # OS specific support (must be 'true' or 'false'). 107 | cygwin=false 108 | msys=false 109 | darwin=false 110 | nonstop=false 111 | case "$( uname )" in #( 112 | CYGWIN* ) cygwin=true ;; #( 113 | Darwin* ) darwin=true ;; #( 114 | MSYS* | MINGW* ) msys=true ;; #( 115 | NONSTOP* ) nonstop=true ;; 116 | esac 117 | 118 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 119 | 120 | 121 | # Determine the Java command to use to start the JVM. 122 | if [ -n "$JAVA_HOME" ] ; then 123 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 124 | # IBM's JDK on AIX uses strange locations for the executables 125 | JAVACMD=$JAVA_HOME/jre/sh/java 126 | else 127 | JAVACMD=$JAVA_HOME/bin/java 128 | fi 129 | if [ ! -x "$JAVACMD" ] ; then 130 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 131 | 132 | Please set the JAVA_HOME variable in your environment to match the 133 | location of your Java installation." 134 | fi 135 | else 136 | JAVACMD=java 137 | if ! command -v java >/dev/null 2>&1 138 | then 139 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 140 | 141 | Please set the JAVA_HOME variable in your environment to match the 142 | location of your Java installation." 143 | fi 144 | fi 145 | 146 | # Increase the maximum file descriptors if we can. 147 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 148 | case $MAX_FD in #( 149 | max*) 150 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 151 | # shellcheck disable=SC2039,SC3045 152 | MAX_FD=$( ulimit -H -n ) || 153 | warn "Could not query maximum file descriptor limit" 154 | esac 155 | case $MAX_FD in #( 156 | '' | soft) :;; #( 157 | *) 158 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 159 | # shellcheck disable=SC2039,SC3045 160 | ulimit -n "$MAX_FD" || 161 | warn "Could not set maximum file descriptor limit to $MAX_FD" 162 | esac 163 | fi 164 | 165 | # Collect all arguments for the java command, stacking in reverse order: 166 | # * args from the command line 167 | # * the main class name 168 | # * -classpath 169 | # * -D...appname settings 170 | # * --module-path (only if needed) 171 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 172 | 173 | # For Cygwin or MSYS, switch paths to Windows format before running java 174 | if "$cygwin" || "$msys" ; then 175 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 176 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 177 | 178 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 179 | 180 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 181 | for arg do 182 | if 183 | case $arg in #( 184 | -*) false ;; # don't mess with options #( 185 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 186 | [ -e "$t" ] ;; #( 187 | *) false ;; 188 | esac 189 | then 190 | arg=$( cygpath --path --ignore --mixed "$arg" ) 191 | fi 192 | # Roll the args list around exactly as many times as the number of 193 | # args, so each arg winds up back in the position where it started, but 194 | # possibly modified. 195 | # 196 | # NB: a `for` loop captures its iteration list before it begins, so 197 | # changing the positional parameters here affects neither the number of 198 | # iterations, nor the values presented in `arg`. 199 | shift # remove old arg 200 | set -- "$@" "$arg" # push replacement arg 201 | done 202 | fi 203 | 204 | 205 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 206 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 207 | 208 | # Collect all arguments for the java command: 209 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 210 | # and any embedded shellness will be escaped. 211 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 212 | # treated as '${Hostname}' itself on the command line. 213 | 214 | set -- \ 215 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 216 | -classpath "$CLASSPATH" \ 217 | org.gradle.wrapper.GradleWrapperMain \ 218 | "$@" 219 | 220 | # Stop when "xargs" is not available. 221 | if ! command -v xargs >/dev/null 2>&1 222 | then 223 | die "xargs is not available" 224 | fi 225 | 226 | # Use "xargs" to parse quoted args. 227 | # 228 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 229 | # 230 | # In Bash we could simply go: 231 | # 232 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 233 | # set -- "${ARGS[@]}" "$@" 234 | # 235 | # but POSIX shell has neither arrays nor command substitution, so instead we 236 | # post-process each arg (as a line of input to sed) to backslash-escape any 237 | # character that might be a shell metacharacter, then use eval to reverse 238 | # that process (while maintaining the separation between arguments), and wrap 239 | # the whole thing up as a single "set" statement. 240 | # 241 | # This will of course break if any of these variables contains a newline or 242 | # an unmatched quote. 243 | # 244 | 245 | eval "set -- $( 246 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 247 | xargs -n1 | 248 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 249 | tr '\n' ' ' 250 | )" '"$@"' 251 | 252 | exec "$JAVACMD" "$@" 253 | -------------------------------------------------------------------------------- /Example/android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /Example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { includeBuild("../node_modules/@react-native/gradle-plugin") } 2 | plugins { id("com.facebook.react.settings") } 3 | extensions.configure(com.facebook.react.ReactSettingsExtension){ ex -> ex.autolinkLibrariesFromCommand() } 4 | rootProject.name = 'com.iaphub.example' 5 | include ':app' 6 | includeBuild('../node_modules/@react-native/gradle-plugin') 7 | -------------------------------------------------------------------------------- /Example/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Example", 3 | "displayName": "Example" 4 | } 5 | -------------------------------------------------------------------------------- /Example/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['module:@react-native/babel-preset'], 3 | }; 4 | -------------------------------------------------------------------------------- /Example/gift.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iaphub/react-native-iaphub-ui/99cab2d5472f682db6caacafedb71765ad0c3512/Example/gift.png -------------------------------------------------------------------------------- /Example/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @format 3 | */ 4 | 5 | import {AppRegistry} from 'react-native'; 6 | import App from './src/App'; 7 | import {name as appName} from './app.json'; 8 | 9 | AppRegistry.registerComponent(appName, () => App); 10 | -------------------------------------------------------------------------------- /Example/ios/.xcode.env: -------------------------------------------------------------------------------- 1 | # This `.xcode.env` file is versioned and is used to source the environment 2 | # used when running script phases inside Xcode. 3 | # To customize your local environment, you can create an `.xcode.env.local` 4 | # file that is not versioned. 5 | 6 | # NODE_BINARY variable contains the PATH to the node executable. 7 | # 8 | # Customize the NODE_BINARY variable here. 9 | # For example, to use nvm with brew, add the following line 10 | # . "$(brew --prefix nvm)/nvm.sh" --no-use 11 | export NODE_BINARY=$(command -v node) 12 | -------------------------------------------------------------------------------- /Example/ios/Example.xcodeproj/xcshareddata/xcschemes/Example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 53 | 55 | 61 | 62 | 63 | 64 | 70 | 72 | 78 | 79 | 80 | 81 | 83 | 84 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /Example/ios/Example.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Example/ios/Example/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface AppDelegate : RCTAppDelegate 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /Example/ios/Example/AppDelegate.mm: -------------------------------------------------------------------------------- 1 | #import "AppDelegate.h" 2 | 3 | #import 4 | 5 | @implementation AppDelegate 6 | 7 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 8 | { 9 | self.moduleName = @"Example"; 10 | // You can add your custom initial props in the dictionary below. 11 | // They will be passed down to the ViewController used by React Native. 12 | self.initialProps = @{}; 13 | 14 | return [super application:application didFinishLaunchingWithOptions:launchOptions]; 15 | } 16 | 17 | - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge 18 | { 19 | return [self bundleURL]; 20 | } 21 | 22 | - (NSURL *)bundleURL 23 | { 24 | #if DEBUG 25 | return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"]; 26 | #else 27 | return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; 28 | #endif 29 | } 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /Example/ios/Example/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ios-marketing", 45 | "scale" : "1x", 46 | "size" : "1024x1024" 47 | } 48 | ], 49 | "info" : { 50 | "author" : "xcode", 51 | "version" : 1 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Example/ios/Example/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Example/ios/Example/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | Example 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(MARKETING_VERSION) 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | $(CURRENT_PROJECT_VERSION) 25 | LSRequiresIPhoneOS 26 | 27 | NSAppTransportSecurity 28 | 29 | 30 | NSAllowsArbitraryLoads 31 | 32 | NSAllowsLocalNetworking 33 | 34 | 35 | NSLocationWhenInUseUsageDescription 36 | 37 | UILaunchStoryboardName 38 | LaunchScreen 39 | UIRequiredDeviceCapabilities 40 | 41 | arm64 42 | 43 | UISupportedInterfaceOrientations 44 | 45 | UIInterfaceOrientationPortrait 46 | UIInterfaceOrientationLandscapeLeft 47 | UIInterfaceOrientationLandscapeRight 48 | 49 | UIViewControllerBasedStatusBarAppearance 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /Example/ios/Example/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 24 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /Example/ios/Example/PrivacyInfo.xcprivacy: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSPrivacyAccessedAPITypes 6 | 7 | 8 | NSPrivacyAccessedAPIType 9 | NSPrivacyAccessedAPICategoryUserDefaults 10 | NSPrivacyAccessedAPITypeReasons 11 | 12 | CA92.1 13 | 14 | 15 | 16 | NSPrivacyAccessedAPIType 17 | NSPrivacyAccessedAPICategoryFileTimestamp 18 | NSPrivacyAccessedAPITypeReasons 19 | 20 | C617.1 21 | 22 | 23 | 24 | NSPrivacyAccessedAPIType 25 | NSPrivacyAccessedAPICategorySystemBootTime 26 | NSPrivacyAccessedAPITypeReasons 27 | 28 | 35F9.1 29 | 30 | 31 | 32 | NSPrivacyCollectedDataTypes 33 | 34 | NSPrivacyTracking 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /Example/ios/Example/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | #import "AppDelegate.h" 4 | 5 | int main(int argc, char *argv[]) 6 | { 7 | @autoreleasepool { 8 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Example/ios/ExampleTests/ExampleTests.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | #import 5 | #import 6 | 7 | #define TIMEOUT_SECONDS 600 8 | #define TEXT_TO_LOOK_FOR @"Welcome to React" 9 | 10 | @interface ExampleTests : XCTestCase 11 | 12 | @end 13 | 14 | @implementation ExampleTests 15 | 16 | - (BOOL)findSubviewInView:(UIView *)view matching:(BOOL (^)(UIView *view))test 17 | { 18 | if (test(view)) { 19 | return YES; 20 | } 21 | for (UIView *subview in [view subviews]) { 22 | if ([self findSubviewInView:subview matching:test]) { 23 | return YES; 24 | } 25 | } 26 | return NO; 27 | } 28 | 29 | - (void)testRendersWelcomeScreen 30 | { 31 | UIViewController *vc = [[[RCTSharedApplication() delegate] window] rootViewController]; 32 | NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS]; 33 | BOOL foundElement = NO; 34 | 35 | __block NSString *redboxError = nil; 36 | #ifdef DEBUG 37 | RCTSetLogFunction( 38 | ^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) { 39 | if (level >= RCTLogLevelError) { 40 | redboxError = message; 41 | } 42 | }); 43 | #endif 44 | 45 | while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) { 46 | [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 47 | [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 48 | 49 | foundElement = [self findSubviewInView:vc.view 50 | matching:^BOOL(UIView *view) { 51 | if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) { 52 | return YES; 53 | } 54 | return NO; 55 | }]; 56 | } 57 | 58 | #ifdef DEBUG 59 | RCTSetLogFunction(RCTDefaultLogFunction); 60 | #endif 61 | 62 | XCTAssertNil(redboxError, @"RedBox error: %@", redboxError); 63 | XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS); 64 | } 65 | 66 | @end 67 | -------------------------------------------------------------------------------- /Example/ios/ExampleTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Example/ios/Podfile: -------------------------------------------------------------------------------- 1 | # Resolve react_native_pods.rb with node to allow for hoisting 2 | require Pod::Executable.execute_command('node', ['-p', 3 | 'require.resolve( 4 | "react-native/scripts/react_native_pods.rb", 5 | {paths: [process.argv[1]]}, 6 | )', __dir__]).strip 7 | 8 | platform :ios, min_ios_version_supported 9 | prepare_react_native_project! 10 | 11 | linkage = ENV['USE_FRAMEWORKS'] 12 | if linkage != nil 13 | Pod::UI.puts "Configuring Pod with #{linkage}ally linked Frameworks".green 14 | use_frameworks! :linkage => linkage.to_sym 15 | end 16 | 17 | target 'Example' do 18 | config = use_native_modules! 19 | 20 | use_react_native!( 21 | :path => config[:reactNativePath], 22 | # An absolute path to your application root. 23 | :app_path => "#{Pod::Config.instance.installation_root}/.." 24 | ) 25 | 26 | target 'ExampleTests' do 27 | inherit! :complete 28 | # Pods for testing 29 | end 30 | 31 | post_install do |installer| 32 | # https://github.com/facebook/react-native/blob/main/packages/react-native/scripts/react_native_pods.rb#L197-L202 33 | react_native_post_install( 34 | installer, 35 | config[:reactNativePath], 36 | :mac_catalyst_enabled => false, 37 | # :ccache_enabled => true 38 | ) 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /Example/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'react-native', 3 | }; 4 | -------------------------------------------------------------------------------- /Example/metro.config.js: -------------------------------------------------------------------------------- 1 | const {getDefaultConfig, mergeConfig} = require('@react-native/metro-config'); 2 | const path = require('path'); 3 | const exclusionList = require('metro-config/src/defaults/exclusionList'); 4 | const escape = require('escape-string-regexp'); 5 | const rootPath = path.resolve(__dirname, '..'); 6 | 7 | const exclude = ['react', 'react-native', '@babel/runtime']; 8 | 9 | const extraNodeModules = { 10 | 'react': path.resolve(__dirname + '/node_modules/react'), 11 | 'react-native': path.resolve(__dirname + '/node_modules/react-native'), 12 | 'react-native-iaphub': path.resolve(__dirname + '/node_modules/react-native-iaphub'), 13 | '@babel/runtime': path.resolve(__dirname + '/node_modules/@babel/runtime'), 14 | 'react-native-iaphub-ui': path.resolve(__dirname + '/..'), 15 | }; 16 | 17 | /** 18 | * Metro configuration 19 | * https://reactnative.dev/docs/metro 20 | * 21 | * @type {import('metro-config').MetroConfig} 22 | */ 23 | const config = { 24 | watchFolders: [ 25 | // Add the parent directory to watch 26 | path.resolve(__dirname, '..'), 27 | ], 28 | resolver: { 29 | blacklistRE: exclusionList(exclude.map((name) => new RegExp(`^${escape(path.join(rootPath, 'node_modules', name))}\\/.*$`))), 30 | extraNodeModules: extraNodeModules 31 | }, 32 | transformer: { 33 | getTransformOptions: async () => ({ 34 | transform: { 35 | experimentalImportSupport: false, 36 | inlineRequires: true, 37 | }, 38 | }), 39 | }, 40 | }; 41 | 42 | module.exports = mergeConfig(getDefaultConfig(__dirname), config); 43 | -------------------------------------------------------------------------------- /Example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.iaphub.example", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "android": "react-native run-android", 7 | "ios": "react-native run-ios", 8 | "lint": "eslint .", 9 | "start": "react-native start", 10 | "test": "jest" 11 | }, 12 | "dependencies": { 13 | "mobx": "^6.13.5", 14 | "mobx-react": "^7.6.0", 15 | "react": "18.3.1", 16 | "react-native": "0.76.5", 17 | "react-native-iaphub": "^8.9.0" 18 | }, 19 | "devDependencies": { 20 | "@babel/core": "^7.25.2", 21 | "@babel/preset-env": "^7.25.3", 22 | "@babel/runtime": "^7.26.0", 23 | "@react-native-community/cli": "15.0.1", 24 | "@react-native-community/cli-platform-android": "15.0.1", 25 | "@react-native-community/cli-platform-ios": "15.0.1", 26 | "@react-native/babel-preset": "0.76.5", 27 | "@react-native/eslint-config": "0.76.5", 28 | "@react-native/metro-config": "0.76.5", 29 | "@react-native/typescript-config": "0.76.5", 30 | "@types/react": "^18.2.6", 31 | "@types/react-test-renderer": "^18.0.0", 32 | "babel-jest": "^29.6.3", 33 | "eslint": "^8.19.0", 34 | "jest": "^29.6.3", 35 | "prettier": "2.8.8", 36 | "react-test-renderer": "18.3.1", 37 | "typescript": "5.0.4" 38 | }, 39 | "engines": { 40 | "node": ">=18" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Example/src-data-provider/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {StyleSheet, Text, View, Image, SafeAreaView} from 'react-native'; 3 | import {IaphubDataProvider, IaphubDataConsumer, PaywallSubscriptionGroup} from 'react-native-iaphub-ui'; 4 | 5 | class App extends React.Component { 6 | 7 | renderPaywall() { 8 | return ( 9 | 10 | Get membership 11 | 12 | 13 | 14 | 15 | {(paywallProps) => } 16 | 17 | 18 | ) 19 | } 20 | 21 | onError(err) { 22 | console.log("IAPHUB Error: ", err); 23 | } 24 | 25 | onPurchase(transaction) { 26 | console.log("IAPHUB transaction: ", transaction); 27 | } 28 | 29 | render() { 30 | // Render IaphubDataProvider at the root of your app on app start 31 | return ( 32 | 33 | 34 | 42 | {this.renderPaywall()} 43 | 44 | 45 | 46 | ); 47 | } 48 | 49 | } 50 | 51 | const styles = StyleSheet.create({ 52 | root: { 53 | flex: 1, 54 | backgroundColor: '#111566' 55 | }, 56 | paywall: { 57 | flex: 1 58 | }, 59 | title: { 60 | fontSize: 24, 61 | fontWeight: '600', 62 | textAlign: 'center', 63 | marginTop: 20, 64 | marginBottom: 20, 65 | color: 'white', 66 | textTransform: 'uppercase' 67 | }, 68 | imageWrapper: { 69 | alignItems: 'center' 70 | }, 71 | image: { 72 | width: 250, 73 | height: 198, 74 | marginBottom: 20 75 | } 76 | }); 77 | 78 | export default App; -------------------------------------------------------------------------------- /Example/src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {StyleSheet, Text, View, Image, SafeAreaView, ActivityIndicator, StatusBar} from 'react-native'; 3 | import {Observer} from 'mobx-react'; 4 | import {PaywallSubscriptionGroup} from 'react-native-iaphub-ui'; 5 | import store from './store'; 6 | 7 | class App extends React.Component { 8 | 9 | componentDidMount() { 10 | store.init(); 11 | } 12 | 13 | renderPaywall() { 14 | return ( 15 | store.buy(product.sku)} 22 | onRestoreStart={() => store.restore()} 23 | showManageSubscriptions={(product) => store.showManageSubscriptions(product.sku)} 24 | onRefreshProducts={() => store.refreshProducts()} 25 | /> 26 | ) 27 | } 28 | 29 | renderContent = () => { 30 | return ( 31 | 32 | 33 | 34 | Get membership 35 | 36 | 37 | 38 | {store.isLoaded && this.renderPaywall()} 39 | {!store.isLoaded && } 40 | 41 | 42 | ); 43 | } 44 | 45 | render() { 46 | return ( 47 | 48 | {this.renderContent} 49 | 50 | ) 51 | } 52 | 53 | } 54 | 55 | const styles = StyleSheet.create({ 56 | root: { 57 | flex: 1, 58 | backgroundColor: '#111566' 59 | }, 60 | title: { 61 | fontSize: 24, 62 | fontWeight: '600', 63 | textAlign: 'center', 64 | marginTop: 20, 65 | marginBottom: 20, 66 | color: 'white', 67 | textTransform: 'uppercase' 68 | }, 69 | imageWrapper: { 70 | alignItems: 'center' 71 | }, 72 | image: { 73 | width: 250, 74 | height: 198, 75 | marginBottom: 20 76 | } 77 | }); 78 | 79 | export default App; 80 | -------------------------------------------------------------------------------- /Example/src/store.js: -------------------------------------------------------------------------------- 1 | import {makeAutoObservable, configure} from "mobx" 2 | import Iaphub from 'react-native-iaphub'; 3 | import {buyWithAlert} from 'react-native-iaphub-ui'; 4 | 5 | configure({enforceActions: "never"}); 6 | 7 | class Store { 8 | 9 | isLoaded = false; 10 | productsForSale = null; 11 | activeProducts = null; 12 | err = null; 13 | 14 | constructor() { 15 | makeAutoObservable(this) 16 | } 17 | 18 | // Init IAPHUB 19 | async init() { 20 | // Init iaphub 21 | Iaphub.start({ 22 | // The app id is available on the settings page of your app 23 | appId: "5e4890f6c61fc971cf46db4d", 24 | // The (client) api key is available on the settings page of your app 25 | apiKey: "SDp7aY220RtzZrsvRpp4BGFm6qZqNkNf", 26 | userId: "8888", 27 | // Allow anonymous purchases 28 | allowAnonymousPurchase: true 29 | }); 30 | // Listen to user updates and refresh productsForSale/activeProducts 31 | Iaphub.addEventListener('onUserUpdate', async () => { 32 | await this.refreshProducts(); 33 | }); 34 | Iaphub.addEventListener('onBuyRequest', async (opts) => { 35 | await buyWithAlert(() => Iaphub.buy(opts.sku)); 36 | }); 37 | // Load products 38 | await this.refreshProducts(); 39 | // Mark as loaded 40 | this.isLoaded = true; 41 | } 42 | 43 | async buy(sku) { 44 | await Iaphub.buy(sku); 45 | } 46 | 47 | async restore() { 48 | var response = await Iaphub.restore(); 49 | 50 | return response; 51 | } 52 | 53 | async showManageSubscriptions(sku = null) { 54 | await Iaphub.showManageSubscriptions({sku: sku}); 55 | } 56 | 57 | // Refresh products 58 | async refreshProducts() { 59 | try { 60 | this.activeProducts = [ 61 | { 62 | id: "5e5198930c48ed07aa275fd9", 63 | type: "renewable_subscription", 64 | sku: "membership_1", 65 | group: "3e5198930c48ed07aa275fd8", 66 | groupName: "subscription_group_1", 67 | localizedTitle: "Membership 1 month", 68 | localizedDescription: "Become a member of the community", 69 | localizedPrice: "$9.99", 70 | price: 9.99, 71 | currency: "USD", 72 | subscriptionDuration: "P1M", 73 | subscriptionIntroPhases: [], 74 | subscriptionState: 'active', 75 | isSubscriptionRenewable: true, 76 | platform: 'android' 77 | } 78 | ]; 79 | this.productsForSale = [ 80 | { 81 | id: "5e5198930c48ed07aa275fd9", 82 | type: "renewable_subscription", 83 | sku: "membership_1", 84 | group: "3e5198930c48ed07aa275fd8", 85 | groupName: "subscription_group_1", 86 | localizedTitle: "Basic", 87 | localizedDescription: "Become a member of the community", 88 | localizedPrice: "$9.99", 89 | price: 9.99, 90 | currency: "USD", 91 | subscriptionDuration: "P1M", 92 | subscriptionIntroPhases: [ 93 | { 94 | type: "trial", 95 | price: 0, 96 | currency: "USD", 97 | localizedPrice: "FREE", 98 | cycleDuration: "P1M", 99 | cycleCount: 1, 100 | payment: "upfront" 101 | }, 102 | { 103 | type: "intro", 104 | price: 4.99, 105 | currency: "USD", 106 | localizedPrice: "$4.99", 107 | cycleDuration: "P1M", 108 | cycleCount: 3, 109 | payment: "as_you_go" 110 | } 111 | ] 112 | }, 113 | { 114 | id: "5e5198930c48ed07aa275fd8", 115 | type: "renewable_subscription", 116 | sku: "membership_6months", 117 | group: "3e5198930c48ed07aa275fd8", 118 | groupName: "subscription_group_1", 119 | localizedTitle: "Premium", 120 | localizedDescription: "Become a member of the community", 121 | localizedPrice: "$39.99", 122 | price: 39.99, 123 | currency: "USD", 124 | subscriptionDuration: "P6M", 125 | subscriptionIntroPhases: [ 126 | { 127 | type: "trial", 128 | price: 0, 129 | currency: "USD", 130 | localizedPrice: "FREE", 131 | cycleDuration: "P1M", 132 | cycleCount: 2, 133 | payment: "upfront" 134 | }, 135 | { 136 | type: "intro", 137 | price: 4.99, 138 | currency: "USD", 139 | localizedPrice: "$4.99", 140 | cycleDuration: "P1M", 141 | cycleCount: 3, 142 | payment: "as_you_go" 143 | } 144 | ] 145 | }, 146 | { 147 | id: "5e5198930c48ed07aa275fd7", 148 | type: "renewable_subscription", 149 | sku: "membership_12months", 150 | group: "3e5198930c48ed07aa275fd8", 151 | groupName: "subscription_group_1", 152 | localizedTitle: "Pro", 153 | localizedDescription: "Become a member of the community", 154 | localizedPrice: "$69.99", 155 | price: 69.99, 156 | currency: "USD", 157 | subscriptionDuration: "P1Y", 158 | subscriptionIntroPhases: [ 159 | { 160 | type: "trial", 161 | price: 0, 162 | currency: "USD", 163 | localizedPrice: "FREE", 164 | cycleDuration: "P1M", 165 | cycleCount: 1, 166 | payment: "upfront" 167 | }, 168 | { 169 | type: "intro", 170 | price: 4.99, 171 | currency: "USD", 172 | localizedPrice: "$4.99", 173 | cycleDuration: "P1M", 174 | cycleCount: 3, 175 | payment: "as_you_go" 176 | } 177 | ] 178 | } 179 | ]; 180 | } 181 | catch (err) { 182 | this.err = err; 183 | } 184 | } 185 | 186 | } 187 | 188 | export default new Store(); -------------------------------------------------------------------------------- /Example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@react-native/typescript-config/tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 IAPHUB 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | IAPHUB 3 | 4 |
5 |
6 | 7 | As you know we're on a mission to make in-app purchase as easy as possible.
8 | The [react-native-iaphub](https://github.com/iaphub/react-native-iaphub) is already doing that pretty well but we've decided to make things even easier! 9 | 10 | Our React Native component ***react-native-iaphub-ui*** is providing all the UI necessary to sell in-app purchases!
11 | You'll be able to display a beautiful paywall that is highly customizable and includes (so far) the translations in 6 languages (English, Spanish, French, German, Portuguese, Japanese). 12 | 13 | ## Install component 14 | 15 | ```bash 16 | npm install react-native-iaphub-ui --save 17 | npm install react-native-iaphub --save // Peer dependency 18 | ``` 19 | 20 | ## Initialize IAPHUB 21 | 22 | In order to initialize the IAPHUB SDK, you must render the `IaphubDataProvider` component in the main tree on your app (it should be executed when starting the app). 23 | 24 | ```jsx 25 | import {IaphubDataProvider} from 'react-native-iaphub-ui'; 26 | 27 | 33 | {/* App Code */} 34 | 35 | ``` 36 | 37 | #### Properties of the component: 38 | 39 | | Prop | Type | Description | 40 | | :------------ |:---------------:| :-----| 41 | | appId | `String` | The app id is available on the settings page of your app | 42 | | apiKey | `String` | The (client) api key is available on the settings page of your app | 43 | | lang | `String` | Language ISO code (Possible values: 'en', 'es', 'de', 'fr', 'pt', 'jp') | 44 | | userId | `Boolean` | ***Optional***, user ID of the logged user | 45 | | allowAnonymousPurchase | `Boolean` | ***Optional***, if you want to allow purchases when the user isn't logged in (false by default) | 46 | | userTags | `Object` | ***Optional***, user tags | 47 | | deviceParams | `Object` | ***Optional***, device params | 48 | | i18n | `Object` | ***Optional***, i18n data (if you want to provide your own translations) | 49 | | alert | `Function` | ***Optional***, customize alert (React native Alert.alert by default) | 50 | | showBuySuccessAlert | `Boolean` | ***Optional***, show alert when a purchase is successful (true by default) | 51 | | showBuyErrorAlert | `Boolean` | ***Optional***, show alert when a purchase fails (true by default) | 52 | | onError | `Function` | ***Optional***, event triggered when an error occurs (***err*** provided as argument) | 53 | | onPurchase | `Function` | ***Optional***, event triggered when a purchase is successful (***transaction*** provided as argument) | 54 | 55 | ## Access IAPHUB data 56 | 57 | Whenever it is needed, you can access the IAPHUB data (such as the active subscriptions or the products for sale) by using the `IaphubDataConsumer` component.
58 | The component requires a callback for its children that will be executed with the IAPHUB data.
59 | The component will also be automatically re-rendered everytime the data is updated. 60 | 61 | ```jsx 62 | import {IaphubDataConsumer} from 'react-native-iaphub-ui'; 63 | 64 | 65 | {(iaphubData) => /* Render here anything that needs the IAPHUB data */} 66 | 67 | ``` 68 | 69 | ## Render paywall 70 | 71 | You can render at any time a beautiful paywall by using the `Paywall` component.
72 | The component requires the IAPHUB data. You should use the `IaphubDataConsumer` component in order to get it. 73 | 74 | ```jsx 75 | import {IaphubDataConsumer, Paywall} from 'react-native-iaphub-ui'; 76 | 77 | 78 | {(iaphubData) => } 79 | 80 | ``` 81 | 82 | #### Preview of the component (the image and title isn't part of the component): 83 | 84 | 85 |
86 | 87 | #### Properties of the component 88 | 89 | | Prop | Type | Description | 90 | | :------------ |:---------------:| :-----| 91 | | style | `Object` | Style | 92 | | isLoading | `Boolean` | Display a spinner when the data is loading | 93 | | activeProducts | `Array` | List of active products | 94 | | productsForSale | `Array` | List of products for sale | 95 | | err | `Object/String` | Customize the error message when the list of products for sale is empty by providing the error code | 96 | | lang | `String` | Language ISO code (Possible values: 'en', 'es', 'fr', 'pt', 'jp') ('en' by default) | 97 | | i18n | `Object` | i18n data (if you want to provide your own translations) | 98 | | defaultSelectedProductIndex | `Number` | Index of the product selected by default (0 by default) | 99 | | theme | `Object` | Theme object to customize the styles of the components (see style customization below) | 100 | | display | `String` | Orientation of the products for sale list (Possible values: 'horizontal', 'vertical') ('horizontal' by default) | 101 | | alert | `Function` | Customize alert (React native Alert.alert by default) | 102 | | showBuySuccessAlert | `Boolean` | Show alert when a purchase is successful (true by default) | 103 | | showBuyErrorAlert | `Boolean` | Show alert when a purchase fails (true by default) | 104 | | showRestoreSuccessAlert | `Boolean` | Show alert when a restore is successful (true by default) | 105 | | showRestoreEmptyAlert | `Boolean` | Show alert when a restore didn't detect any transactions to restore (true by default) | 106 | | showRestoreErrorAlert | `Boolean` | Show alert when a restore fails (true by default) | 107 | | onBuyStart | `Function` | Event triggered when the user clicks on the buy button (***product*** provided as argument) | 108 | | onBuyEnd | `Function` | Event triggered when the user purchase is done (***err***, ***transaction*** provided as arguments) | 109 | | onRestoreStart | `Function` | Event triggered when the user clicks on the restore button | 110 | | onRestoreEnd | `Function` | Event triggered when the user restore is done | 111 | | onShowManageSubscriptions | `Function` | Event triggered when the user clicks on the button to manage the subscriptions | 112 | | onRefreshProducts | `Function` | Event triggered when the user clicks on the button to refresh the products (when the loading previously failed) | 113 | 114 | ## Render paywall optimized for a subscription group 115 | 116 | A [subscription group](https://www.iaphub.com/docs/getting-started/create-subscription-groups) is needed when you offer the same subscription with different durations.
117 | Since it is a pretty common practice we've developped a separate `PaywallSubscriptionGroup` component optimized to display a subscription group.
118 | 119 | ⚠ If the data provided doesn't have one (and only) subscription group, an error message will be displayed. 120 | 121 | ```jsx 122 | import {IaphubDataConsumer, PaywallSubscriptionGroup} from 'react-native-iaphub-ui'; 123 | 124 | 125 | {(iaphubData) => } 126 | 127 | ``` 128 | 129 | #### Preview of the component (the image and title isn't part of the component): 130 | 131 | 132 |
133 | 134 | #### Properties of the component 135 | 136 | The component has the same properties as the `Paywall` component. 137 | 138 | ## Customize the paywall's style 139 | 140 | You can customize any style of the component by using the `theme` property.
141 | You simply have to provide an object containing the name and style of the component you're looking to customize.
142 | You can find all the components and styles in our codebase [here](https://github.com/iaphub/react-native-iaphub-ui/tree/master/src/paywall). 143 | 144 | ```jsx 145 | 154 | ``` 155 | 156 | ## Advanced paywall customization 157 | 158 | If customizing the style isn't enough, you can easily replace any component of the paywall.
159 | We recommend copying the component you're looking to replace in our [codebase]((https://github.com/iaphub/react-native-iaphub-ui/tree/master/src/paywall)) first and then modify it as much as you want.
160 | The [PaywallSubscriptionGroup](https://github.com/iaphub/react-native-iaphub-ui/tree/master/src/paywall-subscription-group) is a good example of how we've customized the `Paywall` component to optimize it for a subscription group. 161 | 162 | #### Example modifying the product title component: 163 | 164 | ```jsx 165 | import React, {Component} from 'react'; 166 | import {View, Text} from 'react-native'; 167 | import withStyles from 'react-native-iaphub-ui/src/util/with-styles'; 168 | import style from 'react-native-iaphub-ui/src/util/style'; 169 | 170 | class CustomProductTitle extends Component { 171 | 172 | render() { 173 | var {product, isSelected, styles} = this.props; 174 | 175 | return ( 176 | 177 | {product.localizedTitle} 178 | 179 | ); 180 | } 181 | 182 | } 183 | 184 | const styles = StyleSheet.create({ 185 | // Root style 186 | root: { 187 | fontSize: 22, 188 | color: 'black', 189 | marginBottom: 10, 190 | textAlign: 'left' 191 | }, 192 | // Root style when the product is selected 193 | selectedRoot: { 194 | color: 'white' 195 | } 196 | }); 197 | 198 | export default withStyles(styles, 'ProductTitle')(CustomProductTitle); 199 | ``` 200 | 201 | ```jsx 202 | import CustomProductTitle from './custom-product-title'; 203 | 204 | 205 | ``` 206 | 207 | ## Translations 208 | 209 | We're currently supporting 6 languages (English, Spanish, French, German, Portuguese, Japanese), if you need another language please open a new issue and we'll do our best to implement the language you've requested. -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-iaphub-ui", 3 | "version": "1.2.6", 4 | "description": "Plug & Play react native component for in-app purchase, includes a beautiful Paywall with the content translated in 6 languages", 5 | "main": "src/index.js", 6 | "license": "MIT", 7 | "peerDependencies": { 8 | "react": "*", 9 | "react-native": "*", 10 | "react-native-iaphub": "^8.2.2" 11 | }, 12 | "dependencies": { 13 | "lodash.isequal": "^4.5.0", 14 | "prop-types": "^15.8.1", 15 | "react-native-material-ripple": "^0.9.1" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/i18n/de.js: -------------------------------------------------------------------------------- 1 | export default { 2 | /* 3 | * PayWall component 4 | */ 5 | Paywall: { 6 | restoreSuccessTitle: () => `Wiederherstellung`, 7 | restoreSuccessMessage: () => `Ihre Einkäufe wurden erfolgreich wiederhergestellt.`, 8 | restoreEmptyMessage: () => `Keine zu wiederherstellenden Einkäufe wurden festgestellt`, 9 | restoreErrorTitle: () => `Wiederherstellung`, 10 | restoreErrorMessage: () => `Die Wiederherstellung Ihrer Einkäufe ist fehlgeschlagen.`, 11 | buySuccessTitle: () => `Kauf getätigt`, 12 | buySuccessMessage: () => `Ihr Kauf wurde erfolgreich getätigt.`, 13 | buySuccessNextRenewalTitle: () => `Abonnementänderung vorgenommen`, 14 | buySuccessNextRenewalMessage: () => `Ihr Abonnement wird am nächsten Verlängerungsdatum geändert.`, 15 | buyDeferredPaymentTitle: () => `Zahlung läuft`, 16 | buyDeferredPaymentMessage: () => `Bitte warten Sie, Ihr Abonnement wird validiert.`, 17 | buyErrorTitle: () => `Fehler`, 18 | buyErrorMessage: () => `Ihre Zahlung ist fehlgeschlagen. Bitte versuchen Sie es später.`, 19 | buyErrorMessageReceiptFailed: () => `Wir konnten Ihre Transaktion nicht validieren, geben Sie uns etwas Zeit und wir werden erneut versuchen, Ihre Transaktion so schnell wie mōglich zu validieren.`, 20 | buyErrorMessageProductNotAvailable: () => `Dieses Produkt ist derzeit nicht verfügbar.`, 21 | buyErrorMessageAnonymousPurchaseNotAllowed: () => `Bitte loggen Sie sich an um einen Kauf tätigen zu können.`, 22 | buyErrorMessageTransactionNotFound: () => `Ihre Zahlung ist fehlgeschlagen. Bitte versuchen Sie es später.`, 23 | buyErrorMessageNetworkError: () => `Netzwerkfehler, bitte versuchen Sie es später erneut.`, 24 | buyErrorMessageBillingUnavailable: () => `Einkäufe werden derzeit auf Ihrem Telefon nicht unterstützt. Bitte versuchen Sie es später.`, 25 | buyErrorMessagePlayStoreOutdated: () => `Das Abrechnungssystem ist derzeit nicht verfügbar. Ihre Play Store-App ist veraltet. Bitte aktualisieren Sie sie und versuchen Sie es erneut.`, 26 | buyErrorMessageCrossPlatformConflict: (opts) => `Anscheinend haben Sie bereits ein aktives Abonnement auf einer anderen Plattform (${opts.platform}), bitte verwenden Sie dieselbe Plattform, um Ihr Abonnement zu ändern, oder warten Sie, bis es abläuft`, 27 | buyErrorMessageProductAlreadyPurchased: () => `Kauf bereits getätigt Wenn Sie keinen Zugriff auf das Produkt haben, stellen Sie bitte Ihre Einkäufe wieder her.`, 28 | buyErrorMessageUserConflict: () => `Produkt, das einem anderen Benutzer gehört, melden Sie sich bitte bei dem Konto an, das Sie während des Kaufs verwendet haben, oder stellen Sie Ihre Einkäufe wieder her.`, 29 | buyErrorMessageProductChangeNextRenewal: () => `Die Änderung Ihres Abonnements wurde bereits vorgenommen, das Abonnement wird an Ihrem nächsten Verlängerungsdatum geändert.`, 30 | manageSubscriptionsErrorTitleDifferentPlatform: () => `Fehler`, 31 | manageSubscriptionsErrorMessageDifferentPlatform: (opts) => `Ihr Abonnement wurde auf einer anderen Plattform erworben (${opts.platform}) verwenden Sie bitte dieselbe Plattform, um auf Ihr Abonnement zuzugreifen`, 32 | ok: () => `OK` 33 | }, 34 | /* 35 | * PaywallSubscriptionGroup component 36 | */ 37 | PaywallSubscriptionGroup: { 38 | errorDifferentSubscriptionGroup: () => `Fehler: Wenn Sie mehrere Abonnements verkaufen, müssen diese zur selben Gruppe gehören.` 39 | }, 40 | /* 41 | * ActiveProduct component 42 | */ 43 | ActiveProduct: { 44 | activeProduct: () => `Sie haben ein aktives Produkt`, 45 | activeSubscription: () => `Sie haben ein aktives Abonnement`, 46 | subscriptionPaused: () => `Sie haben ein pausiertes Abonnement`, 47 | subscriptionCannotBeRenewed: () => `Ihre Abonnementverlängerung ist fehlgeschlagen. Bitte aktualisieren Sie Ihre Zahlungsmethode.`, 48 | autoRenewalActive: () => `Automatische Erneuerung`, 49 | autoRenewalDisabled: () => `Automatische Verlängerung deaktiviert` 50 | }, 51 | /* 52 | * ProductsError component 53 | */ 54 | ProductsError: { 55 | noProducts: () => `Kein Produkt im Angebot, bitte versuchen Sie es später.`, 56 | billingUnavailable: () => `Einkäufe werden derzeit auf Ihrem Telefon nicht unterstützt. Bitte versuchen Sie es später.`, 57 | playStoreOutdated: () => `Das Abrechnungssystem ist derzeit nicht verfügbar. Ihre Play Store-App ist veraltet. Bitte aktualisieren Sie sie und versuchen Sie es erneut.`, 58 | networkError: () => `Netzwerkfehler, bitte versuchen Sie es später erneut.`, 59 | unexpectedError: (opts) => `Unerwarteter Fehler, bitte versuchen Sie es später erneut (${opts.code}).`, 60 | startMissingError: () => `IAPHUB wurde nicht ordnungsgemäß gestartet.`, 61 | tryAgain: () => `Versuchen Sie es nochmal` 62 | }, 63 | /* 64 | * SubscriptionDuration component 65 | */ 66 | SubscriptionDuration: { 67 | // day 68 | day: () => `Tag`, 69 | // week 70 | week: () => `Woche`, 71 | // month 72 | month: () => `Monat`, 73 | // year 74 | year: () => `Jahr`, 75 | // ${1} day 76 | xDay: (opts) => `${opts.count} Tag`, 77 | // ${3} days 78 | xDays: (opts) => `${opts.count} Tage`, 79 | // ${1} week 80 | xWeek: (opts) => `${opts.count} Woche`, 81 | // ${3} weeks 82 | xWeeks: (opts) => `${opts.count} Wochen`, 83 | // ${1} month 84 | xMonth: (opts) => `${opts.count} Monat`, 85 | // ${3} months 86 | xMonths: (opts) => `${opts.count} Monate`, 87 | // ${1} year 88 | xYear: (opts) => `${opts.count} Jahr`, 89 | // ${1} years 90 | xYears: (opts) => `${opts.count} Jahre` 91 | }, 92 | /* 93 | * SubscriptionPriceDuration component 94 | */ 95 | SubscriptionPriceDuration: { 96 | // ${$4.99}/${month} 97 | cycle: (opts) => `${opts.price}/${opts.duration}`, 98 | // ${$4.99} for ${3 months} 99 | cycles: (opts) => `${opts.price} für ${opts.duration}` 100 | }, 101 | /* 102 | * IntroPhasesWrapper component 103 | */ 104 | IntroPhasesWrapper: { 105 | title: () => `Abonnieren Sie jetzt und genießen Sie:` 106 | }, 107 | /* 108 | * IntroPhase component 109 | */ 110 | IntroPhase: { 111 | // ${1 month} free trial 112 | freeTrial: (opts) => `${opts.duration} kostenlose Testversion`, 113 | // ${$4.99/month} for the first ${6 months} 114 | introPrice: (opts) => `${opts.priceDuration} für ${opts.duration}` 115 | }, 116 | /* 117 | * SubscriptionTerms component 118 | */ 119 | SubscriptionTerms: { 120 | subscriptionTermsTitle: () => `Wiederkerender Kauf, jederzeit kündbar.`, 121 | subscriptionTermsDescription: (opts) => { 122 | if (opts.platform == 'android') { 123 | return `Ihr Abonnement wird automatisch zum gleichen Preis und für die gleiche Dauer verlängert, bis Sie es jederzeit im Play Store kündigen.`; 124 | } 125 | return `Ihr Abonnement wird automatisch zum gleichen Preis und für die gleiche Dauer verlängert, bis Sie es jederzeit im App Store kündigen.`; 126 | }, 127 | subscriptionTermsFreeTrialTitle: (opts) => { 128 | if (opts.platform == 'android') { 129 | return `Während des kostenlosen Testzeitraums wird keine Gebühr erhoben. Jederzeit über den Play Store kündbar.`; 130 | } 131 | return `Während des kostenlosen Testzeitraums wird keine Gebühr erhoben. Jederzeit über den App Store kündbar.`; 132 | }, 133 | subscriptionTermsFreeTrialDescription: (opts) => { 134 | if (opts.platform == 'android') { 135 | return `Die Zahlung wird erst nach Ablauf Ihres kostenlosen Testzeitraums eingeleitet und Ihr Abonnement wird automatisch zum gleichen Preis und für die gleiche Dauer verlängert, bis Sie es jederzeit im Play Store kündigen.`; 136 | } 137 | return `Die Zahlung wird erst nach Ablauf Ihres kostenlosen Testzeitraums eingeleitet und Ihr Abonnement wird automatisch zum gleichen Preis und für die gleiche Dauer verlängert, bis Sie es jederzeit im App Store kündigen.`; 138 | } 139 | }, 140 | /* 141 | * Restore component 142 | */ 143 | Restore: { 144 | restorePurchases: () => `Meine Einkäufe wiederherstellen` 145 | }, 146 | /* 147 | * Buy component 148 | */ 149 | Buy: { 150 | continue: () => `Fortsetzen`, 151 | replaceSubscription: () => `Abonnement ersetzen`, 152 | manageSubscription: () => `Abonnement verwalten` 153 | } 154 | } -------------------------------------------------------------------------------- /src/i18n/en.js: -------------------------------------------------------------------------------- 1 | export default { 2 | /* 3 | * PayWall component 4 | */ 5 | Paywall: { 6 | restoreSuccessTitle: () => `Restore successful`, 7 | restoreSuccessMessage: () => `Your purchases have been restored successfully!`, 8 | restoreEmptyMessage: () => `No purchases to restore have been detected`, 9 | restoreErrorTitle: () => `Restore failed`, 10 | restoreErrorMessage: () => `We were not able to restore your purchases, please try again later.`, 11 | buySuccessTitle: () => `Purchase successful`, 12 | buySuccessMessage: () => `Your purchase has been processed successfully!`, 13 | buySuccessNextRenewalTitle: () => `Subscription change successful`, 14 | buySuccessNextRenewalMessage: () => `Your subscription will be changed on the next renewal date.`, 15 | buyDeferredPaymentTitle: () => `Payment pending`, 16 | buyDeferredPaymentMessage: () => `Please wait, the payment is pending.`, 17 | buyErrorTitle: () => `Error`, 18 | buyErrorMessage: () => `We were not able to process your purchase, please try again later.`, 19 | buyErrorMessageReceiptFailed: () => `We're having trouble validating your transaction, give us some time, we'll retry to validate your transaction as soon as possible.`, 20 | buyErrorMessageProductNotAvailable: () => `The product is currently not available for purchase.`, 21 | buyErrorMessageAnonymousPurchaseNotAllowed: () => `Please log in to your account before making any purchase.`, 22 | buyErrorMessageTransactionNotFound: () => `We were not able to process your purchase, please try again later.`, 23 | buyErrorMessageNetworkError: () => `Network error, please try again later.`, 24 | buyErrorMessageBillingUnavailable: () => `Billing system currently unavailable on your device, please try again later.`, 25 | buyErrorMessagePlayStoreOutdated: () => `The billing system is currently unavailable. Your Play Store app is outdated, please update it and try again.`, 26 | buyErrorMessageCrossPlatformConflict: (opts) => `It seems like you already have a subscription on a different platform (${opts.platform}), please use the same platform to change your subscription or wait for your current subscription to expire.`, 27 | buyErrorMessageProductAlreadyPurchased: () => `Product already owned, if you do not have access to the product please restore your purchases.`, 28 | buyErrorMessageUserConflict: () => `Product owned by a different user, please use the account with which you originally bought the product or restore your purchases.`, 29 | buyErrorMessageProductChangeNextRenewal: () => `Subscription change already effective, your subscription will be updated on the next renewal date.`, 30 | manageSubscriptionsErrorTitleDifferentPlatform: () => `Error`, 31 | manageSubscriptionsErrorMessageDifferentPlatform: (opts) => `Your subscription has been purchased on a different platform (${opts.platform}), please use the same platform to manage your subscription.`, 32 | ok: () => `OK` 33 | }, 34 | /* 35 | * PaywallSubscriptionGroup component 36 | */ 37 | PaywallSubscriptionGroup: { 38 | errorDifferentSubscriptionGroup: () => `Error: If you're selling multiple subscriptions, they must belong to the same subscription group.` 39 | }, 40 | /* 41 | * ActiveProduct component 42 | */ 43 | ActiveProduct: { 44 | activeProduct: () => `You have an active product`, 45 | activeSubscription: () => `You have an active subscription`, 46 | subscriptionPaused: () => `You have a paused subscription`, 47 | subscriptionCannotBeRenewed: () => `The renewal of your subscription failed. Update your payment method to renew.`, 48 | autoRenewalActive: () => `Auto-renewal active`, 49 | autoRenewalDisabled: () => `Auto-renewal disabled` 50 | }, 51 | /* 52 | * ProductsError component 53 | */ 54 | ProductsError: { 55 | noProducts: () => `No products for sale, please try again later.`, 56 | billingUnavailable: () => `Billing system currently unavailable on your device, please try again later.`, 57 | playStoreOutdated: () => `The billing system is currently unavailable. Your Play Store app is outdated, please update it and try again.`, 58 | networkError: () => `Network error, please try again later.`, 59 | unexpectedError: (opts) => `Unexpected error, please try again later (${opts.code}).`, 60 | startMissingError: () => `IAPHUB hasn't been started properly.`, 61 | tryAgain: () => `Try again` 62 | }, 63 | /* 64 | * SubscriptionDuration component 65 | */ 66 | SubscriptionDuration: { 67 | // day 68 | day: () => `day`, 69 | // week 70 | week: () => `week`, 71 | // month 72 | month: () => `month`, 73 | // year 74 | year: () => `year`, 75 | // ${1} day 76 | xDay: (opts) => `${opts.count} day`, 77 | // ${3} days 78 | xDays: (opts) => `${opts.count} days`, 79 | // ${1} week 80 | xWeek: (opts) => `${opts.count} week`, 81 | // ${3} weeks 82 | xWeeks: (opts) => `${opts.count} weeks`, 83 | // ${1} month 84 | xMonth: (opts) => `${opts.count} month`, 85 | // ${3} months 86 | xMonths: (opts) => `${opts.count} months`, 87 | // ${1} year 88 | xYear: (opts) => `${opts.count} year`, 89 | // ${1} years 90 | xYears: (opts) => `${opts.count} years` 91 | }, 92 | /* 93 | * SubscriptionPriceDuration component 94 | */ 95 | SubscriptionPriceDuration: { 96 | // ${$4.99}/${month} 97 | cycle: (opts) => `${opts.price}/${opts.duration}`, 98 | // ${$4.99} for ${3 months} 99 | cycles: (opts) => `${opts.price} for ${opts.duration}` 100 | }, 101 | /* 102 | * IntroPhasesWrapper component 103 | */ 104 | IntroPhasesWrapper: { 105 | title: () => `Subscribe now and enjoy the following:` 106 | }, 107 | /* 108 | * IntroPhase component 109 | */ 110 | IntroPhase: { 111 | // ${1 month} free trial 112 | freeTrial: (opts) => `${opts.duration} free trial`, 113 | // ${$4.99/month} for the first ${6 months} 114 | introPrice: (opts) => `${opts.priceDuration} for the first ${opts.duration}` 115 | }, 116 | /* 117 | * SubscriptionTerms component 118 | */ 119 | SubscriptionTerms: { 120 | subscriptionTermsTitle: () => `Recurring billing. Cancel anytime.`, 121 | subscriptionTermsDescription: (opts) => { 122 | if (opts.platform == 'android') { 123 | return `Your subscription will auto-renew for the same price and duration until you cancel at any time from the Play Store.`; 124 | } 125 | return `Your subscription will auto-renew for the same price and duration until you cancel at any time from the App Store.`; 126 | }, 127 | subscriptionTermsFreeTrialTitle: (opts) => { 128 | if (opts.platform == 'android') { 129 | return `You won't be charged during the free trial period.\nCancel at any time from the Play Store.`; 130 | } 131 | return `You won't be charged during the free trial period.\nCancel at any time from the App Store.`; 132 | }, 133 | subscriptionTermsFreeTrialDescription: (opts) => { 134 | if (opts.platform == 'android') { 135 | return `The payment will only be initiated after your free trial period ends and your subscription will auto-renew for the same price and duration until you cancel at any time from the Play Store.`; 136 | } 137 | return `The payment will only be initiated after your free trial period ends and your subscription will auto-renew for the same price and duration until you cancel at any time from the App Store.`; 138 | } 139 | }, 140 | /* 141 | * Restore component 142 | */ 143 | Restore: { 144 | restorePurchases: () => `Restore purchases` 145 | }, 146 | /* 147 | * Buy component 148 | */ 149 | Buy: { 150 | continue: () => `Continue`, 151 | replaceSubscription: () => `Replace subscription`, 152 | manageSubscription: () => `Manage subscription` 153 | } 154 | } -------------------------------------------------------------------------------- /src/i18n/es.js: -------------------------------------------------------------------------------- 1 | export default { 2 | /* 3 | * PayWall component 4 | */ 5 | Paywall: { 6 | restoreSuccessTitle: () => `Restaurado con éxito`, 7 | restoreSuccessMessage: () => `¡Tus compras se han restaurado correctamente!`, 8 | restoreEmptyMessage: () => `No se han detectado compras para restaurar`, 9 | restoreErrorTitle: () => `Error al restaurar`, 10 | restoreErrorMessage: () => `No hemos podido restaurar tus compras. Inténtalo de nuevo más tarde.`, 11 | buySuccessTitle: () => `Compra realizada correctamente`, 12 | buySuccessMessage: () => `¡Tu compra se ha procesado correctamente!`, 13 | buySuccessNextRenewalTitle: () => `Cambio de suscripción realizado correctamente`, 14 | buySuccessNextRenewalMessage: () => `Tu suscripción se cambiará en la próxima fecha de renovación.`, 15 | buyDeferredPaymentTitle: () => `Pago pendiente`, 16 | buyDeferredPaymentMessage: () => `Espera, el pago está pendiente.`, 17 | buyErrorTitle: () => `Error`, 18 | buyErrorMessage: () => `No hemos podido procesar tu compra. Inténtalo de nuevo más tarde.`, 19 | buyErrorMessageReceiptFailed: () => `Estamos teniendo problemas para validar tu transacción. Danos algo de tiempo y volveremos a intentar validar tu transacción lo antes posible.`, 20 | buyErrorMessageProductNotAvailable: () => `El producto no está disponible para comprarlo actualmente.`, 21 | buyErrorMessageAnonymousPurchaseNotAllowed: () => `Inicia sesión en tu cuenta antes de realizar cualquier compra.`, 22 | buyErrorMessageTransactionNotFound: () => `No hemos podido procesar tu compra. Inténtalo de nuevo más tarde.`, 23 | buyErrorMessageNetworkError: () => `Error de red. Inténtalo de nuevo más tarde.`, 24 | buyErrorMessageBillingUnavailable: () => `El sistema de facturación no está disponible actualmente en tu dispositivo. Inténtalo de nuevo más tarde.`, 25 | buyErrorMessagePlayStoreOutdated: () => `El sistema de facturación no está disponible en este momento. Su aplicación de Play Store está desactualizada. Por favor, actualícela e intente de nuevo.`, 26 | buyErrorMessageCrossPlatformConflict: (opts) => `Parece que ya tienes una suscripción en una plataforma diferente (${opts.platform}). Usa la misma plataforma para cambiar tu suscripción o espera a que caduque tu suscripción actual.`, 27 | buyErrorMessageProductAlreadyPurchased: () => `Ya tienes este producto. Si no tienes acceso al producto, restaura tus compras.`, 28 | buyErrorMessageUserConflict: () => `Producto propiedad de un usuario diferente, utilice la cuenta con la que compró originalmente el producto o restaure sus compras.`, 29 | buyErrorMessageProductChangeNextRenewal: () => `El cambio de suscripción se ha realizado correctamente. Tu suscripción se actualizará en la próxima fecha de renovación.`, 30 | manageSubscriptionsErrorTitleDifferentPlatform: () => `Error`, 31 | manageSubscriptionsErrorMessageDifferentPlatform: (opts) => `Tu suscripción se compró en una plataforma diferente (${opts.platform}). Usa la misma plataforma para administrar tu suscripción.`, 32 | ok: () => `VALE` 33 | }, 34 | /* 35 | * PaywallSubscriptionGroup component 36 | */ 37 | PaywallSubscriptionGroup: { 38 | errorDifferentSubscriptionGroup: () => `Error: si vendes varias suscripciones, estas deben pertenecer al mismo grupo de suscripción.` 39 | }, 40 | /* 41 | * ActiveProduct component 42 | */ 43 | ActiveProduct: { 44 | activeProduct: () => `Tienes un producto activo`, 45 | activeSubscription: () => `Tienes una suscripción activa`, 46 | subscriptionPaused: () => `Tienes una suscripción en pausa`, 47 | subscriptionCannotBeRenewed: () => `Se ha producido un error con la renovación de tu suscripción. Actualiza tu método de pago para renovar.`, 48 | autoRenewalActive: () => `Renovación Automática`, 49 | autoRenewalDisabled: () => `Renovación automática inhabilitada` 50 | }, 51 | /* 52 | * ProductsError component 53 | */ 54 | ProductsError: { 55 | noProducts: () => `No hay productos a la venta. Inténtalo de nuevo más tarde.`, 56 | billingUnavailable: () => `El sistema de facturación no está disponible actualmente en tu dispositivo. Inténtalo de nuevo más tarde.`, 57 | playStoreOutdated: () => `El sistema de facturación no está disponible en este momento. Su aplicación de Play Store está desactualizada. Por favor, actualícela e intente de nuevo.`, 58 | networkError: () => `Error de red. Inténtalo de nuevo más tarde.`, 59 | unexpectedError: (opts) => `Error inesperado, por favor inténtalo de nuevo más tarde (${opts.code}).`, 60 | startMissingError: () => `IAPHUB no se ha iniciado correctamente.`, 61 | tryAgain: () => `Intentar otra vez` 62 | }, 63 | /* 64 | * SubscriptionDuration component 65 | */ 66 | SubscriptionDuration: { 67 | // day 68 | day: () => `día`, 69 | // week 70 | week: () => `semana`, 71 | // month 72 | month: () => `mes`, 73 | // year 74 | year: () => `año`, 75 | // ${1} day 76 | xDay: (opts) => `${opts.count} día`, 77 | // ${3} days 78 | xDays: (opts) => `${opts.count} días`, 79 | // ${1} week 80 | xWeek: (opts) => `${opts.count} semana`, 81 | // ${3} weeks 82 | xWeeks: (opts) => `${opts.count} semanas`, 83 | // ${1} month 84 | xMonth: (opts) => `${opts.count} mes`, 85 | // ${3} months 86 | xMonths: (opts) => `${opts.count} meses`, 87 | // ${1} year 88 | xYear: (opts) => `${opts.count} año`, 89 | // ${1} years 90 | xYears: (opts) => `${opts.count} años` 91 | }, 92 | /* 93 | * SubscriptionPriceDuration component 94 | */ 95 | SubscriptionPriceDuration: { 96 | // ${$4.99}/${month} 97 | cycle: (opts) => `${opts.price}/${opts.duration}`, 98 | // ${$4.99} for ${3 months} 99 | cycles: (opts) => `${opts.price} por ${opts.duration}` 100 | }, 101 | /* 102 | * IntroPhasesWrapper component 103 | */ 104 | IntroPhasesWrapper: { 105 | title: () => `Suscríbete ahora y disfruta de lo siguiente:` 106 | }, 107 | /* 108 | * IntroPhase component 109 | */ 110 | IntroPhase: { 111 | // ${1 month} free trial 112 | freeTrial: (opts) => `${opts.duration} de prueba gratis`, 113 | // ${$4.99/month} for the first ${6 months} 114 | introPrice: (opts) => `${opts.priceDuration} durante los primeros ${opts.duration}` 115 | }, 116 | /* 117 | * SubscriptionTerms component 118 | */ 119 | SubscriptionTerms: { 120 | subscriptionTermsTitle: () => `Facturación periódica. Cancela en cualquier momento.`, 121 | subscriptionTermsDescription: (opts) => { 122 | if (opts.platform == 'android') { 123 | return `Su suscripción se renovará automáticamente por el mismo precio y duración hasta que la cancele en cualquier momento desde la Play Store.`; 124 | } 125 | return `Su suscripción se renovará automáticamente por el mismo precio y duración hasta que la cancele en cualquier momento desde la App Store.`; 126 | }, 127 | subscriptionTermsFreeTrialTitle: (opts) => { 128 | if (opts.platform == 'android') { 129 | return `No se te cobrará durante el período de prueba gratuito.\nPuedes cancelar en cualquier momento desde Google Play.`; 130 | } 131 | return `No se te cobrará durante el período de prueba gratuito.\nPuedes cancelar en cualquier momento desde la App Store.`; 132 | }, 133 | subscriptionTermsFreeTrialDescription: (opts) => { 134 | if (opts.platform == 'android') { 135 | return `El pago solo se iniciará después de que finalice tu período de prueba gratuito y tu suscripción se renovará automáticamente por el mismo precio y duración hasta que canceles en cualquier momento desde Google Play.`; 136 | } 137 | return `El pago solo se iniciará después de que finalice tu período de prueba gratuito y tu suscripción se renovará automáticamente por el mismo precio y duración hasta que canceles en cualquier momento desde la App Store.`; 138 | } 139 | }, 140 | /* 141 | * Restore component 142 | */ 143 | Restore: { 144 | restorePurchases: () => `Restaurar compras` 145 | }, 146 | /* 147 | * Buy component 148 | */ 149 | Buy: { 150 | continue: () => `Continuar`, 151 | replaceSubscription: () => `Reemplazar suscripción`, 152 | manageSubscription: () => `Administrar suscripción` 153 | } 154 | } -------------------------------------------------------------------------------- /src/i18n/fr.js: -------------------------------------------------------------------------------- 1 | export default { 2 | /* 3 | * PayWall component 4 | */ 5 | Paywall: { 6 | restoreSuccessTitle: () => `Restauration`, 7 | restoreSuccessMessage: () => `Vos achats ont été restaurés avec succès!`, 8 | restoreEmptyMessage: () => `Aucun achat à restaurer n'a été détecté`, 9 | restoreErrorTitle: () => `Restauration`, 10 | restoreErrorMessage: () => `La restauration de vos achats a échoué`, 11 | buySuccessTitle: () => `Achat effectué`, 12 | buySuccessMessage: () => `Votre achat a été effectué avec succès!`, 13 | buySuccessNextRenewalTitle: () => `Changement d'abonnement effectué`, 14 | buySuccessNextRenewalMessage: () => `Votre abonnement sera changé à votre prochaine date de renouvellement.`, 15 | buyDeferredPaymentTitle: () => `Paiement en cours...`, 16 | buyDeferredPaymentMessage: () => `Merci de patienter, votre paiement est en cours de validation.`, 17 | buyErrorTitle: () => `Erreur`, 18 | buyErrorMessage: () => `Votre paiement a échoué, veuillez réessayer plus tard.`, 19 | buyErrorMessageReceiptFailed: () => `Nous n'avons pu valider votre transaction, donnez-nous un peu de temps et nous allons réessayer de valider votre transaction au plus vite.`, 20 | buyErrorMessageProductNotAvailable: () => `Ce produit n'est actuellement pas disponible.`, 21 | buyErrorMessageAnonymousPurchaseNotAllowed: () => `Veuillez vous connecter pour pouvoir effectuer un achat.`, 22 | buyErrorMessageTransactionNotFound: () => `Votre paiement a échoué, veuillez réessayer plus tard.`, 23 | buyErrorMessageNetworkError: () => `Erreur réseau, veuillez réessayer plus tard.`, 24 | buyErrorMessageBillingUnavailable: () => `Les achats ne sont actuellement pas supportés sur votre téléphone, veuillez réessayer plus tard.`, 25 | buyErrorMessagePlayStoreOutdated: () => `Le système de facturation n'est pas disponible actuellement. Votre application Play Store est obsolète. Veuillez la mettre à jour et réessayer.`, 26 | buyErrorMessageCrossPlatformConflict: (opts) => `Il semblerait que vous ayez déjà un abonnement actif sur une autre plateforme (${opts.platform}), veuillez utiliser la même plateforme pour changer votre abonnement ou attendez que celui-ci expire.`, 27 | buyErrorMessageProductAlreadyPurchased: () => `Achat déjà effectué, si vous n'avez pas accès au produit veuillez restaurer vos achats.`, 28 | buyErrorMessageUserConflict: () => `Produit détenu par un autre utilisateur, veuillez vous connecter au compte que vous utilisiez durant l'achat ou restaurez vos achats.`, 29 | buyErrorMessageProductChangeNextRenewal: () => `Le changement de votre abonnement a déjà été effectué, l'abonnement sera changé à votre prochaine date de renouvellement.`, 30 | manageSubscriptionsErrorTitleDifferentPlatform: () => `Erreur`, 31 | manageSubscriptionsErrorMessageDifferentPlatform: (opts) => `Votre abonnement a été acheté sur une autre plateforme (${opts.platform}), veuillez utiliser la même plateforme pour accéder à votre abonnement`, 32 | ok: () => `OK` 33 | }, 34 | /* 35 | * PaywallSubscriptionGroup component 36 | */ 37 | PaywallSubscriptionGroup: { 38 | errorDifferentSubscriptionGroup: () => `Erreur: Si vous vendez plusieurs abonnements, ils doivent appartenir au même groupe.` 39 | }, 40 | /* 41 | * ActiveProduct component 42 | */ 43 | ActiveProduct: { 44 | activeProduct: () => `Vous avez un produit actif`, 45 | activeSubscription: () => `Vous avez un abonnement actif`, 46 | subscriptionPaused: () => `Vous avez un abonnement en pause`, 47 | subscriptionCannotBeRenewed: () => `Le renouvellement de votre abonnement a échoué. Veuillez mettre à jour votre méthode de paiement.`, 48 | autoRenewalActive: () => `Renouvellement automatique`, 49 | autoRenewalDisabled: () => `Renouvellement automatique désactivé` 50 | }, 51 | /* 52 | * ProductsError component 53 | */ 54 | ProductsError: { 55 | noProducts: () => `Aucun produit en vente, veuillez réessayer plus tard.`, 56 | billingUnavailable: () => `Les achats ne sont actuellement pas supportés sur votre téléphone, veuillez réessayer plus tard.`, 57 | playStoreOutdated: () => `Le système de facturation n'est pas disponible actuellement. Votre application Play Store est obsolète. Veuillez la mettre à jour et réessayer.`, 58 | networkError: () => `Erreur réseau, veuillez réessayer plus tard.`, 59 | unexpectedError: (opts) => `Erreur inattendue, veuillez réessayer ultérieurement (${opts.code}).`, 60 | startMissingError: () => `IAPHUB n'a pas été démarré correctement.`, 61 | tryAgain: () => `Réessayer` 62 | }, 63 | /* 64 | * SubscriptionDuration component 65 | */ 66 | SubscriptionDuration: { 67 | // day 68 | day: () => `jour`, 69 | // week 70 | week: () => `semaine`, 71 | // month 72 | month: () => `mois`, 73 | // year 74 | year: () => `an`, 75 | // ${1} day 76 | xDay: (opts) => `${opts.count} jour`, 77 | // ${3} days 78 | xDays: (opts) => `${opts.count} jours`, 79 | // ${1} week 80 | xWeek: (opts) => `${opts.count} semaine`, 81 | // ${3} weeks 82 | xWeeks: (opts) => `${opts.count} semaines`, 83 | // ${1} month 84 | xMonth: (opts) => `${opts.count} mois`, 85 | // ${3} months 86 | xMonths: (opts) => `${opts.count} mois`, 87 | // ${1} year 88 | xYear: (opts) => `${opts.count} an`, 89 | // ${1} years 90 | xYears: (opts) => `${opts.count} ans` 91 | }, 92 | /* 93 | * SubscriptionPriceDuration component 94 | */ 95 | SubscriptionPriceDuration: { 96 | // ${$4.99}/${month} 97 | cycle: (opts) => `${opts.price}/${opts.duration}`, 98 | // ${$4.99} for ${3 months} 99 | cycles: (opts) => `${opts.price} pour ${opts.duration}` 100 | }, 101 | /* 102 | * IntroPhasesWrapper component 103 | */ 104 | IntroPhasesWrapper: { 105 | title: () => `Souscrivez maintenant et profitez de:` 106 | }, 107 | /* 108 | * IntroPhase component 109 | */ 110 | IntroPhase: { 111 | // ${1 month} free trial 112 | freeTrial: (opts) => `Essai gratuit de ${opts.duration}`, 113 | // ${$4.99/month} for the first ${6 months} 114 | introPrice: (opts) => `${opts.priceDuration} pendant ${opts.duration}` 115 | }, 116 | /* 117 | * SubscriptionTerms component 118 | */ 119 | SubscriptionTerms: { 120 | subscriptionTermsTitle: () => `Achat récurrent. Annulez à tout moment.`, 121 | subscriptionTermsDescription: (opts) => { 122 | if (opts.platform == 'android') { 123 | return `Votre abonnement se renouvellera automatiquement pour le même prix et la même durée jusqu'à ce que vous l'annuliez à tout moment depuis le Play Store.`; 124 | } 125 | return `Votre abonnement se renouvellera automatiquement pour le même prix et la même durée jusqu'à ce que vous l'annuliez à tout moment depuis l'App Store.`; 126 | }, 127 | subscriptionTermsFreeTrialTitle: (opts) => { 128 | if (opts.platform == 'android') { 129 | return `Vous ne serez pas facturé pendant la période d'essai gratuite. Annulez à tout moment depuis le Google Play Store.`; 130 | } 131 | return `Vous ne serez pas facturé pendant la période d'essai gratuite. Annulez à tout moment depuis l'App Store.`; 132 | }, 133 | subscriptionTermsFreeTrialDescription: (opts) => { 134 | if (opts.platform == 'android') { 135 | return `Le paiement ne sera initié qu'après la fin de votre période d'essai gratuite, et votre abonnement sera renouvelé automatiquement pour le même prix et la même durée jusqu'à ce que vous l'annuliez à tout moment depuis le Google Play Store.`; 136 | } 137 | return `Le paiement ne sera initié qu'après la fin de votre période d'essai gratuite, et votre abonnement sera renouvelé automatiquement pour le même prix et la même durée jusqu'à ce que vous l'annuliez à tout moment depuis l'App Store.`; 138 | } 139 | }, 140 | /* 141 | * Restore component 142 | */ 143 | Restore: { 144 | restorePurchases: () => `Restaurer mes achats` 145 | }, 146 | /* 147 | * Buy component 148 | */ 149 | Buy: { 150 | continue: () => `Continuer`, 151 | replaceSubscription: () => `Remplacer abonnement`, 152 | manageSubscription: () => `Gérer abonnement` 153 | } 154 | } -------------------------------------------------------------------------------- /src/i18n/index.js: -------------------------------------------------------------------------------- 1 | import en from './en'; 2 | import fr from './fr'; 3 | import es from './es'; 4 | import jp from './jp'; 5 | import pt from './pt'; 6 | import de from './de'; 7 | import it from './it'; 8 | 9 | const dictionary = { 10 | en: en, 11 | fr: fr, 12 | es: es, 13 | jp: jp, 14 | pt: pt, 15 | de: de, 16 | it: it 17 | }; 18 | 19 | export default (componentName, locale, data) => { 20 | var localeDictionary = dictionary[locale || 'en']; 21 | if (!localeDictionary) { 22 | localeDictionary = dictionary['en']; 23 | } 24 | var componentDictionary = localeDictionary[componentName]; 25 | if (!componentDictionary) { 26 | throw `Component '${componentName}' i18n not found for language ${locale}`; 27 | } 28 | var phrases = Object.assign({}, componentDictionary, data ? data[componentName] : null); 29 | 30 | return (id, opts) => { 31 | var fun = phrases[id]; 32 | 33 | if (typeof fun == 'undefined') { 34 | throw `Translation ${id} undefined`; 35 | } 36 | else if (typeof fun != 'function') { 37 | throw `Translation ${id} invalid`; 38 | } 39 | return fun(opts); 40 | } 41 | } -------------------------------------------------------------------------------- /src/i18n/it.js: -------------------------------------------------------------------------------- 1 | export default { 2 | /* 3 | * PayWall component 4 | */ 5 | Paywall: { 6 | restoreSuccessTitle: () => `Ripristino riuscito`, 7 | restoreSuccessMessage: () => `I tuoi acquisti sono stati ripristinati con successo!`, 8 | restoreEmptyMessage: () => `Nessun acquisto da ripristinare rilevato`, 9 | restoreErrorTitle: () => `Ripristino non riuscito`, 10 | restoreErrorMessage: () => `Non siamo riusciti a ripristinare i tuoi acquisti, riprova più tardi.`, 11 | buySuccessTitle: () => `Acquisto riuscito`, 12 | buySuccessMessage: () => `Il tuo acquisto è stato elaborato con successo!`, 13 | buySuccessNextRenewalTitle: () => `Cambio abbonamento riuscito`, 14 | buySuccessNextRenewalMessage: () => `Il tuo abbonamento verrà cambiato alla prossima data di rinnovo.`, 15 | buyDeferredPaymentTitle: () => `Pagamento in sospeso`, 16 | buyDeferredPaymentMessage: () => `Attendi, il pagamento è in sospeso.`, 17 | buyErrorTitle: () => `Errore`, 18 | buyErrorMessage: () => `Non siamo riusciti a elaborare il tuo acquisto, riprova più tardi.`, 19 | buyErrorMessageReceiptFailed: () => `Stiamo avendo problemi a convalidare la tua transazione, dacci un po' di tempo, riproveremo a convalidare la tua transazione il prima possibile.`, 20 | buyErrorMessageProductNotAvailable: () => `Il prodotto non è attualmente disponibile per l'acquisto.`, 21 | buyErrorMessageAnonymousPurchaseNotAllowed: () => `Effettua il login al tuo account prima di effettuare qualsiasi acquisto.`, 22 | buyErrorMessageTransactionNotFound: () => `Non siamo riusciti a elaborare il tuo acquisto, riprova più tardi.`, 23 | buyErrorMessageNetworkError: () => `Errore di rete, riprova più tardi.`, 24 | buyErrorMessageBillingUnavailable: () => `Sistema di fatturazione attualmente non disponibile sul tuo dispositivo, riprova più tardi.`, 25 | buyErrorMessagePlayStoreOutdated: () => `Il sistema di fatturazione non è al momento disponibile. La tua app Play Store è obsoleta. Si prega di aggiornarla e riprovare.`, 26 | buyErrorMessageCrossPlatformConflict: (opts) => `Sembra che tu abbia già un abbonamento su una piattaforma diversa (${opts.platform}), utilizza la stessa piattaforma per cambiare il tuo abbonamento o attendi che il tuo abbonamento attuale scada.`, 27 | buyErrorMessageProductAlreadyPurchased: () => `Prodotto già posseduto, se non hai accesso al prodotto ripristina i tuoi acquisti.`, 28 | buyErrorMessageUserConflict: () => `Prodotto posseduto da un utente diverso, utilizzare l'account con cui è stato acquistato originariamente o ripristinare gli acquisti.`, 29 | buyErrorMessageProductChangeNextRenewal: () => `Cambio abbonamento già effettivo, il tuo abbonamento verrà aggiornato alla prossima data di rinnovo.`, 30 | manageSubscriptionsErrorTitleDifferentPlatform: () => `Errore`, 31 | manageSubscriptionsErrorMessageDifferentPlatform: (opts) => `Il tuo abbonamento è stato acquistato su una piattaforma diversa (${opts.platform}), utilizzare la stessa piattaforma per gestire il tuo abbonamento.`, 32 | ok: () => `OK` 33 | }, 34 | /* 35 | * PaywallSubscriptionGroup component 36 | */ 37 | PaywallSubscriptionGroup: { 38 | errorDifferentSubscriptionGroup: () => `Errore: Se si vendono più abbonamenti, devono appartenere allo stesso gruppo di abbonamenti.` 39 | }, 40 | /* 41 | * ActiveProduct component 42 | */ 43 | ActiveProduct: { 44 | activeProduct: () => `Hai un prodotto attivo`, 45 | activeSubscription: () => `Hai un abbonamento attivo`, 46 | subscriptionPaused: () => `Hai un abbonamento in pausa`, 47 | subscriptionCannotBeRenewed: () => `Il rinnovo dell'abbonamento non è riuscito. Aggiorna il metodo di pagamento per rinnovare.`, 48 | autoRenewalActive: () => `Rinnovo automatico attivo`, 49 | autoRenewalDisabled: () => `Rinnovo automatico disattivato` 50 | }, 51 | /* 52 | * ProductsError component 53 | */ 54 | ProductsError: { 55 | noProducts: () => `Nessun prodotto in vendita, riprova più tardi.`, 56 | billingUnavailable: () => `Sistema di fatturazione attualmente non disponibile sul tuo dispositivo, riprova più tardi.`, 57 | playStoreOutdated: () => `Il sistema di fatturazione non è al momento disponibile. La tua app Play Store è obsoleta. Si prega di aggiornarla e riprovare.`, 58 | networkError: () => `Errore di rete, riprova più tardi.`, 59 | unexpectedError: (opts) => `Errore imprevisto, si prega di riprovare più tardi (${opts.code}).`, 60 | startMissingError: () => `IAPHUB non è stato avviato correttamente.`, 61 | tryAgain: () => `Riprova` 62 | }, 63 | /* 64 | * SubscriptionDuration component 65 | */ 66 | SubscriptionDuration: { 67 | // day 68 | day: () => `giorno`, 69 | // week 70 | week: () => `settimana`, 71 | // month 72 | month: () => `mese`, 73 | // year 74 | year: () => `anno`, 75 | // ${1} day 76 | xDay: (opts) => `${opts.count} giorno`, 77 | // ${3} days 78 | xDays: (opts) => `${opts.count} giorni`, 79 | // ${1} week 80 | xWeek: (opts) => `${opts.count} settimana`, 81 | // ${3} weeks 82 | xWeeks: (opts) => `${opts.count} settimane`, 83 | // ${1} month 84 | xMonth: (opts) => `${opts.count} mese`, 85 | // ${3} months 86 | xMonths: (opts) => `${opts.count} mesi`, 87 | // ${1} year 88 | xYear: (opts) => `${opts.count} anno`, 89 | // ${1} years 90 | xYears: (opts) => `${opts.count} anni` 91 | }, 92 | /* 93 | * SubscriptionPriceDuration component 94 | */ 95 | SubscriptionPriceDuration: { 96 | // ${$4.99}/${month} 97 | cycle: (opts) => `${opts.price}/${opts.duration}`, 98 | // ${$4.99} for ${3 months} 99 | cycles: (opts) => `${opts.price} per ${opts.duration}` 100 | }, 101 | /* 102 | * IntroPhasesWrapper component 103 | */ 104 | IntroPhasesWrapper: { 105 | title: () => `Iscriviti ora e goditi i seguenti:` 106 | }, 107 | /* 108 | * IntroPhase component 109 | */ 110 | IntroPhase: { 111 | // ${1 month} free trial 112 | freeTrial: (opts) => `${opts.duration} di prova gratuita`, 113 | // ${$4.99/month} for the first ${6 months} 114 | introPrice: (opts) => `${opts.priceDuration} per i primi ${opts.duration}` 115 | }, 116 | /* 117 | * SubscriptionTerms component 118 | */ 119 | SubscriptionTerms: { 120 | subscriptionTermsTitle: () => `Fatturazione ricorrente. Annulla in qualsiasi momento.`, 121 | subscriptionTermsDescription: (opts) => { 122 | if (opts.platform == 'android') { 123 | return `La tua iscrizione si rinnoverà automaticamente allo stesso prezzo e durata fino a quando non la annulli in qualsiasi momento dal Play Store.`; 124 | } 125 | return `La tua iscrizione si rinnoverà automaticamente allo stesso prezzo e durata fino a quando non la annulli in qualsiasi momento dall'App Store.`; 126 | }, 127 | subscriptionTermsFreeTrialTitle: (opts) => { 128 | if (opts.platform == 'android') { 129 | return `Non verrai addebitato durante il periodo di prova gratuito. Puoi annullare in qualsiasi momento dal Play Store.`; 130 | } 131 | return `Non verrai addebitato durante il periodo di prova gratuito. Puoi annullare in qualsiasi momento dall'App Store.`; 132 | }, 133 | subscriptionTermsFreeTrialDescription: (opts) => { 134 | if (opts.platform == 'android') { 135 | return `Il pagamento verrà effettuato solo al termine del periodo di prova gratuito e la tua sottoscrizione si rinnoverà automaticamente allo stesso prezzo e per la stessa durata fino a quando non annullerai la sottoscrizione in qualsiasi momento dal Play Store.`; 136 | } 137 | return `Il pagamento verrà effettuato solo al termine del periodo di prova gratuito e la tua sottoscrizione si rinnoverà automaticamente allo stesso prezzo e per la stessa durata fino a quando non annullerai la sottoscrizione in qualsiasi momento dall'App Store.`; 138 | } 139 | }, 140 | /* 141 | * Restore component 142 | */ 143 | Restore: { 144 | restorePurchases: () => `Ripristina acquisti` 145 | }, 146 | /* 147 | * Buy component 148 | */ 149 | Buy: { 150 | continue: () => `Continua`, 151 | replaceSubscription: () => `Sostituisci sottoscrizione`, 152 | manageSubscription: () => `Gestisci sottoscrizione` 153 | } 154 | } -------------------------------------------------------------------------------- /src/i18n/jp.js: -------------------------------------------------------------------------------- 1 | export default { 2 | /* 3 | * PayWall component 4 | */ 5 | Paywall: { 6 | restoreSuccessTitle: () => `正常に復元されました`, 7 | restoreSuccessMessage: () => `購入が正常に復元されました。`, 8 | restoreEmptyMessage: () => `復元する購入は検出されていません`, 9 | restoreErrorTitle: () => `復元に失敗しました`, 10 | restoreErrorMessage: () => `購入を復元できませんでした。しばらくしてからもう一度お試しください。`, 11 | buySuccessTitle: () => `正常に購入できました`, 12 | buySuccessMessage: () => `購入は正常に処理されました!`, 13 | buySuccessNextRenewalTitle: () => `サブスクリプションの変更が成功しました`, 14 | buySuccessNextRenewalMessage: () => `サブスクリプションは、次回の更新日に変更されます。`, 15 | buyDeferredPaymentTitle: () => `支払い保留中`, 16 | buyDeferredPaymentMessage: () => `お待ちください。支払いは保留中です。`, 17 | buyErrorTitle: () => `エラー`, 18 | buyErrorMessage: () => `購入を処理できませんでした。しばらくしてからもう一度お試しください。`, 19 | buyErrorMessageReceiptFailed: () => `トランザクションの検証で問題が発生しました。しばらくお待ちください。できるだけ早くトランザクションの検証を再試行します。`, 20 | buyErrorMessageProductNotAvailable: () => `この製品は現在購入できません。`, 21 | buyErrorMessageAnonymousPurchaseNotAllowed: () => `購入を行う前に、アカウントにログインしてください。`, 22 | buyErrorMessageTransactionNotFound: () => `購入を処理できませんでした。しばらくしてからもう一度お試しください。`, 23 | buyErrorMessageNetworkError: () => `ネットワーク エラーです。後でもう一度お試しください。`, 24 | buyErrorMessageBillingUnavailable: () => `お使いのデバイスでは課金システムを現在ご利用いただけません。しばらくしてからもう一度お試しください。`, 25 | buyErrorMessagePlayStoreOutdated: () => `請求システムは現在利用できません。プレイストアアプリが古いため、更新してから再試行してください。`, 26 | buyErrorMessageCrossPlatformConflict: (opts) => `既に別のプラットフォーム (${opts.platform}) でサブスクリプションをお持ちのようです。同じプラットフォームを使用してサブスクリプションを変更するか、現在のサブスクリプションが期限切れになるまでお待ちください。`, 27 | buyErrorMessageProductAlreadyPurchased: () => `商品は既に所有されています。製品にアクセスできない場合は、購入を復元してください。`, 28 | buyErrorMessageUserConflict: () => `商品が別のユーザーに所有されています。最初に製品を購入したアカウントを使用するか、購入を復元してください。`, 29 | buyErrorMessageProductChangeNextRenewal: () => `サブスクリプションの変更は既に有効です。サブスクリプションは次の更新日に更新されます。`, 30 | manageSubscriptionsErrorTitleDifferentPlatform: () => `エラー`, 31 | manageSubscriptionsErrorMessageDifferentPlatform: (opts) => `サブスクリプションは別のプラットフォーム (${opts.platform}) で購入されました。同じプラットフォームを使用してサブスクリプションを管理してください。`, 32 | ok: () => `OK` 33 | }, 34 | /* 35 | * PaywallSubscriptionGroup component 36 | */ 37 | PaywallSubscriptionGroup: { 38 | errorDifferentSubscriptionGroup: () => `エラー: 複数のサブスクリプションを販売している場合、それらは同じサブスクリプション グループに属している必要があります。` 39 | }, 40 | /* 41 | * ActiveProduct component 42 | */ 43 | ActiveProduct: { 44 | activeProduct: () => `アクティブな製品があります`, 45 | activeSubscription: () => `アクティブなサブスクリプションがあります。`, 46 | subscriptionPaused: () => `サブスクリプションを一時停止しています。`, 47 | subscriptionCannotBeRenewed: () => `サブスクリプションの更新に失敗しました。お支払い方法を更新して更新してください。`, 48 | autoRenewalActive: () => `自動更新`, 49 | autoRenewalDisabled: () => `自動更新無効` 50 | }, 51 | /* 52 | * ProductsError component 53 | */ 54 | ProductsError: { 55 | noProducts: () => `販売する商品はありません。しばらくしてからもう一度お試しください。`, 56 | billingUnavailable: () => `お使いのデバイスでは課金システムを現在ご利用いただけません。しばらくしてからもう一度お試しください。`, 57 | playStoreOutdated: () => `請求システムは現在利用できません。プレイストアアプリが古いため、更新してから再試行してください。`, 58 | networkError: () => `ネットワーク エラーです。後でもう一度お試しください。`, 59 | unexpectedError: (opts) => `予期せぬエラーが発生しました。後でもう一度お試しください (${opts.code})。`, 60 | startMissingError: () => `IAPHUBが正しく開始されていません。`, 61 | tryAgain: () => `再試行` 62 | }, 63 | /* 64 | * SubscriptionDuration component 65 | */ 66 | SubscriptionDuration: { 67 | // day 68 | day: () => `日`, 69 | // week 70 | week: () => `週`, 71 | // month 72 | month: () => `月`, 73 | // year 74 | year: () => `年`, 75 | // ${1} day 76 | xDay: (opts) => `${opts.count}日`, 77 | // ${3} days 78 | xDays: (opts) => `${opts.count}日`, 79 | // ${1} week 80 | xWeek: (opts) => `${opts.count}週間`, 81 | // ${3} weeks 82 | xWeeks: (opts) => `${opts.count}週間`, 83 | // ${1} month 84 | xMonth: (opts) => `${opts.count}ヶ月`, 85 | // ${3} months 86 | xMonths: (opts) => `${opts.count}ヶ月`, 87 | // ${1} year 88 | xYear: (opts) => `${opts.count}年`, 89 | // ${1} years 90 | xYears: (opts) => `${opts.count}年` 91 | }, 92 | /* 93 | * SubscriptionPriceDuration component 94 | */ 95 | SubscriptionPriceDuration: { 96 | // ${$4.99}/${month} 97 | cycle: (opts) => `${opts.price}/${opts.duration}`, 98 | // ${$4.99} for ${3 months} 99 | cycles: (opts) => `${opts.duration} か月間 ${opts.price}` 100 | }, 101 | /* 102 | * IntroPhasesWrapper component 103 | */ 104 | IntroPhasesWrapper: { 105 | title: () => `今すぐサブスクライブして、以下をお楽しみください:` 106 | }, 107 | /* 108 | * IntroPhase component 109 | */ 110 | IntroPhase: { 111 | // ${1 month} free trial 112 | freeTrial: (opts) => `${opts.duration}無料トライアル`, 113 | // ${$4.99/month} for the first ${6 months} 114 | introPrice: (opts) => `最初の${opts.duration}間は${opts.priceDuration}`, 115 | }, 116 | /* 117 | * SubscriptionTerms component 118 | */ 119 | SubscriptionTerms: { 120 | subscriptionTermsTitle: () => `定期請求。いつでもキャンセルできます。`, 121 | subscriptionTermsDescription: (opts) => { 122 | if (opts.platform == 'android') { 123 | return `Playストアからいつでもキャンセルできるまで、同じ価格と期間で自動更新されます。`; 124 | } 125 | return `App Store からいつでもキャンセルできるまで、同じ価格と期間で自動更新されます。`; 126 | }, 127 | subscriptionTermsFreeTrialTitle: (opts) => { 128 | if (opts.platform == 'android') { 129 | return `無料トライアル期間中には料金はかかりません。\nGoogle Playからいつでもキャンセルできます。`; 130 | } 131 | return `無料トライアル期間中には料金はかかりません。\nApp Storeからいつでもキャンセルできます。`; 132 | }, 133 | subscriptionTermsFreeTrialDescription: (opts) => { 134 | if (opts.platform == 'android') { 135 | return `無料トライアル期間が終了した後にのみ支払いが開始され、サブスクリプションは同じ価格と期間で自動更新され、Google Playからいつでもキャンセルするまで継続されます。`; 136 | } 137 | return `無料トライアル期間が終了した後にのみ支払いが開始され、サブスクリプションは同じ価格と期間で自動更新され、App Storeからいつでもキャンセルするまで継続されます。`; 138 | } 139 | }, 140 | /* 141 | * Restore component 142 | */ 143 | Restore: { 144 | restorePurchases: () => `購入を復元` 145 | }, 146 | /* 147 | * Buy component 148 | */ 149 | Buy: { 150 | continue: () => `続行`, 151 | replaceSubscription: () => `サブスクリプションを置き換える`, 152 | manageSubscription: () => `サブスクリプションを管理する` 153 | } 154 | } -------------------------------------------------------------------------------- /src/i18n/pt.js: -------------------------------------------------------------------------------- 1 | export default { 2 | /* 3 | * PayWall component 4 | */ 5 | Paywall: { 6 | restoreSuccessTitle: () => `Restauração bem-sucedida`, 7 | restoreSuccessMessage: () => `As suas compras foram restauradas com sucesso!`, 8 | restoreEmptyMessage: () => `Nenhuma compra para restaurar foi detectada`, 9 | restoreErrorTitle: () => `Restauração falhada`, 10 | restoreErrorMessage: () => `Não nos foi possível restaurar as suas compras, tente novamente mais tarde.`, 11 | buySuccessTitle: () => `Compra bem-sucedida`, 12 | buySuccessMessage: () => `A sua compra foi processada com sucesso!`, 13 | buySuccessNextRenewalTitle: () => `Alteração de subscrição bem sucedida`, 14 | buySuccessNextRenewalMessage: () => `A sua subscrição será alterada na próxima data de renovação.`, 15 | buyDeferredPaymentTitle: () => `Pagamento pendente`, 16 | buyDeferredPaymentMessage: () => `Por favor aguarde, o pagamento está pendente.`, 17 | buyErrorTitle: () => `Erro`, 18 | buyErrorMessage: () => `Não nos foi possível processar a sua compra, tente novamente mais tarde.`, 19 | buyErrorMessageReceiptFailed: () => `Estamos a ter dificuldades em validar a sua transação, dê-nos algum tempo, tentaremos novamente validar a sua transação, assim que possível.`, 20 | buyErrorMessageProductNotAvailable: () => `O produto não está atualmente disponível para compra.`, 21 | buyErrorMessageAnonymousPurchaseNotAllowed: () => `Por favor, inicie sessão na sua conta antes de efetuar qualquer compra.`, 22 | buyErrorMessageTransactionNotFound: () => `Não nos foi possível processar a sua compra, tente novamente mais tarde.`, 23 | buyErrorMessageNetworkError: () => `Erro de rede, tente novamente mais tarde.`, 24 | buyErrorMessageBillingUnavailable: () => `Sistema de faturação atualmente indisponível no seu dispositivo, tente novamente mais tarde.`, 25 | buyErrorMessagePlayStoreOutdated: () => `O sistema de faturamento não está disponível no momento. Seu aplicativo da Play Store está desatualizado, por favor atualize-o e tente novamente.`, 26 | buyErrorMessageCrossPlatformConflict: (opts) => `Parece que já tem uma subscrição numa plataforma diferente (${opts.platform}). Por favor utilize a mesma plataforma para alterar a sua subscrição ou espere que a sua subscrição atual expire.`, 27 | buyErrorMessageProductAlreadyPurchased: () => `Já possui este produto. Se não tiver acesso ao produto, restaure as suas compras.`, 28 | buyErrorMessageUserConflict: () => `Este produto pertence a um utilizador diferente. Por favor utilize a conta com a qual comprou originalmente o produto ou restaure as suas compras.`, 29 | buyErrorMessageProductChangeNextRenewal: () => `A alteração da subscrição já foi efetuada, a sua subscrição será atualizada na próxima data de renovação.`, 30 | manageSubscriptionsErrorTitleDifferentPlatform: () => `Erro`, 31 | manageSubscriptionsErrorMessageDifferentPlatform: (opts) => `A sua subscrição foi adquirida numa plataforma diferente (${opts.platform}). Por favor utilize a mesma plataforma para gerir a sua subscrição.`, 32 | ok: () => `OK` 33 | }, 34 | /* 35 | * PaywallSubscriptionGroup component 36 | */ 37 | PaywallSubscriptionGroup: { 38 | errorDifferentSubscriptionGroup: () => `Erro: Se estiver a vender várias subscrições, estas devem pertencer ao mesmo grupo de subscrição.` 39 | }, 40 | /* 41 | * ActiveProduct component 42 | */ 43 | ActiveProduct: { 44 | activeProduct: () => `Você tem um produto ativo`, 45 | activeSubscription: () => `Tem uma subscrição ativa`, 46 | subscriptionPaused: () => `Tem uma subscrição em pausa`, 47 | subscriptionCannotBeRenewed: () => `A renovação da sua subscrição falhou. Atualize o seu método de pagamento para renovar.`, 48 | autoRenewalActive: () => `Renovação automática`, 49 | autoRenewalDisabled: () => `Renovação automática desativada` 50 | }, 51 | /* 52 | * ProductsError component 53 | */ 54 | ProductsError: { 55 | noProducts: () => `Não há produtos à venda, tente novamente mais tarde.`, 56 | billingUnavailable: () => `Sistema de faturação atualmente indisponível no seu dispositivo, tente novamente mais tarde.`, 57 | playStoreOutdated: () => `O sistema de faturamento não está disponível no momento. Seu aplicativo da Play Store está desatualizado, por favor atualize-o e tente novamente.`, 58 | networkError: () => `Erro de rede, tente novamente mais tarde.`, 59 | unexpectedError: (opts) => `Erro inesperado, por favor tente novamente mais tarde (${opts.code}).`, 60 | startMissingError: () => `O IAPHUB não foi iniciado corretamente.`, 61 | tryAgain: () => `Tente novamente` 62 | }, 63 | /* 64 | * SubscriptionDuration component 65 | */ 66 | SubscriptionDuration: { 67 | // day 68 | day: () => `dia`, 69 | // week 70 | week: () => `semana`, 71 | // month 72 | month: () => `mês`, 73 | // year 74 | year: () => `ano`, 75 | // ${1} day 76 | xDay: (opts) => `${opts.count} dia`, 77 | // ${3} days 78 | xDays: (opts) => `${opts.count} dias`, 79 | // ${1} week 80 | xWeek: (opts) => `${opts.count} semana`, 81 | // ${3} weeks 82 | xWeeks: (opts) => `${opts.count} semanas`, 83 | // ${1} month 84 | xMonth: (opts) => `${opts.count} mês`, 85 | // ${3} months 86 | xMonths: (opts) => `${opts.count} meses`, 87 | // ${1} year 88 | xYear: (opts) => `${opts.count} ano`, 89 | // ${1} years 90 | xYears: (opts) => `${opts.count} anos` 91 | }, 92 | /* 93 | * SubscriptionPriceDuration component 94 | */ 95 | SubscriptionPriceDuration: { 96 | // ${$4.99}/${month} 97 | cycle: (opts) => `${opts.price}/${opts.duration}`, 98 | // ${$4.99} for ${3 months} 99 | cycles: (opts) => `${opts.price} por ${opts.duration}` 100 | }, 101 | /* 102 | * IntroPhasesWrapper component 103 | */ 104 | IntroPhasesWrapper: { 105 | title: () => `Subscreva agora e aproveite o seguinte:` 106 | }, 107 | /* 108 | * IntroPhase component 109 | */ 110 | IntroPhase: { 111 | // ${1 month} free trial 112 | freeTrial: (opts) => `${opts.duration} de teste gratuito`, 113 | // ${$4.99/month} for the first ${6 months} 114 | introPrice: (opts) => `${opts.priceDuration} durante os primeiros ${opts.duration}` 115 | }, 116 | /* 117 | * SubscriptionTerms component 118 | */ 119 | SubscriptionTerms: { 120 | subscriptionTermsTitle: () => `Faturação recorrente. Cancele a qualquer momento.`, 121 | subscriptionTermsDescription: (opts) => { 122 | if (opts.platform == 'android') { 123 | return `Sua assinatura será renovada automaticamente pelo mesmo preço e duração até que você cancele a qualquer momento na Play Store.`; 124 | } 125 | return `Sua assinatura será renovada automaticamente pelo mesmo preço e duração até que você cancele a qualquer momento na App Store.`; 126 | }, 127 | subscriptionTermsFreeTrialTitle: (opts) => { 128 | if (opts.platform == 'android') { 129 | return `Você não será cobrado durante o período de teste gratuito. Cancelável a qualquer momento na Play Store.`; 130 | } 131 | return `Você não será cobrado durante o período de teste gratuito. Cancelável a qualquer momento na App Store.`; 132 | }, 133 | subscriptionTermsFreeTrialDescription: (opts) => { 134 | if (opts.platform == 'android') { 135 | return `O pagamento só será iniciado após o término do período de teste gratuito e sua assinatura será renovada automaticamente pelo mesmo preço e duração até que você cancele a qualquer momento na Play Store.`; 136 | } 137 | return `O pagamento só será iniciado após o término do período de teste gratuito e sua assinatura será renovada automaticamente pelo mesmo preço e duração até que você cancele a qualquer momento na App Store.`; 138 | } 139 | }, 140 | /* 141 | * Restore component 142 | */ 143 | Restore: { 144 | restorePurchases: () => `Restaurar compras` 145 | }, 146 | /* 147 | * Buy component 148 | */ 149 | Buy: { 150 | continue: () => `Continuar`, 151 | replaceSubscription: () => `Substituir assinatura`, 152 | manageSubscription: () => `Gerenciar assinatura` 153 | } 154 | } -------------------------------------------------------------------------------- /src/iaphub-data-consumer/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Context from '../iaphub-data-provider/context'; 3 | 4 | export default class IaphubDataConsumer extends React.Component { 5 | 6 | renderContent = (context) => { 7 | var {children} = this.props; 8 | 9 | return (typeof children == 'function') ? children(context) : null; 10 | } 11 | 12 | render() { 13 | return ( 14 | 15 | {this.renderContent} 16 | 17 | ); 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /src/iaphub-data-provider/context.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Context = React.createContext({ 4 | lang: null, 5 | isLoading: null, 6 | err: null, 7 | activeProducts: null, 8 | productsForSale: null, 9 | i18n: null, 10 | alert: null, 11 | showBuySuccessAlert: null, 12 | showBuyErrorAlert: null, 13 | onBuyStart: null, 14 | onBuyEnd: null, 15 | onRestoreStart: null, 16 | onRefreshProducts: null, 17 | onShowManageSubscriptions: null 18 | }); 19 | 20 | export default Context; -------------------------------------------------------------------------------- /src/iaphub-data-provider/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import Context from './context'; 4 | import IaphubData from '../iaphub-data'; 5 | 6 | export default class IaphubDataProvider extends React.Component { 7 | 8 | static propTypes = { 9 | appId: PropTypes.string.isRequired, 10 | apiKey: PropTypes.string.isRequired, 11 | lang: PropTypes.string.isRequired, 12 | userId: PropTypes.string, 13 | allowAnonymousPurchase: PropTypes.bool, 14 | onError: PropTypes.func 15 | }; 16 | 17 | renderContent = (context) => { 18 | var {children} = this.props; 19 | 20 | return ( 21 | 22 | {children} 23 | 24 | ) 25 | } 26 | 27 | render() { 28 | var {children, ...props} = this.props; 29 | 30 | return ( 31 | 32 | {this.renderContent} 33 | 34 | ) 35 | } 36 | 37 | } -------------------------------------------------------------------------------- /src/iaphub-data/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import Iaphub from 'react-native-iaphub'; 4 | import isEqual from 'lodash.isequal'; 5 | import buyWithAlert from '../util/buy-with-alert'; 6 | 7 | export default class IaphubData extends React.Component { 8 | 9 | static propTypes = { 10 | appId: PropTypes.string.isRequired, 11 | apiKey: PropTypes.string.isRequired, 12 | lang: PropTypes.string.isRequired, 13 | userId: PropTypes.string, 14 | userTags: PropTypes.object, 15 | deviceParams: PropTypes.object, 16 | allowAnonymousPurchase: PropTypes.bool, 17 | i18n: PropTypes.object, 18 | alert: PropTypes.func, 19 | showBuySuccessAlert: PropTypes.bool, 20 | showBuyErrorAlert: PropTypes.bool, 21 | onError: PropTypes.func, 22 | onPurchase: PropTypes.func 23 | }; 24 | 25 | static defaultProps = { 26 | showBuySuccessAlert: true, 27 | showBuyErrorAlert: true 28 | }; 29 | 30 | constructor(props) { 31 | super(props); 32 | this.state = { 33 | isLoading: true, 34 | activeProducts: [], 35 | productsForSale: [] 36 | }; 37 | } 38 | 39 | componentDidMount() { 40 | this.start(this.props); 41 | } 42 | 43 | componentDidUpdate(prevProps) { 44 | // Update user id 45 | if (this.props.userId != prevProps.userId) { 46 | // Login 47 | if (this.props.userId) { 48 | this.login(this.props.userId); 49 | } 50 | // Logout 51 | else if (prevProps.userId) { 52 | this.logout(); 53 | } 54 | } 55 | // Update user tags 56 | if (!isEqual(this.props.userTags, prevProps.userTags)) { 57 | this.setUserTags(this.props.userTags); 58 | } 59 | // Update device params 60 | if (!isEqual(this.props.deviceParams, prevProps.deviceParams)) { 61 | this.setDeviceParams(this.props.deviceParams); 62 | } 63 | } 64 | 65 | componentWillUnmount() { 66 | Iaphub.removeEventListener(this.onErrorListener); 67 | Iaphub.removeEventListener(this.buyRequestListener); 68 | Iaphub.removeEventListener(this.userUpdateListener); 69 | } 70 | 71 | async start(props) { 72 | try { 73 | await Iaphub.start({ 74 | appId: props.appId, 75 | apiKey: props.apiKey, 76 | userId: props.userId, 77 | allowAnonymousPurchase: props.allowAnonymousPurchase 78 | }); 79 | // Listen for errors 80 | this.onErrorListener = Iaphub.addEventListener('onError', (err) => { 81 | if (typeof this.props.onError == 'function') { 82 | this.props.onError(err); 83 | } 84 | }); 85 | // Listen for buy request 86 | this.buyRequestListener = Iaphub.addEventListener('onBuyRequest', async (opts) => { 87 | try { 88 | var transaction = await buyWithAlert(() => Iaphub.buy(opts.sku), this.props.lang, this.props.i18n, this.props.showBuySuccessAlert, this.props.showBuyErrorAlert, this.props.alert); 89 | this.onPurchase(transaction); 90 | } 91 | catch(err) { 92 | // No need to do anything here 93 | } 94 | }); 95 | // Listen for user updates 96 | this.userUpdateListener = Iaphub.addEventListener('onUserUpdate', () => { 97 | this.refreshProducts(); 98 | }); 99 | // Set tags if defined 100 | if (props.userTags) { 101 | await Iaphub.setUserTags(props.userTags); 102 | } 103 | // Set device params if defined 104 | if (props.deviceParams) { 105 | await Iaphub.setDeviceParams(props.deviceParams); 106 | } 107 | // Refresh products 108 | await this.refreshProducts(); 109 | } 110 | catch(err) { 111 | this.setState({isLoading: false, err: err}); 112 | } 113 | } 114 | 115 | login = async (userId) => { 116 | try { 117 | await Iaphub.login(userId); 118 | await this.refreshProducts(); 119 | } 120 | catch(err) { 121 | // No need to do anything here 122 | } 123 | } 124 | 125 | logout = async () => { 126 | try { 127 | await Iaphub.logout(); 128 | await this.refreshProducts(); 129 | } 130 | catch(err) { 131 | // No need to do anything here 132 | } 133 | } 134 | 135 | setUserTags = async (tags) => { 136 | try { 137 | await Iaphub.setUserTags(tags); 138 | await this.refreshProducts(); 139 | } 140 | catch(err) { 141 | // No need to do anything here 142 | } 143 | } 144 | 145 | setDeviceParams = async (params) => { 146 | try { 147 | await Iaphub.setDeviceParams(params); 148 | await this.refreshProducts(); 149 | } 150 | catch(err) { 151 | // No need to do anything here 152 | } 153 | } 154 | 155 | manageSubscriptions = async () => { 156 | try { 157 | await Iaphub.showManageSubscriptions(); 158 | } 159 | catch(err) { 160 | // No need to do anything here 161 | } 162 | } 163 | 164 | onPurchase = (transaction) => { 165 | if (typeof this.props.onPurchase == 'function') { 166 | this.props.onPurchase(transaction); 167 | } 168 | } 169 | 170 | onBuyStart = async (product) => { 171 | var transaction = await Iaphub.buy(product.sku); 172 | 173 | return transaction; 174 | } 175 | 176 | onBuyEnd = (err, transaction) => { 177 | if (transaction != null) { 178 | this.onPurchase(transaction); 179 | } 180 | } 181 | 182 | onRestoreStart = async () => { 183 | await Iaphub.restore(); 184 | } 185 | 186 | refreshProducts = async () => { 187 | try { 188 | var products = await Iaphub.getProducts({includeSubscriptionStates: ['retry_period', 'paused']}); 189 | this.setState({activeProducts: products.activeProducts, productsForSale: products.productsForSale, isLoading: false, err: null}); 190 | } 191 | catch(err) { 192 | this.setState({isLoading: false, err: err}); 193 | } 194 | } 195 | 196 | render() { 197 | var {children, lang} = this.props; 198 | var {isLoading, err, activeProducts, productsForSale} = this.state; 199 | 200 | return (typeof children == 'function') ? children({ 201 | lang, 202 | isLoading, 203 | err, 204 | activeProducts, 205 | productsForSale, 206 | onBuyStart: this.onBuyStart, 207 | onBuyEnd: this.onBuyEnd, 208 | onRestoreStart: this.onRestoreStart, 209 | onRefreshProducts: this.refreshProducts, 210 | onShowManageSubscriptions: this.manageSubscriptions 211 | }) : null; 212 | } 213 | 214 | } -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | export {default as Paywall} from './paywall'; 2 | export {default as PaywallSubscriptionGroup} from './paywall-subscription-group'; 3 | export {default as buyWithAlert} from './util/buy-with-alert'; 4 | export {default as getBuyAlertMessage} from './util/get-buy-alert-message'; 5 | export {default as IaphubDataProvider} from './iaphub-data-provider'; 6 | export {default as IaphubDataConsumer} from './iaphub-data-consumer'; -------------------------------------------------------------------------------- /src/paywall-subscription-group/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {StyleSheet, Text} from 'react-native'; 3 | import PropTypes from 'prop-types'; 4 | import Paywall from '../paywall'; 5 | import DefaultProduct from './product'; 6 | import DefaultProductTitle from './product-title'; 7 | import DefaultProductPrice from './product-price'; 8 | import DefaultProductPricePerMonth from './product-price-per-month'; 9 | import DefaultProductContentSingleMonthlySubscription from './product-content-single-monthly-subscription'; 10 | import DefaultProductContent from './product-content'; 11 | import buildTranslate from '../i18n'; 12 | import withStyles from '../util/with-styles'; 13 | 14 | class PaywallSubscriptionGroup extends React.Component { 15 | 16 | static propTypes = { 17 | ProductPricePerMonth: PropTypes.element 18 | }; 19 | 20 | render() { 21 | var {styles, Product, ProductContentSingleMonthlySubscription, ProductContent, ProductTitle, ProductPrice, ProductPricePerMonth, ...props} = this.props; 22 | var translate = buildTranslate('PaywallSubscriptionGroup', this.props.lang, this.props.i18n); 23 | var sameGroup = true; 24 | 25 | // Check all subscriptions belong to the same group 26 | if (this.props.productsForSale) { 27 | this.props.productsForSale.reduce((previousProduct, product) => { 28 | if (previousProduct && previousProduct.group != product.group) { 29 | sameGroup = false; 30 | } 31 | return product; 32 | }, null); 33 | if (!sameGroup) { 34 | return {translate('errorDifferentSubscriptionGroup')} 35 | } 36 | } 37 | // If everything is fine, display the paywall 38 | return ( 39 | 49 | ); 50 | } 51 | 52 | } 53 | 54 | const styles = StyleSheet.create({ 55 | root: {}, 56 | error: { 57 | color: 'red', 58 | textAlign: 'center' 59 | } 60 | }); 61 | 62 | export default withStyles(styles, 'PaywallSubscriptionGroup')(PaywallSubscriptionGroup); -------------------------------------------------------------------------------- /src/paywall-subscription-group/product-content-single-monthly-subscription.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {StyleSheet, View, Text} from 'react-native'; 3 | import withStyles from '../util/with-styles'; 4 | 5 | class ProductContentSingleMonthlySubscription extends Component { 6 | 7 | render() { 8 | var {styles, ProductPricePerMonth} = this.props; 9 | 10 | return ( 11 | 12 | {ProductPricePerMonth && } 17 | 18 | ); 19 | } 20 | 21 | } 22 | 23 | const styles = StyleSheet.create({ 24 | root: { 25 | flex: 1, 26 | minHeight: 70, 27 | justifyContent: "center", 28 | alignItems: "center" 29 | } 30 | }); 31 | 32 | export default withStyles(styles, 'ProductContentSingleMonthlySubscription')(ProductContentSingleMonthlySubscription); -------------------------------------------------------------------------------- /src/paywall-subscription-group/product-content.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {StyleSheet, View} from 'react-native'; 3 | import withStyles from '../util/with-styles'; 4 | 5 | class ProductContent extends Component { 6 | 7 | render() { 8 | var {styles, ProductTitle, ProductPrice, ProductPricePerMonth} = this.props; 9 | 10 | return ( 11 | 12 | {ProductTitle && } 13 | {ProductPrice && } 14 | {ProductPricePerMonth && } 15 | 16 | ); 17 | } 18 | 19 | } 20 | 21 | const styles = StyleSheet.create({ 22 | root: { 23 | flex: 1 24 | } 25 | }); 26 | 27 | export default withStyles(styles, 'ProductContent')(ProductContent); -------------------------------------------------------------------------------- /src/paywall-subscription-group/product-price-per-month.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {StyleSheet, View, Text} from 'react-native'; 3 | import withStyles from '../util/with-styles'; 4 | import style from '../util/style'; 5 | import getSubscriptionPriceDuration from '../util/get-subscription-price-duration'; 6 | 7 | class ProductPricePerMonth extends Component { 8 | 9 | render() { 10 | var {product, isSelected, styles, lang, i18n} = this.props; 11 | 12 | if (!product.subscriptionDuration) return null; 13 | 14 | var durations = {"P7D": 0.25, "P1W": 0.25, "P1M": 1, "P3M": 3, "P6M": 6, "P1Y": 12}; 15 | var price = Math.round((product.price / durations[product.subscriptionDuration]) * 100) / 100; 16 | var currency = product.localizedPrice.match(/[^0-9.]+/); 17 | var localizedPrice = isNaN(product.localizedPrice) ? currency + price : price + currency; 18 | var formattedPrice = getSubscriptionPriceDuration(localizedPrice, "P1M", true, lang, i18n); 19 | var split = formattedPrice.split("/"); 20 | 21 | if (split.length != 2) return null; 22 | return ( 23 | 24 | {split[0]} 25 | / 26 | {split[1]} 27 | 28 | ); 29 | } 30 | 31 | } 32 | 33 | const styles = StyleSheet.create({ 34 | root: { 35 | flexDirection: 'row', 36 | alignItems: 'center', 37 | justifyContent: 'center', 38 | marginTop: 5 39 | }, 40 | text: { 41 | fontSize: 14, 42 | color: '#000000' 43 | }, 44 | price: { 45 | 46 | }, 47 | delimiter: { 48 | 49 | }, 50 | duration: { 51 | 52 | }, 53 | // Style when the product is selected 54 | selectedRoot: { 55 | 56 | }, 57 | selectedText: { 58 | color: 'white' 59 | } 60 | }); 61 | 62 | export default withStyles(styles, 'ProductPricePerMonth')(ProductPricePerMonth); -------------------------------------------------------------------------------- /src/paywall-subscription-group/product-price.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {StyleSheet, Text} from 'react-native'; 3 | import withStyles from '../util/with-styles'; 4 | import style from '../util/style'; 5 | 6 | class ProductPrice extends Component { 7 | 8 | render() { 9 | var {isSelected, product, styles} = this.props; 10 | 11 | return ( 12 | 13 | {product.localizedPrice} 14 | 15 | ); 16 | } 17 | 18 | } 19 | 20 | const styles = StyleSheet.create({ 21 | root: { 22 | fontSize: 16, 23 | fontWeight: 'bold', 24 | color: '#000000', 25 | textAlign: 'center', 26 | marginTop: 10 27 | }, 28 | // Style when the product is selected 29 | selectedRoot: { 30 | color: 'white' 31 | } 32 | }); 33 | 34 | export default withStyles(styles, 'ProductPrice')(ProductPrice); -------------------------------------------------------------------------------- /src/paywall-subscription-group/product-title.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {StyleSheet, View, Text} from 'react-native'; 3 | import withStyles from '../util/with-styles'; 4 | import style from '../util/style'; 5 | import getSubscriptionDuration from '../util/get-subscription-duration'; 6 | 7 | class ProductTitle extends Component { 8 | 9 | render() { 10 | var {product, isSelected, styles, lang, i18n} = this.props; 11 | if (!product.subscriptionDuration) return null; 12 | var duration = getSubscriptionDuration(product.subscriptionDuration, 1, false, lang, i18n); 13 | var durationCount = duration ? duration.split(" ")[0] : ""; 14 | var durationUnit = duration ? duration.split(" ")[1] : ""; 15 | 16 | return ( 17 | 18 | {durationCount} 19 | {durationUnit} 20 | 21 | ); 22 | } 23 | 24 | } 25 | 26 | const styles = StyleSheet.create({ 27 | count: { 28 | fontWeight: 'bold', 29 | fontSize: 28, 30 | color: 'black', 31 | textAlign: 'center', 32 | //marginBottom: 2 33 | }, 34 | unit: { 35 | fontSize: 18, 36 | color: 'black', 37 | textAlign: 'center', 38 | marginBottom: 5 39 | }, 40 | // Style when the product is selected 41 | selectedCount: { 42 | color: 'white' 43 | }, 44 | selectedUnit: { 45 | color: 'white' 46 | } 47 | }); 48 | 49 | export default withStyles(styles, 'ProductTitle')(ProductTitle); -------------------------------------------------------------------------------- /src/paywall-subscription-group/product.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {StyleSheet, Platform} from 'react-native'; 3 | import withStyles from '../util/with-styles'; 4 | 5 | class Product extends Component { 6 | 7 | render() { 8 | var {product, index, onProductPress, styles, TouchableProduct, ProductContentSingleMonthlySubscription, ProductContent, productsForSale} = this.props; 9 | var isSingle = productsForSale.length == 1 && product.subscriptionDuration == "P1M"; 10 | 11 | return ( 12 | onProductPress(product, index)}> 13 | {isSingle ? : } 14 | 15 | ); 16 | } 17 | 18 | } 19 | 20 | const styles = StyleSheet.create({ 21 | root: { 22 | flex: 1, 23 | padding: 10, 24 | marginHorizontal: 5, 25 | marginTop: 10 26 | } 27 | }); 28 | 29 | export default withStyles(styles, 'Product')(Product); -------------------------------------------------------------------------------- /src/paywall/active-product.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {StyleSheet, Platform, Text, View} from 'react-native'; 3 | import withStyles from '../util/with-styles'; 4 | import style from '../util/style'; 5 | import buildTranslate from '../i18n'; 6 | 7 | class ActiveProduct extends Component { 8 | 9 | render() { 10 | var {styles, theme, lang, i18n, product, index, isSelected, onPress, TouchableProduct, ProductTitle} = this.props; 11 | var translate = buildTranslate('ActiveProduct', lang, i18n); 12 | var text = (product.type.indexOf("subscription") != -1) ? translate('activeSubscription') : translate('activeProduct'); 13 | var showAutoRenewal = false; 14 | 15 | if (product.type == "renewable_subscription") { 16 | showAutoRenewal = true; 17 | } 18 | if (product.subscriptionState == 'paused') { 19 | text = translate('subscriptionPaused'); 20 | showAutoRenewal = false; 21 | } 22 | else if (['grace_period', 'retry_period'].indexOf(product.subscriptionState) != -1) { 23 | text = translate('subscriptionCannotBeRenewed'); 24 | showAutoRenewal = false; 25 | } 26 | return ( 27 | onPress(product, index)}> 28 | {product.platform == Platform.OS && 29 | 30 | 31 | 32 | } 33 | 34 | 35 | {text} 36 | 37 | {showAutoRenewal && 38 | 39 | 40 | {product.isSubscriptionRenewable ? translate('autoRenewalActive') : translate('autoRenewalDisabled')} 41 | 42 | 43 | } 44 | 45 | 46 | ); 47 | } 48 | 49 | } 50 | 51 | const styles = StyleSheet.create({ 52 | root: { 53 | minHeight: 70, 54 | margin: 10, 55 | marginBottom: 0, 56 | flexDirection: 'row' 57 | }, 58 | title: { 59 | width: 120, 60 | padding: 10, 61 | alignItems: 'center', 62 | justifyContent: 'center', 63 | backgroundColor: 'rgba(0, 0, 0, 0.15)' 64 | }, 65 | description: { 66 | flex: 1, 67 | padding: 10, 68 | paddinLeft: 5, 69 | paddinRight: 5, 70 | alignItems: 'center', 71 | justifyContent: 'center' 72 | }, 73 | descriptionText: { 74 | fontSize: 14, 75 | color: 'black', 76 | fontWeight: 'bold', 77 | flexWrap: 'wrap' 78 | }, 79 | bubble: { 80 | backgroundColor: 'rgba(0, 0, 0, 0.15)', 81 | borderRadius: 20, 82 | padding: 5, 83 | paddingLeft: 10, 84 | paddingRight: 10, 85 | marginTop: 5 86 | }, 87 | bubbleText: { 88 | fontSize: 13, 89 | color: 'black', 90 | }, 91 | // Style when the subscription is selected 92 | selectedDescriptionText: { 93 | color: 'white' 94 | }, 95 | selectedBubbleText: { 96 | color: 'white' 97 | } 98 | }); 99 | 100 | export default withStyles(styles, 'ActiveProduct')(ActiveProduct); -------------------------------------------------------------------------------- /src/paywall/active-products-wrapper.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {StyleSheet, View} from 'react-native'; 3 | import withStyles from '../util/with-styles'; 4 | 5 | class ActiveProductsWrapper extends React.Component { 6 | 7 | render() { 8 | var {ActiveProduct, activeProducts, selectedActiveProductIndex, onProductPress, style, styles, ...props} = this.props; 9 | 10 | if (!ActiveProduct || !activeProducts) return null; 11 | return ( 12 | 13 | {activeProducts.map((product, index) => ( 14 | 22 | ))} 23 | 24 | ) 25 | } 26 | 27 | } 28 | 29 | const styles = StyleSheet.create({ 30 | root: { 31 | 32 | } 33 | }); 34 | 35 | export default withStyles(styles, 'ActiveProductsWrapper')(ActiveProductsWrapper); -------------------------------------------------------------------------------- /src/paywall/buy.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {StyleSheet, View, Text, ActivityIndicator, Platform} from 'react-native'; 3 | import Ripple from 'react-native-material-ripple'; 4 | import withStyles from '../util/with-styles'; 5 | import buildTranslate from '../i18n'; 6 | 7 | class Buy extends React.Component { 8 | 9 | render() { 10 | var {styles, onBuy, onShowManageSubscriptions, isBuyLoading, lang, i18n, selectedProduct, selectedActiveProductIndex, activeProducts} = this.props; 11 | if (!selectedProduct) return null; 12 | var translate = buildTranslate('Buy', lang, i18n); 13 | var buttonText = translate('continue'); 14 | var onPress = onBuy; 15 | var hideButton = false; 16 | 17 | // Check if it is an active product selected 18 | if (selectedActiveProductIndex != null) { 19 | // If the selected active product is a non-consumable or non-renewing subscription, do not show any button 20 | if (["non_consumable", "subscription"].indexOf(selectedProduct.type) != -1) { 21 | hideButton = true; 22 | } 23 | // Otherwise show the manage subscription button 24 | buttonText = translate('manageSubscription'); 25 | onPress = onShowManageSubscriptions; 26 | } 27 | // Check if it is a product for sale replacing an existing subscription 28 | else if (activeProducts && activeProducts.find((product) => product.group == selectedProduct.group)) { 29 | buttonText = translate('replaceSubscription'); 30 | } 31 | 32 | if (hideButton) { 33 | return null; 34 | } 35 | return ( 36 | 37 | onPress && onPress(selectedProduct)}> 38 | 39 | {!isBuyLoading && {buttonText}} 40 | {isBuyLoading && } 41 | 42 | 43 | 44 | ) 45 | } 46 | 47 | } 48 | 49 | const styles = StyleSheet.create({ 50 | root: { 51 | flex: 1, 52 | minHeight: 60, 53 | justifyContent: 'flex-end' 54 | }, 55 | button: { 56 | margin: 10, 57 | padding: 10, 58 | borderRadius: 40, 59 | backgroundColor: '#0294FF', 60 | alignItems: 'center', 61 | justifyContent: 'center', 62 | ...Platform.select({ 63 | ios: { 64 | shadowOpacity: 1, 65 | shadowRadius: 2, 66 | shadowOffset: {height: 1, width: 1}, 67 | shadowColor: '#000000' 68 | }, 69 | android: { 70 | elevation: 2 71 | } 72 | }) 73 | }, 74 | text: { 75 | fontSize: 16, 76 | color: 'white', 77 | textTransform: 'uppercase' 78 | }, 79 | activityIndicator: { 80 | color: '#ffffff' 81 | } 82 | }); 83 | 84 | export default withStyles(styles, 'Buy')(Buy); -------------------------------------------------------------------------------- /src/paywall/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import Paywall from './paywall'; 4 | import DefaultTouchableProduct from './touchable-product'; 5 | import DefaultActiveProductsWrapper from './active-products-wrapper'; 6 | import DefaultActiveProduct from './active-product'; 7 | import DefaultProductsWrapper from './products-wrapper'; 8 | import DefaultProduct from './product'; 9 | import DefaultProductTitle from './product-title'; 10 | import DefaultProductPrice from './product-price'; 11 | import DefaultProductsError from './products-error'; 12 | import DefaultIntroPhasesWrapper from './intro-phases-wrapper'; 13 | import DefaultIntroPhase from './intro-phase'; 14 | import DefaultSubscriptionTerms from './subscription-terms'; 15 | import DefaultRestore from './restore'; 16 | import DefaultBuy from './buy'; 17 | import DefaultLoading from './loading'; 18 | 19 | export default class PaywallWrapper extends React.Component { 20 | 21 | static propTypes = { 22 | isLoading: PropTypes.bool, 23 | err: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), 24 | productsForSale: PropTypes.array, 25 | activeProducts: PropTypes.array, 26 | lang: PropTypes.string, 27 | defaultSelectedProductIndex: PropTypes.number, 28 | theme: PropTypes.object, 29 | display: PropTypes.oneOf(['vertical', 'horizontal']), 30 | alert: PropTypes.func, 31 | showBuySuccessAlert: PropTypes.bool, 32 | showBuyErrorAlert: PropTypes.bool, 33 | showRestoreSuccessAlert: PropTypes.bool, 34 | showRestoreEmptyAlert: PropTypes.bool, 35 | showRestoreErrorAlert: PropTypes.bool, 36 | onBuyStart: PropTypes.func, 37 | onBuyEnd: PropTypes.func, 38 | onRestoreStart: PropTypes.func, 39 | onRestoreEnd: PropTypes.func, 40 | onShowManageSubscriptions: PropTypes.func, 41 | onRefreshProducts: PropTypes.func, 42 | ActiveProductsWrapper: PropTypes.func, 43 | ActiveProduct: PropTypes.func, 44 | Product: PropTypes.func, 45 | ProductTitle: PropTypes.func, 46 | ProductPrice: PropTypes.func, 47 | ProductsWrapper: PropTypes.func, 48 | ProductsError: PropTypes.func, 49 | IntroPhasesWrapper: PropTypes.func, 50 | IntroPhase: PropTypes.func, 51 | Restore: PropTypes.func, 52 | Buy: PropTypes.func, 53 | Loading: PropTypes.func 54 | }; 55 | 56 | static defaultProps = { 57 | isLoading: false, 58 | lang: 'en', 59 | defaultSelectedProductIndex: 0, 60 | display: 'horizontal', 61 | showBuySuccessAlert: true, 62 | showBuyErrorAlert: true, 63 | showRestoreSuccessAlert: true, 64 | showRestoreEmptyAlert: true, 65 | showRestoreErrorAlert: true, 66 | onBuyStart: () => {}, 67 | onBuyEnd: () => {}, 68 | onRestoreStart: () => {}, 69 | onRestoreEnd: () => {}, 70 | onShowManageSubscriptions: () => {}, 71 | onRefreshProducts: () => {} 72 | }; 73 | 74 | render() { 75 | var {TouchableProduct, ActiveProductsWrapper, ActiveProduct, ProductsWrapper, Product, ProductTitle, ProductPrice, ProductsError, IntroPhasesWrapper, IntroPhase, SubscriptionTerms, Restore, Buy, Loading, ...props} = this.props; 76 | 77 | return ( 78 | 95 | ); 96 | } 97 | 98 | } -------------------------------------------------------------------------------- /src/paywall/intro-phase.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {StyleSheet, View, Text} from 'react-native'; 3 | import withStyles from '../util/with-styles'; 4 | import buildTranslate from '../i18n'; 5 | import getSubscriptionDuration from '../util/get-subscription-duration'; 6 | import getSubscriptionPriceDuration from '../util/get-subscription-price-duration'; 7 | 8 | class IntroPhase extends React.Component { 9 | 10 | getPhaseText() { 11 | var {introPhase, lang, i18n} = this.props; 12 | var translate = buildTranslate('IntroPhase', lang, i18n); 13 | 14 | if (introPhase.type == 'trial') { 15 | return translate('freeTrial', {duration: getSubscriptionDuration(introPhase.cycleDuration, introPhase.cycleCount, false, lang, i18n)}); 16 | } 17 | else if (introPhase.type == 'intro') { 18 | return translate('introPrice', { 19 | priceDuration: getSubscriptionPriceDuration(introPhase.localizedPrice, introPhase.cycleDuration, true, lang, i18n), 20 | duration: getSubscriptionDuration(introPhase.cycleDuration, introPhase.cycleCount, true, lang, i18n) 21 | }); 22 | } 23 | } 24 | 25 | render() { 26 | var {styles, key, index, introPhase} = this.props; 27 | 28 | if (!introPhase) return null; 29 | return ( 30 | 31 | {this.getPhaseText()} 32 | 33 | ); 34 | } 35 | 36 | } 37 | 38 | const styles = StyleSheet.create({ 39 | root: { 40 | padding: 10, 41 | backgroundColor: '#D9485A', 42 | borderRadius: 3, 43 | marginTop: 5, 44 | marginBottom: 5, 45 | marginLeft: 10, 46 | maxWidth: 170, 47 | justifyContent: 'center' 48 | }, 49 | text: { 50 | color: 'white', 51 | fontSize: 11, 52 | fontWeight: 'bold', 53 | textTransform: 'uppercase', 54 | textAlign: 'center' 55 | } 56 | }); 57 | 58 | export default withStyles(styles, 'IntroPhase')(IntroPhase); -------------------------------------------------------------------------------- /src/paywall/intro-phases-wrapper.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {StyleSheet, View, Text} from 'react-native'; 3 | import withStyles from '../util/with-styles'; 4 | import buildTranslate from '../i18n'; 5 | 6 | class IntroPhasesWrapper extends React.Component { 7 | 8 | render() { 9 | var {styles, selectedProduct, lang, i18n, IntroPhase} = this.props; 10 | var translate = buildTranslate('IntroPhasesWrapper', lang, i18n); 11 | 12 | if (!selectedProduct || !selectedProduct.subscriptionIntroPhases || !selectedProduct.subscriptionIntroPhases.length) return null; 13 | return ( 14 | 15 | {translate('title')} 16 | 17 | {selectedProduct.subscriptionIntroPhases.map((phase, index) => ( 18 | 19 | ))} 20 | 21 | 22 | ); 23 | } 24 | 25 | } 26 | 27 | const styles = StyleSheet.create({ 28 | root: { 29 | paddingTop: 10 30 | }, 31 | title: { 32 | color: 'white', 33 | fontSize: 14, 34 | marginLeft: 10, 35 | marginBottom: 5, 36 | marginTop: 5 37 | }, 38 | phases: { 39 | flexDirection: 'row', 40 | flexWrap: 'wrap', 41 | justifyContent: 'flex-start', 42 | } 43 | }); 44 | 45 | export default withStyles(styles, 'IntroPhasesWrapper')(IntroPhasesWrapper); -------------------------------------------------------------------------------- /src/paywall/loading.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {StyleSheet, View, ActivityIndicator} from 'react-native'; 3 | import withStyles from '../util/with-styles'; 4 | 5 | class Loading extends Component { 6 | 7 | static defaultProps = { 8 | size: 'large', 9 | color: '#ffffff' 10 | }; 11 | 12 | render() { 13 | var {isLoading, styles, size, color} = this.props; 14 | 15 | if (!isLoading) return null; 16 | return ( 17 | 18 | 19 | 20 | ); 21 | } 22 | 23 | } 24 | 25 | const styles = StyleSheet.create({ 26 | root: { 27 | flex: 1, 28 | alignItems: 'center', 29 | justifyContent: 'center' 30 | } 31 | }); 32 | 33 | export default withStyles(styles, 'Loading')(Loading); -------------------------------------------------------------------------------- /src/paywall/paywall.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {StyleSheet, View, Alert, ScrollView, Platform} from 'react-native'; 3 | import withStyles from '../util/with-styles'; 4 | import buyWithAlert from '../util/buy-with-alert'; 5 | import showAlert from '../util/show-alert'; 6 | import buildTranslate from '../i18n'; 7 | 8 | class Paywall extends React.Component { 9 | 10 | static getDerivedStateFromProps(props, state) { 11 | if (state.hasSelectedProduct == false) { 12 | var selectedProduct = null; 13 | var selectedProductForSaleIndex = 0; 14 | var selectedActiveProductIndex = null; 15 | 16 | if (props.productsForSale && props.productsForSale.length > props.defaultSelectedProductIndex) { 17 | selectedProductForSaleIndex = props.defaultSelectedProductIndex || 0; 18 | selectedProduct = props.productsForSale[selectedProductForSaleIndex]; 19 | } 20 | if (!selectedProduct && props.productsForSale && props.productsForSale.length >= 1) { 21 | selectedProductForSaleIndex = 0; 22 | selectedProduct = props.productsForSale[0]; 23 | } 24 | if (props.activeProducts && props.activeProducts.length) { 25 | selectedProductForSaleIndex = null; 26 | selectedActiveProductIndex = 0; 27 | selectedProduct = props.activeProducts[0]; 28 | } 29 | 30 | return {selectedProduct : selectedProduct, selectedProductForSaleIndex: selectedProductForSaleIndex, selectedActiveProductIndex: selectedActiveProductIndex}; 31 | } 32 | return null; 33 | } 34 | 35 | constructor(props) { 36 | super(props); 37 | this.state = { 38 | selectedActiveProductIndex: null, 39 | selectedProductForSaleIndex: null, 40 | selectedProduct: null, 41 | hasSelectedProduct: false, 42 | isBuyLoading: false, 43 | isRestoreLoading: false, 44 | isProductsRefreshing: false 45 | }; 46 | } 47 | 48 | onActiveProductPress = (product, index) => { 49 | if (this.state.isBuyLoading || this.state.isRestoreLoading) return; 50 | this.setState({ 51 | hasSelectedProduct: true, 52 | selectedProduct: product, 53 | selectedProductForSaleIndex: null, 54 | selectedActiveProductIndex: index 55 | }); 56 | } 57 | 58 | onProductForSalePress = (product, index) => { 59 | if (this.state.isBuyLoading || this.state.isRestoreLoading) return; 60 | this.setState({ 61 | hasSelectedProduct: true, 62 | selectedProduct: product, 63 | selectedProductForSaleIndex: index, 64 | selectedActiveProductIndex: null 65 | }); 66 | } 67 | 68 | onRefreshProducts = async () => { 69 | var {isProductsRefreshing} = this.state; 70 | var {onRefreshProducts} = this.props; 71 | 72 | if (isProductsRefreshing) return; 73 | this.setState({isProductsRefreshing: true}); 74 | var startTime = Date.now(); 75 | await onRefreshProducts(); 76 | // Wait at least 2secs for the activity indicator to show up nicely 77 | var waitTime = Date.now() - startTime; 78 | var minimumWaitTime = 2000; 79 | if (waitTime < minimumWaitTime) { 80 | await new Promise((resolve) => setTimeout(resolve, minimumWaitTime - waitTime)); 81 | } 82 | 83 | this.setState({isProductsRefreshing: false}); 84 | } 85 | 86 | onBuy = async () => { 87 | var {isBuyLoading, isRestoreLoading, selectedProduct} = this.state; 88 | var {onBuyStart, onBuyEnd, lang, i18n, showBuySuccessAlert, showBuyErrorAlert, alert} = this.props; 89 | var transaction = null; 90 | 91 | if (isBuyLoading || isRestoreLoading) return; 92 | this.setState({isBuyLoading: true}); 93 | try { 94 | transaction = await buyWithAlert(() => onBuyStart(selectedProduct), lang, i18n, showBuySuccessAlert, showBuyErrorAlert, alert); 95 | onBuyEnd(null, transaction, selectedProduct); 96 | } 97 | catch (err) { 98 | onBuyEnd(err, null, selectedProduct); 99 | } 100 | // Update state 101 | this.setState({isBuyLoading: false}); 102 | } 103 | 104 | onRestore = async () => { 105 | var {isBuyLoading, isRestoreLoading} = this.state; 106 | var {onRestoreStart, onRestoreEnd, lang, i18n, showRestoreSuccessAlert, showRestoreEmptyAlert, showRestoreErrorAlert} = this.props; 107 | var translate = buildTranslate('Paywall', lang, i18n); 108 | 109 | if (isBuyLoading || isRestoreLoading) return; 110 | this.setState({isRestoreLoading: true}); 111 | var error = null; 112 | var alertTitle = translate('restoreSuccessTitle'); 113 | var alertMessage = translate('restoreSuccessMessage'); 114 | var displayAlert = true; 115 | var response = null; 116 | var isEmptyRestore = false; 117 | // Start restore 118 | try { 119 | response = await onRestoreStart(); 120 | if (response && response.newPurchases && !response.newPurchases.length && response.transferredActiveProducts && !response.transferredActiveProducts.length) { 121 | alertMessage = translate('restoreEmptyMessage'); 122 | isEmptyRestore = true; 123 | } 124 | } 125 | // Catch any error 126 | catch (err) { 127 | error = err; 128 | alertTitle = translate('restoreErrorTitle'); 129 | alertMessage = translate('restoreErrorMessage'); 130 | } 131 | // Hide alert depending on config 132 | if (isEmptyRestore) { 133 | if (showRestoreEmptyAlert == false) { 134 | displayAlert = false; 135 | } 136 | } 137 | else { 138 | if (!error && showRestoreSuccessAlert == false) { 139 | displayAlert = false; 140 | } 141 | if (error && showRestoreErrorAlert == false) { 142 | displayAlert = false; 143 | } 144 | } 145 | // Update state 146 | this.setState({isRestoreLoading: false}); 147 | // Show alert 148 | if (displayAlert) { 149 | await this.showAlert({title: alertTitle, description: alertMessage, button: translate('ok')}); 150 | } 151 | // Call restore end 152 | onRestoreEnd(error, response); 153 | } 154 | 155 | onShowManageSubscriptions = async (product) => { 156 | var {onShowManageSubscriptions, lang, i18n} = this.props; 157 | var translate = buildTranslate('Paywall', lang, i18n); 158 | 159 | // Show alert if the product is managed by a different platform 160 | if (product.platform != Platform.OS) { 161 | this.showAlert({ 162 | title: translate('manageSubscriptionsErrorTitleDifferentPlatform', product), 163 | description: translate('manageSubscriptionsErrorMessageDifferentPlatform', product), 164 | button: translate('ok') 165 | }); 166 | } 167 | // Otherwise call the event 168 | else { 169 | onShowManageSubscriptions(product); 170 | } 171 | } 172 | 173 | showAlert = async (opts) => { 174 | var {alert} = this.props; 175 | 176 | if (alert) { 177 | await alert(opts); 178 | } 179 | else { 180 | await showAlert(opts); 181 | } 182 | } 183 | 184 | renderActiveProducts = () => { 185 | var {ActiveProductsWrapper, style, styles, ...props} = this.props; 186 | 187 | if (!ActiveProductsWrapper) return null; 188 | return ( 189 | 190 | ) 191 | } 192 | 193 | renderProducts = () => { 194 | var {ProductsWrapper, style, styles, ...props} = this.props; 195 | 196 | if (!ProductsWrapper) return null; 197 | return ( 198 | 199 | ) 200 | } 201 | 202 | renderProductsError = () => { 203 | var {ProductsError, style, styles, ...props} = this.props; 204 | 205 | if (!ProductsError) return null; 206 | return ( 207 | 208 | ) 209 | } 210 | 211 | renderIntroPhases = () => { 212 | var {IntroPhasesWrapper, style, styles, ...props} = this.props; 213 | 214 | if (!IntroPhasesWrapper) return null; 215 | return ( 216 | 217 | ) 218 | } 219 | 220 | renderSubscriptionTerms = () => { 221 | var {SubscriptionTerms, style, styles, ...props} = this.props; 222 | 223 | if (!SubscriptionTerms) return null; 224 | return ( 225 | 226 | ) 227 | } 228 | 229 | renderRestore = () => { 230 | var {Restore, style, styles, ...props} = this.props; 231 | 232 | if (!Restore) return null; 233 | return ( 234 | 235 | ) 236 | } 237 | 238 | renderBuy = () => { 239 | var {Buy, style, styles, ...props} = this.props; 240 | 241 | if (!Buy) return null; 242 | return ( 243 | 244 | ) 245 | } 246 | 247 | renderLoading = () => { 248 | var {Loading, style, styles, ...props} = this.props; 249 | 250 | if (!Loading) return null; 251 | return ( 252 | 253 | ) 254 | } 255 | 256 | renderContent() { 257 | var {productsForSale, activeProducts, err} = this.props; 258 | 259 | return ( 260 | 261 | {this.renderActiveProducts()} 262 | {(!productsForSale || !productsForSale.length) && (!activeProducts || !activeProducts.length || err) && this.renderProductsError()} 263 | {(productsForSale && productsForSale.length > 0) && this.renderProducts()} 264 | {this.renderIntroPhases()} 265 | {this.renderSubscriptionTerms()} 266 | {this.renderRestore()} 267 | 268 | ) 269 | } 270 | 271 | render() { 272 | var {styles, isLoading} = this.props; 273 | 274 | return ( 275 | 276 | 277 | {isLoading && this.renderLoading()} 278 | {!isLoading && this.renderContent()} 279 | {!isLoading && this.renderBuy()} 280 | 281 | 282 | ) 283 | } 284 | 285 | } 286 | 287 | const styles = StyleSheet.create({ 288 | root: { 289 | flex: 1, 290 | alignItems: 'center' 291 | }, 292 | content: { 293 | flex: 1, 294 | width: '100%', 295 | maxWidth: 480 296 | } 297 | }); 298 | 299 | export default withStyles(styles, 'Paywall')(Paywall); -------------------------------------------------------------------------------- /src/paywall/product-price.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {StyleSheet, Text} from 'react-native'; 3 | import withStyles from '../util/with-styles'; 4 | import style from '../util/style'; 5 | import getSubscriptionPriceDuration from '../util/get-subscription-price-duration'; 6 | 7 | class ProductPrice extends Component { 8 | 9 | renderText() { 10 | var {product, lang, i18n} = this.props; 11 | 12 | if (product.type == "renewable_subscription") { 13 | return getSubscriptionPriceDuration(product.localizedPrice, product.subscriptionDuration, true, lang, i18n); 14 | } 15 | return product.localizedPrice; 16 | } 17 | 18 | render() { 19 | var {isSelected, styles} = this.props; 20 | 21 | return ( 22 | 23 | {this.renderText()} 24 | 25 | ); 26 | } 27 | 28 | } 29 | 30 | const styles = StyleSheet.create({ 31 | root: { 32 | fontSize: 14, 33 | color: '#000000', 34 | textAlign: 'center' 35 | }, 36 | // Style when the product is selected 37 | selectedRoot: { 38 | color: 'white' 39 | } 40 | }); 41 | 42 | export default withStyles(styles, 'ProductPrice')(ProductPrice); -------------------------------------------------------------------------------- /src/paywall/product-title.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {StyleSheet, Text} from 'react-native'; 3 | import withStyles from '../util/with-styles'; 4 | import style from '../util/style'; 5 | 6 | class ProductTitle extends Component { 7 | 8 | render() { 9 | var {product, isSelected, styles} = this.props; 10 | 11 | return ( 12 | 13 | {product.localizedTitle} 14 | 15 | ); 16 | } 17 | 18 | } 19 | 20 | const styles = StyleSheet.create({ 21 | root: { 22 | fontWeight: 'bold', 23 | fontSize: 18, 24 | color: 'black', 25 | textAlign: 'center' 26 | }, 27 | // Style when the product is selected 28 | selectedRoot: { 29 | color: 'white' 30 | } 31 | }); 32 | 33 | export default withStyles(styles, 'ProductTitle')(ProductTitle); -------------------------------------------------------------------------------- /src/paywall/product.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {StyleSheet} from 'react-native'; 3 | import withStyles from '../util/with-styles'; 4 | 5 | class Product extends Component { 6 | 7 | render() { 8 | var {product, index, onProductPress, isSelected, styles, TouchableProduct, ProductTitle, ProductPrice} = this.props; 9 | 10 | return ( 11 | onProductPress(product, index)}> 12 | {ProductTitle && } 13 | {ProductPrice && } 14 | 15 | ); 16 | } 17 | 18 | } 19 | 20 | const styles = StyleSheet.create({ 21 | root: { 22 | flex: 1, 23 | padding: 10, 24 | marginHorizontal: 5, 25 | marginTop: 10 26 | } 27 | }); 28 | 29 | export default withStyles(styles, 'Product')(Product); -------------------------------------------------------------------------------- /src/paywall/products-error.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {StyleSheet, View, Text, ActivityIndicator, Platform} from 'react-native'; 3 | import Ripple from 'react-native-material-ripple'; 4 | import withStyles from '../util/with-styles'; 5 | import buildTranslate from '../i18n'; 6 | 7 | class ProductsError extends React.Component { 8 | 9 | render() { 10 | var {styles, lang, i18n, isProductsRefreshing, err, onRefreshProducts} = this.props; 11 | var translate = buildTranslate('ProductsError', lang, i18n); 12 | var description = translate('noProducts'); 13 | 14 | if (err && err.code) { 15 | if (err.code == "billing_unavailable") { 16 | description = translate('billingUnavailable'); 17 | if (typeof err == "object" && err.subcode == "play_store_outdated") { 18 | description = translate('playStoreOutdated'); 19 | } 20 | } 21 | else if (err.code == "network_error") { 22 | description = translate('networkError'); 23 | } 24 | else if (err.code == "unexpected") { 25 | description = translate('unexpectedError', {code: err.subcode}); 26 | if (typeof err == "object" && err.subcode == "start_missing") { 27 | description = translate('startMissingError'); 28 | } 29 | } 30 | else { 31 | description = translate('unexpectedError', {code: [err.code, err.subcode].filter((item) => item).join("/")}); 32 | } 33 | } 34 | else if (err) { 35 | description = translate('unexpectedError', {code: "exception"}); 36 | } 37 | 38 | return ( 39 | 40 | {description} 41 | 42 | {!isProductsRefreshing && onRefreshProducts()}> 43 | {translate('tryAgain')} 44 | } 45 | {isProductsRefreshing && } 46 | 47 | 48 | ) 49 | } 50 | 51 | } 52 | 53 | const styles = StyleSheet.create({ 54 | root: { 55 | margin: 10, 56 | padding: 15, 57 | paddingBottom: 10, 58 | justifyContent: 'center', 59 | alignItems: 'center', 60 | backgroundColor: 'white', 61 | borderRadius: 3, 62 | ...Platform.select({ 63 | ios: { 64 | shadowOpacity: 1, 65 | shadowRadius: 2, 66 | shadowOffset: {height: 1, width: 1}, 67 | shadowColor: '#000000' 68 | }, 69 | android: { 70 | elevation: 2 71 | } 72 | }) 73 | }, 74 | text: { 75 | color: 'black', 76 | fontWeight: 'bold', 77 | textAlign: 'center' 78 | }, 79 | button: { 80 | justifyContent: 'center', 81 | marginTop: 5, 82 | paddingLeft: 10, 83 | paddingRight: 10, 84 | height: 40 85 | }, 86 | buttonText: { 87 | fontWeight: 'bold', 88 | textTransform: 'uppercase', 89 | color: '#0294FF' 90 | }, 91 | buttonActivityIndicator: { 92 | color: '#000000' 93 | } 94 | }); 95 | 96 | export default withStyles(styles, 'ProductsError')(ProductsError); -------------------------------------------------------------------------------- /src/paywall/products-wrapper.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {StyleSheet, View} from 'react-native'; 3 | import withStyles from '../util/with-styles'; 4 | 5 | class ProductsWrapper extends React.Component { 6 | 7 | renderProducts() { 8 | var {styles, productsForSale, Product, selectedProductForSaleIndex, ...props} = this.props; 9 | 10 | return productsForSale.map((product, index) => ( 11 | 18 | )) 19 | } 20 | 21 | renderVertical() { 22 | var {styles, productsForSale, Product, selectedIndex, ...props} = this.props; 23 | 24 | return ( 25 | 26 | {this.renderProducts()} 27 | 28 | ); 29 | } 30 | 31 | renderHorizontal() { 32 | var {styles, productsForSale, Product, selectedIndex, ...props} = this.props; 33 | 34 | return ( 35 | 36 | {this.renderProducts()} 37 | 38 | ); 39 | } 40 | 41 | render() { 42 | var {display} = this.props; 43 | 44 | return (display == 'horizontal') ? this.renderHorizontal() : this.renderVertical(); 45 | } 46 | 47 | } 48 | 49 | const styles = StyleSheet.create({ 50 | root: { 51 | paddingLeft: 5, 52 | paddingRight: 5 53 | }, 54 | rootHorizontal: { 55 | flexDirection: 'row', 56 | flexWrap: 'wrap', 57 | justifyContent: 'center' 58 | } 59 | }); 60 | 61 | export default withStyles(styles, 'ProductsWrapper')(ProductsWrapper); -------------------------------------------------------------------------------- /src/paywall/restore.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {StyleSheet, View, Text, TouchableOpacity, ActivityIndicator} from 'react-native'; 3 | import withStyles from '../util/with-styles'; 4 | import buildTranslate from '../i18n'; 5 | 6 | class Restore extends React.Component { 7 | 8 | render() { 9 | var {styles, lang, i18n, isRestoreLoading, onRestore} = this.props; 10 | var translate = buildTranslate('Restore', lang, i18n); 11 | 12 | return ( 13 | 14 | 15 | {translate('restorePurchases')} 16 | {isRestoreLoading && } 17 | 18 | 19 | ) 20 | } 21 | 22 | } 23 | 24 | const styles = StyleSheet.create({ 25 | root: { 26 | padding: 20 27 | }, 28 | content: { 29 | flexDirection: 'row', 30 | justifyContent: 'center', 31 | alignItems: 'center' 32 | }, 33 | text: { 34 | fontSize: 13, 35 | fontWeight: 'bold', 36 | color: 'white', 37 | textAlign: 'center' 38 | }, 39 | activityIndicator: { 40 | marginLeft: 5, 41 | color: '#ffffff' 42 | } 43 | }); 44 | 45 | export default withStyles(styles, 'Restore')(Restore); -------------------------------------------------------------------------------- /src/paywall/subscription-terms.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {StyleSheet, View, Text, Platform} from 'react-native'; 3 | import withStyles from '../util/with-styles'; 4 | import buildTranslate from '../i18n'; 5 | 6 | class SubscriptionTerms extends React.Component { 7 | 8 | render() { 9 | var {styles, lang, i18n, selectedProduct, activeProducts, selectedActiveProductIndex} = this.props; 10 | var translate = buildTranslate('SubscriptionTerms', lang, i18n); 11 | 12 | if (!selectedProduct) return null; 13 | // Do not display the terms if the selected product isn't a renewable subscription 14 | if (selectedProduct.type != "renewable_subscription") return null; 15 | // Do not display the terms if it is for an active product 16 | if (selectedActiveProductIndex != null) return null; 17 | 18 | var title = translate('subscriptionTermsTitle', {platform: Platform.OS}); 19 | var description = translate('subscriptionTermsDescription', {platform: Platform.OS}); 20 | var hasFreeTrial = selectedProduct.subscriptionIntroPhases && selectedProduct.subscriptionIntroPhases.length && selectedProduct.subscriptionIntroPhases[0].type == "trial"; 21 | 22 | if (hasFreeTrial) { 23 | title = translate('subscriptionTermsFreeTrialTitle', {platform: Platform.OS}); 24 | description = translate('subscriptionTermsFreeTrialDescription', {platform: Platform.OS}); 25 | } 26 | return ( 27 | 28 | {(title && title != "") && {title}} 29 | {(description && description != "") && {description}} 30 | 31 | ) 32 | } 33 | 34 | } 35 | 36 | const styles = StyleSheet.create({ 37 | root: { 38 | padding: 10, 39 | paddingBottom: 0, 40 | marginTop: 5 41 | }, 42 | title: { 43 | fontSize: 13, 44 | fontWeight: 'bold', 45 | color: 'white', 46 | textAlign: 'center' 47 | }, 48 | description: { 49 | marginTop: 5, 50 | fontSize: 13, 51 | color: 'white', 52 | textAlign: 'center' 53 | } 54 | }); 55 | 56 | export default withStyles(styles, 'SubscriptionTerms')(SubscriptionTerms); -------------------------------------------------------------------------------- /src/paywall/touchable-product.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {StyleSheet, Platform} from 'react-native'; 3 | import Ripple from 'react-native-material-ripple'; 4 | import withStyles from '../util/with-styles'; 5 | import style from '../util/style'; 6 | 7 | class TouchableProduct extends Component { 8 | 9 | render() { 10 | var {onPress, isSelected, styles, children} = this.props; 11 | 12 | return ( 13 | 14 | {children} 15 | 16 | ); 17 | } 18 | 19 | } 20 | 21 | const styles = StyleSheet.create({ 22 | root: { 23 | backgroundColor: 'white', 24 | borderRadius: 3, 25 | ...Platform.select({ 26 | ios: { 27 | shadowOpacity: 1, 28 | shadowRadius: 2, 29 | shadowOffset: {height: 1, width: 1}, 30 | shadowColor: '#000000' 31 | }, 32 | android: { 33 | elevation: 2 34 | } 35 | }) 36 | }, 37 | // Style when the product is selected 38 | selectedRoot: { 39 | backgroundColor: '#0294FF' 40 | } 41 | }); 42 | 43 | export default withStyles(styles, 'TouchableProduct')(TouchableProduct); -------------------------------------------------------------------------------- /src/util/buy-with-alert.js: -------------------------------------------------------------------------------- 1 | import buildTranslate from '../i18n'; 2 | import getBuyAlertMessage from './get-buy-alert-message'; 3 | import showAlert from './show-alert'; 4 | 5 | export default async function(fn, lang = 'en', i18n, alertOnSuccess = true, alertOnError = true, alert) { 6 | var translate = buildTranslate('Paywall', lang, i18n); 7 | var error = null; 8 | var transaction = null; 9 | // Use default alert of not defined 10 | if (!alert) { 11 | alert = showAlert; 12 | } 13 | // Start buy 14 | try { 15 | transaction = await fn(); 16 | } 17 | // Catch any error 18 | catch (err) { 19 | error = err; 20 | } 21 | // Get alert message 22 | var message = getBuyAlertMessage(error, transaction, lang, i18n); 23 | if (transaction && alertOnSuccess == false) { 24 | message = null; 25 | } 26 | if (error && alertOnError == false) { 27 | message = null; 28 | } 29 | // Show message if there is one 30 | if (message) { 31 | await alert({title: message.title, description: message.description, button: translate('ok')}); 32 | } 33 | // Return promise 34 | return new Promise((resolve, reject) => { 35 | error ? reject(error) : resolve(transaction); 36 | }); 37 | } -------------------------------------------------------------------------------- /src/util/get-buy-alert-message.js: -------------------------------------------------------------------------------- 1 | import {Platform} from 'react-native'; 2 | import buildTranslate from '../i18n'; 3 | 4 | export default function(err, transaction, lang = 'en', i18n) { 5 | var translate = buildTranslate('Paywall', lang, i18n); 6 | var title = null; 7 | var description = null; 8 | 9 | if (transaction) { 10 | // Handle failed webhook 11 | if (transaction.webhookStatus == "failed") { 12 | title = translate('buyErrorTitle'); 13 | description = translate('buyErrorMessageReceiptFailed'); 14 | } 15 | // Handle subscription change on next renewal 16 | else if (transaction.subscriptionRenewalProductSku) { 17 | title = translate('buySuccessNextRenewalTitle'); 18 | description = translate('buySuccessNextRenewalMessage'); 19 | } 20 | // Regular success message 21 | else { 22 | title = translate('buySuccessTitle'); 23 | description = translate('buySuccessMessage'); 24 | } 25 | } 26 | else if (err) { 27 | if (err.code == "user_cancelled") { 28 | // Should not display an alert when the purchase is cancelled 29 | } 30 | else if (err.code == "deferred_payment") { 31 | title = translate('buyDeferredPaymentTitle'); 32 | description = translate('buyDeferredPaymentMessage'); 33 | } 34 | else if (err.code == "product_change_next_renewal") { 35 | title = translate('buyDeferredPaymentTitle'); 36 | description = translate('buyDeferredPaymentMessage'); 37 | } 38 | else if (err.code == "billing_unavailable" && err.subcode == "play_store_outdated") { 39 | title = translate('buyErrorTitle'); 40 | description = translate('buyErrorMessagePlayStoreOutdated'); 41 | } 42 | else if (['receipt_failed', 'product_not_available', 'anonymous_purchase_not_allowed', 'transaction_not_found', 'network_error', 'billing_unavailable', 'cross_platform_conflict', 'product_already_purchased', 'user_conflict', 'product_change_next_renewal'].indexOf(err.code) != -1) { 43 | var camelCaseCode = err.code.replace(/_([a-z])/g, (g) => g[1].toUpperCase()).replace(/^./, str => str.toUpperCase()); 44 | title = translate('buyErrorTitle'); 45 | description = translate(`buyErrorMessage${camelCaseCode}`, {platform: Platform.OS}); 46 | } 47 | else { 48 | title = translate('buyErrorTitle'); 49 | description = translate('buyErrorMessage'); 50 | } 51 | } 52 | else { 53 | title = translate('buyErrorTitle'); 54 | description = translate('buyErrorMessage'); 55 | } 56 | 57 | if (!title || !description) { 58 | return false; 59 | } 60 | return {title: title, description: description}; 61 | } -------------------------------------------------------------------------------- /src/util/get-subscription-duration.js: -------------------------------------------------------------------------------- 1 | import buildTranslate from '../i18n'; 2 | 3 | export default function(cycleDuration, cycleCount = 1, omitSingleUnit = true, lang = 'en', i18n) { 4 | var translate = buildTranslate('SubscriptionDuration', lang, i18n); 5 | var count = parseInt(cycleDuration.match(/[0-9]+/)) * cycleCount; 6 | 7 | var getDuration = () => { 8 | var duration = ""; 9 | 10 | if (cycleDuration.indexOf('D') != -1) { 11 | duration = translate(`xDay${count > 1 ? 's' : ''}`, {count: count}); 12 | } 13 | else if (cycleDuration.indexOf('W') != -1) { 14 | duration = translate(`xWeek${count > 1 ? 's' : ''}`, {count: count}); 15 | } 16 | else if (cycleDuration.indexOf('M') != -1) { 17 | duration = translate(`xMonth${count > 1 ? 's' : ''}`, {count: count}); 18 | } 19 | else if (cycleDuration.indexOf('Y') != -1) { 20 | duration = translate(`xYear${count > 1 ? 's' : ''}`, {count: count}); 21 | } 22 | 23 | return duration; 24 | }; 25 | 26 | var getSingleDuration = () => { 27 | var data = { 28 | "P7D": translate('week'), 29 | "P1W": translate('week'), 30 | "P1M": translate('month'), 31 | "P1Y": translate('year') 32 | }; 33 | 34 | return data[cycleDuration]; 35 | }; 36 | 37 | return (count > 1 || omitSingleUnit == false) ? getDuration() : getSingleDuration(); 38 | } -------------------------------------------------------------------------------- /src/util/get-subscription-price-duration.js: -------------------------------------------------------------------------------- 1 | import buildTranslate from '../i18n'; 2 | import getSubscriptionDuration from './get-subscription-duration'; 3 | 4 | export default function(localizedPrice, cycleDuration, omitSingleUnit = true, lang = 'en', i18n) { 5 | var translate = buildTranslate('SubscriptionPriceDuration', lang, i18n); 6 | var isMultiple = ['P7D', 'P1W', 'P1M', 'P1Y'].indexOf(cycleDuration) == -1; 7 | 8 | return translate(`cycle${isMultiple ? 's' : ''}`, { 9 | price: localizedPrice, 10 | duration: getSubscriptionDuration(cycleDuration, 1, omitSingleUnit, lang, i18n) 11 | }); 12 | } -------------------------------------------------------------------------------- /src/util/show-alert.js: -------------------------------------------------------------------------------- 1 | import {Alert} from 'react-native'; 2 | 3 | export default async function(opts) { 4 | return new Promise((resolve) => { 5 | Alert.alert(opts.title, opts.description, [ 6 | { 7 | text: opts.button, 8 | onPress: () => resolve() 9 | } 10 | ]); 11 | }); 12 | } -------------------------------------------------------------------------------- /src/util/style.js: -------------------------------------------------------------------------------- 1 | export default (style, condition) => { 2 | return condition ? style : null; 3 | } -------------------------------------------------------------------------------- /src/util/with-styles.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const formatStyles = (styles) => { 4 | var formattedStyles = {}; 5 | 6 | Object.keys(styles).forEach((propertyName) => { 7 | formattedStyles[propertyName] = [styles[propertyName]]; 8 | }); 9 | 10 | return formattedStyles; 11 | } 12 | 13 | const compileStyles = (defaultStyles, customStyles, customStyle) => { 14 | var styles = formatStyles(defaultStyles); 15 | 16 | if (customStyles) { 17 | Object.keys(customStyles).forEach((propertyName) => { 18 | if (!styles[propertyName]) { 19 | styles[propertyName] = []; 20 | } 21 | if (Array.isArray(customStyles[propertyName])) { 22 | styles[propertyName].push(...customStyles[propertyName]); 23 | } 24 | else { 25 | styles[propertyName].push(customStyles[propertyName]); 26 | } 27 | }); 28 | } 29 | if (customStyle) { 30 | if (!styles.root) { 31 | styles.root = []; 32 | } 33 | if (Array.isArray(customStyle)) { 34 | styles.root.push(...customStyle); 35 | } 36 | else { 37 | styles.root.push(customStyle); 38 | } 39 | } 40 | return styles; 41 | } 42 | 43 | export default (styles, themeName) => { 44 | return (Component) => { 45 | return (props) => { 46 | var compiledStyles = compileStyles(styles, Object.assign({}, props.theme ? props.theme[themeName] : {}, props.styles), props.style); 47 | 48 | return 49 | } 50 | } 51 | } --------------------------------------------------------------------------------