├── .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 |
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 | }
--------------------------------------------------------------------------------