diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 5d401a7..0000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,98 +0,0 @@ -version: 2.1 - -executors: - default: - docker: - - image: circleci/node:10 - working_directory: ~/project - -commands: - attach_project: - steps: - - attach_workspace: - at: ~/project - -jobs: - install-dependencies: - executor: default - steps: - - checkout - - attach_project - - restore_cache: - keys: - - dependencies-{{ checksum "package.json" }} - - dependencies- - - restore_cache: - keys: - - dependencies-example-{{ checksum "example/package.json" }} - - dependencies-example- - - run: - name: Install dependencies - command: | - yarn install --cwd example --frozen-lockfile - yarn install --frozen-lockfile - - save_cache: - key: dependencies-{{ checksum "package.json" }} - paths: node_modules - - save_cache: - key: dependencies-example-{{ checksum "example/package.json" }} - paths: example/node_modules - - persist_to_workspace: - root: . - paths: . - - lint: - executor: default - steps: - - attach_project - - run: - name: Lint files - command: | - yarn lint - - typescript: - executor: default - steps: - - attach_project - - run: - name: Typecheck files - command: | - yarn typescript - - unit-tests: - executor: default - steps: - - attach_project - - run: - name: Run unit tests - command: | - yarn test --coverage - - store_artifacts: - path: coverage - destination: coverage - - build-package: - executor: default - steps: - - attach_project - - run: - name: Build package - command: | - yarn prepare - -workflows: - build-and-test: - jobs: - - install-dependencies - - lint: - requires: - - install-dependencies - - typescript: - requires: - - install-dependencies - - unit-tests: - requires: - - install-dependencies - - build-package: - requires: - - install-dependencies diff --git a/.gitattributes b/.gitattributes index 030ef14..e27f70f 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1,3 @@ *.pbxproj -text # specific for windows script files -*.bat text eol=crlf \ No newline at end of file +*.bat text eol=crlf diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..6e42fec --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,67 @@ +name: πŸ› Bug report +description: Report a reproducible bug or regression in this library. +labels: [bug] +body: + - type: markdown + attributes: + value: | + # Bug report + + πŸ‘‹ Hi! + + **Please fill the following carefully before opening a new issue ❗** + *(Your issue may be closed if it doesn't provide the required pieces of information)* + - type: checkboxes + attributes: + label: Before submitting a new issue + description: Please perform simple checks first. + options: + - label: I tested using the latest version of the library, as the bug might be already fixed. + required: true + - label: I tested using a [supported version](https://github.com/reactwg/react-native-releases/blob/main/docs/support.md) of react native. + required: true + - label: I checked for possible duplicate issues, with possible answers. + required: true + - type: textarea + id: summary + attributes: + label: Bug summary + description: | + Provide a clear and concise description of what the bug is. + If needed, you can also provide other samples: error messages / stack traces, screenshots, gifs, etc. + validations: + required: true + - type: input + id: library-version + attributes: + label: Library version + description: What version of the library are you using? + placeholder: "x.x.x" + validations: + required: true + - type: textarea + id: react-native-info + attributes: + label: Environment info + description: Run `react-native info` in your terminal and paste the results here. + render: shell + validations: + required: true + - type: textarea + id: steps-to-reproduce + attributes: + label: Steps to reproduce + description: | + You must provide a clear list of steps and code to reproduce the problem. + value: | + 1. … + 2. … + validations: + required: true + - type: input + id: reproducible-example + attributes: + label: Reproducible example repository + description: Please provide a link to a repository on GitHub with a reproducible example. + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..0e4d3ae --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: Feature Request πŸ’‘ + url: https://github.com/cafebazaar/react-native-poolakey/discussions/new?category=ideas + about: If you have a feature request, please create a new discussion on GitHub. + - name: Discussions on GitHub πŸ’¬ + url: https://github.com/cafebazaar/react-native-poolakey/discussions + about: If this library works as promised but you need help, please ask questions there. diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml new file mode 100644 index 0000000..a613332 --- /dev/null +++ b/.github/actions/setup/action.yml @@ -0,0 +1,36 @@ +name: Setup +description: Setup Node.js and install dependencies + +runs: + using: composite + steps: + - name: Setup Node.js + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + with: + node-version-file: .nvmrc + + - name: Restore dependencies + id: yarn-cache + uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + with: + path: | + **/node_modules + .yarn/install-state.gz + key: ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock') }}-${{ hashFiles('**/package.json', '!node_modules/**') }} + restore-keys: | + ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock') }} + ${{ runner.os }}-yarn- + + - name: Install dependencies + if: steps.yarn-cache.outputs.cache-hit != 'true' + run: yarn install --immutable + shell: bash + + - name: Cache dependencies + if: steps.yarn-cache.outputs.cache-hit != 'true' + uses: actions/cache/save@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + with: + path: | + **/node_modules + .yarn/install-state.gz + key: ${{ steps.yarn-cache.outputs.cache-primary-key }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..ee892cb --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,166 @@ +name: CI +on: + push: + branches: + - main + pull_request: + branches: + - main + merge_group: + types: + - checks_requested + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + lint: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Setup + uses: ./.github/actions/setup + + - name: Lint files + run: yarn lint + + - name: Typecheck files + run: yarn typecheck + + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Setup + uses: ./.github/actions/setup + + - name: Run unit tests + run: yarn test --maxWorkers=2 --coverage + + build-library: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Setup + uses: ./.github/actions/setup + + - name: Build package + run: yarn prepare + + build-android: + runs-on: ubuntu-latest + + env: + TURBO_CACHE_DIR: .turbo/android + + steps: + - name: Checkout + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Setup + uses: ./.github/actions/setup + + - name: Cache turborepo for Android + uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + with: + path: ${{ env.TURBO_CACHE_DIR }} + key: ${{ runner.os }}-turborepo-android-${{ hashFiles('yarn.lock') }} + restore-keys: | + ${{ runner.os }}-turborepo-android- + + - name: Check turborepo cache for Android + run: | + TURBO_CACHE_STATUS=$(node -p "($(yarn turbo run build:android --cache-dir="${{ env.TURBO_CACHE_DIR }}" --dry=json)).tasks.find(t => t.task === 'build:android').cache.status") + + if [[ $TURBO_CACHE_STATUS == "HIT" ]]; then + echo "turbo_cache_hit=1" >> $GITHUB_ENV + fi + + - name: Install JDK + if: env.turbo_cache_hit != 1 + uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 + with: + distribution: 'zulu' + java-version: '17' + + - name: Finalize Android SDK + if: env.turbo_cache_hit != 1 + run: | + /bin/bash -c "yes | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --licenses > /dev/null" + + - name: Cache Gradle + if: env.turbo_cache_hit != 1 + uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + with: + path: | + ~/.gradle/wrapper + ~/.gradle/caches + key: ${{ runner.os }}-gradle-${{ hashFiles('example/android/gradle/wrapper/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + + - name: Build example for Android + env: + JAVA_OPTS: "-XX:MaxHeapSize=6g" + run: | + yarn turbo run build:android --cache-dir="${{ env.TURBO_CACHE_DIR }}" + + build-ios: + runs-on: macos-latest + + env: + XCODE_VERSION: 16.3 + TURBO_CACHE_DIR: .turbo/ios + RCT_USE_RN_DEP: 1 + RCT_USE_PREBUILT_RNCORE: 1 + + steps: + - name: Checkout + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Setup + uses: ./.github/actions/setup + + - name: Cache turborepo for iOS + uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + with: + path: ${{ env.TURBO_CACHE_DIR }} + key: ${{ runner.os }}-turborepo-ios-${{ hashFiles('yarn.lock') }} + restore-keys: | + ${{ runner.os }}-turborepo-ios- + + - name: Check turborepo cache for iOS + run: | + TURBO_CACHE_STATUS=$(node -p "($(yarn turbo run build:ios --cache-dir="${{ env.TURBO_CACHE_DIR }}" --dry=json)).tasks.find(t => t.task === 'build:ios').cache.status") + + if [[ $TURBO_CACHE_STATUS == "HIT" ]]; then + echo "turbo_cache_hit=1" >> $GITHUB_ENV + fi + + - name: Use appropriate Xcode version + if: env.turbo_cache_hit != 1 + uses: maxim-lobanov/setup-xcode@60606e260d2fc5762a71e64e74b2174e8ea3c8bd # v1.6.0 + with: + xcode-version: ${{ env.XCODE_VERSION }} + + - name: Install cocoapods + if: env.turbo_cache_hit != 1 && steps.cocoapods-cache.outputs.cache-hit != 'true' + run: | + cd example + bundle install + bundle exec pod repo update --verbose + bundle exec pod install --project-directory=ios + + - name: Build example for iOS + run: | + yarn turbo run build:ios --cache-dir="${{ env.TURBO_CACHE_DIR }}" diff --git a/.gitignore b/.gitignore index d3b53df..cae0682 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,9 @@ .vscode/ jsconfig.json +# library local builds +/*.tgz + # Xcode # build/ @@ -28,6 +31,7 @@ DerivedData *.ipa *.xcuserstate project.xcworkspace +**/.xcode.env.local # Android/IJ # @@ -76,3 +80,10 @@ android/keystores/debug.keystore # generated by bob lib/ + +# React Native Codegen +ios/generated +android/generated + +# React Native Nitro Modules +nitrogen/ diff --git a/.nvmrc b/.nvmrc index 3f430af..5f53e87 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -v18 +v20.19.0 diff --git a/.yarnrc.yml b/.yarnrc.yml index 0c7d84c..13215d6 100644 --- a/.yarnrc.yml +++ b/.yarnrc.yml @@ -2,7 +2,6 @@ nodeLinker: node-modules nmHoistingLimits: workspaces plugins: - # - path: scripts/pod-install.cjs - path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs spec: "@yarnpkg/plugin-interactive-tools" - path: .yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8d4348b..a192d0b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,13 +11,15 @@ This project is a monorepo managed using [Yarn workspaces](https://yarnpkg.com/f - The library package in the root directory. - An example app in the `example/` directory. -To get started with the project, run `yarn` in the root directory to install the required dependencies for each package: +To get started with the project, make sure you have the correct version of [Node.js](https://nodejs.org/) installed. See the [`.nvmrc`](./.nvmrc) file for the version used in this project. + +Run `yarn` in the root directory to install the required dependencies for each package: ```sh yarn ``` -> Since the project relies on Yarn workspaces, you cannot use [`npm`](https://github.com/npm/cli) for development. +> Since the project relies on Yarn workspaces, you cannot use [`npm`](https://github.com/npm/cli) for development without manually migrating. The [example app](/example/) demonstrates usage of the library. You need to run it to test any changes you make. @@ -47,6 +49,14 @@ To run the example app on iOS: yarn example ios ``` +To confirm that the app is running with the new architecture, you can check the Metro logs for a message like this: + +```sh +Running "ReactNativePoolakeyExample" with {"fabric":true,"initialProps":{"concurrentRoot":true},"rootTag":1} +``` + +Note the `"fabric":true` and `"concurrentRoot":true` properties. + Make sure your code passes TypeScript and ESLint. Run the following to verify: ```sh @@ -73,7 +83,7 @@ We follow the [conventional commits specification](https://www.conventionalcommi - `fix`: bug fixes, e.g. fix crash due to deprecated method. - `feat`: new features, e.g. add new method to the module. - `refactor`: code refactor, e.g. migrate from class components to hooks. -- `docs`: changes into documentation, e.g. add usage example for the module.. +- `docs`: changes into documentation, e.g. add usage example for the module. - `test`: adding or updating tests, e.g. add integration tests using detox. - `chore`: tooling changes, e.g. change CI config. @@ -101,7 +111,7 @@ yarn release The `package.json` file contains various scripts for common tasks: -- `yarn`: setup project by installing dependencies and pods - run with `POD_INSTALL=0` to skip installing pods. +- `yarn`: setup project by installing dependencies. - `yarn typecheck`: type-check files with TypeScript. - `yarn lint`: lint files with ESLint. - `yarn test`: run unit tests with Jest. diff --git a/LICENSE b/LICENSE index b8727e5..b34dc3c 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023 CafeBazaar +Copyright (c) 2025 CafeBazaar Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights diff --git a/README.md b/README.md index 4cb091a..989bee3 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,9 @@ ReactNative implementation of CafeBazaar's in-app payment SDK. ```sh yarn add @cafebazaar/react-native-poolakey ``` + or + ```sh npm install @cafebazaar/react-native-poolakey ``` @@ -41,9 +43,7 @@ import bazaar from '@cafebazaar/react-native-poolakey'; class MyApp extends React.Component { componentDidMount() { - bazaar - .connect(YOUR_RSA_KEY) - .catch(handleError); // bazaar is not installed or what?! + bazaar.connect(YOUR_RSA_KEY).catch(handleError); // bazaar is not installed or what?! } componentWillUnmount() { @@ -58,11 +58,13 @@ class MyApp extends React.Component { ``` ## Complete Example + Please see [example](https://github.com/cafebazaar/react-native-poolakey/tree/main/example) folder for a complete example react-native implementation. ## API Documentation ### connect / disconnect + TLDR: For each `connect` call, you need to call `disconnect` too. To use bazaar apis, user needs to have Bazaar app installed on his phone. @@ -78,34 +80,44 @@ Inside functional components, you can use `useBazaar` which automatically calls `connect`/`disconnect` on mount/unmount hooks. ### purchaseProduct(productId: string): Promise<PurchaseResult> + Purchase a product, bazaar starts payment flow automatically. ### consumePurchase(purchaseToken: string): Promise<void> + If your product is consumable, you can call `consumePurchase` whenever you see fit. To consume, you need to provide purchaseToken from a previous `consumePurchase` call result. ### subscribeProduct(productId: string): Promise<PurchaseResult> + Subscribe to a product, bazaar starts payment flow automatically. ### getPurchasedProducts(): Promise<PurchaseResult[]> + Get a list of products purchased by current user (logged in inside his bazaar app). ### getSubscribedProducts(): Promise<PurchaseResult[]> + Get a list of subscriptions purchased by current user (logged in inside his bazaar app). ### queryPurchaseProduct(productId: string): Promise<PurchaseResult> + Get a specific purchase data by productId ### querySubscribeProduct(productId: string): Promise<PurchaseResult> + Get a specific subscription data by productId ### getInAppSkuDetails(productIds: string[]): Promise + Get array of in-app sku details for all provided product ids ### getSubscriptionSkuDetails(productIds: string[]): Promise + Get array of subscription sku details for all provided product ids ### PurchaseResult + ```typescript type PurchaseResult = { orderId: string; @@ -115,10 +127,11 @@ type PurchaseResult = { purchaseState: number; developerPayload: string; purchaseToken: string; -} +}; ``` ### SkuDetails + ```typescript type SkuDetails = { sku: string; @@ -126,7 +139,7 @@ type SkuDetails = { price: string; title: Date; description: number; -} +}; ``` ## License diff --git a/ReactNativePoolakey.podspec b/ReactNativePoolakey.podspec new file mode 100644 index 0000000..dd9679d --- /dev/null +++ b/ReactNativePoolakey.podspec @@ -0,0 +1,21 @@ +require "json" + +package = JSON.parse(File.read(File.join(__dir__, "package.json"))) + +Pod::Spec.new do |s| + s.name = "ReactNativePoolakey" + s.version = package["version"] + s.summary = package["description"] + s.homepage = package["homepage"] + s.license = package["license"] + s.authors = package["author"] + + s.platforms = { :ios => min_ios_version_supported } + s.source = { :git => "https://github.com/cafebazaar/react-native-poolakey.git", :tag => "#{s.version}" } + + s.source_files = "ios/**/*.{h,m,mm,cpp}" + s.private_header_files = "ios/**/*.h" + + + install_modules_dependencies(s) +end diff --git a/android/build.gradle b/android/build.gradle index d9f7f1d..af9bc06 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,6 +1,7 @@ buildscript { - // Buildscript is evaluated before everything else so we can't use getExtOrDefault - def kotlin_version = rootProject.ext.has("kotlinVersion") ? rootProject.ext.get("kotlinVersion") : project.properties["ReactNativePoolakey_kotlinVersion"] + ext.getExtOrDefault = {name -> + return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties['ReactNativePoolakey_' + name] + } repositories { google() @@ -8,65 +9,36 @@ buildscript { } dependencies { - classpath "com.android.tools.build:gradle:7.2.1" + classpath "com.android.tools.build:gradle:8.7.2" // noinspection DifferentKotlinGradleVersion - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${getExtOrDefault('kotlinVersion')}" } } -def isNewArchitectureEnabled() { - return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true" -} apply plugin: "com.android.library" apply plugin: "kotlin-android" - -def appProject = rootProject.allprojects.find { it.plugins.hasPlugin('com.android.application') } - -if (isNewArchitectureEnabled()) { - apply plugin: "com.facebook.react" -} - -def getExtOrDefault(name) { - return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties["ReactNativePoolakey_" + name] -} +apply plugin: "com.facebook.react" def getExtOrIntegerDefault(name) { return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["ReactNativePoolakey_" + name]).toInteger() } -def supportsNamespace() { - def parsed = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.') - def major = parsed[0].toInteger() - def minor = parsed[1].toInteger() - - // Namespace support was added in 7.3.0 - if (major == 7 && minor >= 3) { - return true - } - - return major >= 8 -} - android { - if (supportsNamespace()) { - namespace "ir.cafebazaar.poolakey.rn" - - sourceSets { - main { - manifest.srcFile "src/main/AndroidManifestNew.xml" - } - } - } + namespace "com.cafebazaar.reactnativepoolakey" compileSdkVersion getExtOrIntegerDefault("compileSdkVersion") defaultConfig { minSdkVersion getExtOrIntegerDefault("minSdkVersion") targetSdkVersion getExtOrIntegerDefault("targetSdkVersion") - buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString() } + + buildFeatures { + buildConfig true + } + buildTypes { release { minifyEnabled false @@ -82,28 +54,27 @@ android { targetCompatibility JavaVersion.VERSION_1_8 } + sourceSets { + main { + java.srcDirs += [ + "generated/java", + "generated/jni" + ] + } + } } repositories { mavenCentral() google() + maven { url 'https://jitpack.io' } } def kotlin_version = getExtOrDefault("kotlinVersion") dependencies { - // For < 0.71, this will be from the local maven repo - // For > 0.71, this will be replaced by `com.facebook.react:react-android:$version` by react gradle plugin - //noinspection GradleDynamicVersion - implementation "com.facebook.react:react-native:+" + implementation "com.facebook.react:react-android" implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation "com.github.cafebazaar.Poolakey:poolakey:2.2.0" -} - -if (isNewArchitectureEnabled()) { - react { - jsRootDir = file("../src/") - libraryName = "ReactNativePoolakey" - codegenJavaPackageName = "ir.cafebazaar.poolakey.rn" - } + implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" } diff --git a/android/gradle.properties b/android/gradle.properties index d53efa2..599672d 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,5 +1,5 @@ -ReactNativePoolakey_kotlinVersion=1.7.0 -ReactNativePoolakey_minSdkVersion=21 -ReactNativePoolakey_targetSdkVersion=31 -ReactNativePoolakey_compileSdkVersion=31 -ReactNativePoolakey_ndkversion=21.4.7075529 +ReactNativePoolakey_kotlinVersion=2.0.21 +ReactNativePoolakey_minSdkVersion=24 +ReactNativePoolakey_targetSdkVersion=34 +ReactNativePoolakey_compileSdkVersion=35 +ReactNativePoolakey_ndkVersion=27.1.12297006 diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml index 101671b..57e7989 100644 --- a/android/src/main/AndroidManifest.xml +++ b/android/src/main/AndroidManifest.xml @@ -1,5 +1,6 @@ - - - - + + + + diff --git a/android/src/main/AndroidManifestNew.xml b/android/src/main/AndroidManifestNew.xml deleted file mode 100644 index 101671b..0000000 --- a/android/src/main/AndroidManifestNew.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/android/src/main/java/com/cafebazaar/reactnativepoolakey/NotFoundException.kt b/android/src/main/java/com/cafebazaar/reactnativepoolakey/NotFoundException.kt deleted file mode 100644 index 94a0260..0000000 --- a/android/src/main/java/com/cafebazaar/reactnativepoolakey/NotFoundException.kt +++ /dev/null @@ -1,3 +0,0 @@ -package ir.cafebazaar.poolakey.rn - -object NotFoundException : Exception("Item not found") diff --git a/android/src/main/java/com/cafebazaar/reactnativepoolakey/PaymentActivity.kt b/android/src/main/java/com/cafebazaar/reactnativepoolakey/PaymentActivity.kt index 11810a7..098d7f9 100644 --- a/android/src/main/java/com/cafebazaar/reactnativepoolakey/PaymentActivity.kt +++ b/android/src/main/java/com/cafebazaar/reactnativepoolakey/PaymentActivity.kt @@ -1,4 +1,4 @@ -package ir.cafebazaar.poolakey.rn +package com.cafebazaar.reactnativepoolakey import android.app.Activity import android.content.Intent @@ -15,7 +15,7 @@ class PaymentActivity : ComponentActivity() { val callback: PurchaseCallback.() -> Unit = { purchaseSucceed { purchaseEntity -> - promise.resolve(purchaseEntity.originalJson) + promise.resolve(Util.getWritableMapOf(purchaseEntity)) finish() } purchaseCanceled { @@ -26,8 +26,7 @@ class PaymentActivity : ComponentActivity() { promise.reject(throwable) finish() } - purchaseFlowBegan { - } + purchaseFlowBegan {} failedToBeginFlow { promise.reject(it) finish() @@ -41,17 +40,17 @@ class PaymentActivity : ComponentActivity() { private fun purchaseProduct(callback: PurchaseCallback.() -> Unit) { payment.purchaseProduct( - activityResultRegistry, - PurchaseRequest(productId, payload, dynamicPriceToken), - callback + activityResultRegistry, + PurchaseRequest(productId, payload, dynamicPriceToken), + callback ) } private fun subscribeProduct(callback: PurchaseCallback.() -> Unit) { payment.subscribeProduct( - activityResultRegistry, - PurchaseRequest(productId, payload, dynamicPriceToken), - callback + activityResultRegistry, + PurchaseRequest(productId, payload, dynamicPriceToken), + callback ) } @@ -66,13 +65,13 @@ class PaymentActivity : ComponentActivity() { @JvmStatic fun start( - activity: Activity, - command: Command, - productId: String, - payment: Payment, - promise: Promise, - payload: String?, - dynamicPriceToken: String? + activity: Activity, + command: Command, + productId: String, + payment: Payment, + promise: Promise, + payload: String?, + dynamicPriceToken: String? ) { PaymentActivity.command = command PaymentActivity.productId = productId diff --git a/android/src/main/java/com/cafebazaar/reactnativepoolakey/Purchase.kt b/android/src/main/java/com/cafebazaar/reactnativepoolakey/Purchase.kt deleted file mode 100644 index 921ac50..0000000 --- a/android/src/main/java/com/cafebazaar/reactnativepoolakey/Purchase.kt +++ /dev/null @@ -1,8 +0,0 @@ -package ir.cafebazaar.poolakey.rn - -import ir.cafebazaar.poolakey.entity.PurchaseInfo - -fun List.toJsonString(): String { - val originalsJson = map { it.originalJson } - return "[" + originalsJson.joinToString() + "]" -} diff --git a/android/src/main/java/com/cafebazaar/reactnativepoolakey/PurchaseDetails.kt b/android/src/main/java/com/cafebazaar/reactnativepoolakey/PurchaseDetails.kt new file mode 100644 index 0000000..be1838e --- /dev/null +++ b/android/src/main/java/com/cafebazaar/reactnativepoolakey/PurchaseDetails.kt @@ -0,0 +1,5 @@ +package com.cafebazaar.reactnativepoolakey + + +data class User(val name: String, val age: Int) {} +data class PurchaseDetails(val purchasedBy: String, val user: User) {} diff --git a/android/src/main/java/com/cafebazaar/reactnativepoolakey/ReactNativePoolakeyModule.kt b/android/src/main/java/com/cafebazaar/reactnativepoolakey/ReactNativePoolakeyModule.kt index a9aa802..e1b7be6 100644 --- a/android/src/main/java/com/cafebazaar/reactnativepoolakey/ReactNativePoolakeyModule.kt +++ b/android/src/main/java/com/cafebazaar/reactnativepoolakey/ReactNativePoolakeyModule.kt @@ -1,62 +1,78 @@ -package ir.cafebazaar.poolakey.rn +package com.cafebazaar.reactnativepoolakey -import android.app.Activity -import android.content.Intent +import android.util.Log +import com.facebook.react.bridge.Arguments import com.facebook.react.bridge.Promise import com.facebook.react.bridge.ReactApplicationContext -import com.facebook.react.bridge.ReactContextBaseJavaModule -import com.facebook.react.bridge.ReactMethod -import com.facebook.react.modules.core.DeviceEventManagerModule; +import com.facebook.react.bridge.ReadableArray +import com.facebook.react.bridge.WritableMap +import com.facebook.react.module.annotations.ReactModule +import com.facebook.react.modules.core.DeviceEventManagerModule import ir.cafebazaar.poolakey.Connection import ir.cafebazaar.poolakey.ConnectionState import ir.cafebazaar.poolakey.Payment import ir.cafebazaar.poolakey.config.PaymentConfiguration import ir.cafebazaar.poolakey.config.SecurityCheck import ir.cafebazaar.poolakey.exception.DisconnectException -import ir.cafebazaar.poolakey.request.PurchaseRequest -class ReactNativePoolakeyModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) { - - override fun getName(): String { - return "ReactNativePoolakey" - } +@ReactModule(name = ReactNativePoolakeyModule.NAME) +class ReactNativePoolakeyModule(private val reactContext: ReactApplicationContext) : + NativeReactNativePoolakeySpec(reactContext) { private var paymentConnection: Connection? = null private lateinit var payment: Payment - private var reactContext: ReactApplicationContext = reactContext - @ReactMethod - fun connectPayment(rsaPublicKey: String? = null, promise: Promise) { - // println("@@@ reactContext") - // println(reactContext) + data class KeyValue(val key: String?, val value: String?) + + private fun createMap(vararg keyValues: KeyValue): WritableMap { + val writableMap = Arguments.createMap() + for (keyValue in keyValues) { + if (keyValue.key != null) writableMap.putString(keyValue.key, keyValue.value) + } // end for + return writableMap + } // end createMap + - val securityCheck = if (rsaPublicKey == null) { + /** + * creates a new instance of Payment class + * initiates a connection to Bazaar application + * @param rsa + */ + override fun connectPayment(rsa: String?, promise: Promise?) { + // Step 1: define a payment object + val securityCheck = if (rsa == null) { SecurityCheck.Disable } else { - SecurityCheck.Enable(rsaPublicKey = rsaPublicKey) + SecurityCheck.Enable(rsaPublicKey = rsa) } + Log.i("Security check", securityCheck.toString()) val paymentConfig = PaymentConfiguration(localSecurityCheck = securityCheck) payment = Payment(context = reactContext, config = paymentConfig) + // Step 2: connect to bazaar service runIfPaymentInitialized(promise) { paymentConnection = payment.connect { - connectionSucceed { promise.resolve(null) } - connectionFailed { promise.reject(it) } + /* different callbacks to notify the connection state change */ + connectionSucceed { + promise?.resolve(null) + } + connectionFailed { + promise?.reject(it) + } disconnected { - // reactContext.removeActivityEventListener(this) - promise.reject(DisconnectException()) - reactContext - .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) + promise?.reject(DisconnectException()) + reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) .emit("disconnected", null); } } } - } + } // end connectPayment - @ReactMethod - fun disconnectPayment(promise: Promise) { + /** + * disconnects the connection from Bazaar + */ + override fun disconnectPayment() { paymentConnection?.disconnect() - promise.resolve(null) } fun startActivity( @@ -68,189 +84,187 @@ class ReactNativePoolakeyModule(reactContext: ReactApplicationContext) : ReactCo ) { if (paymentConnection?.getState() != ConnectionState.Connected) { - promise.reject(IllegalStateException("payment not connected")) + promise.reject(IllegalStateException("Payment not connected")) return } - val activity = getCurrentActivity(); + val activity = reactContext.currentActivity; if (activity == null) { - promise.reject(IllegalStateException("activity not found")) + promise.reject(IllegalStateException("Activity not found")) return } PaymentActivity.start( - activity, - command, - productId, - payment, - promise, - payload, - dynamicPriceToken + activity, command, productId, payment, promise, payload, dynamicPriceToken ) - } + } // end startActivity - @ReactMethod - fun purchaseProduct( - productId: String, - developerPayload: String?, - dynamicPriceToken: String?, - promise: Promise + override fun purchaseProduct( + productId: String?, developerPayload: String?, dynamicPriceToken: String?, promise: Promise? ) { - - check(currentActivity != null) { + check(reactContext.currentActivity != null) { "currentActivity is null" } + if (productId == null) { + promise?.reject(code = "nullException", message = "productId can't be null") + return + } runIfPaymentInitialized(promise) { startActivity( command = PaymentActivity.Command.Purchase, productId = productId, - promise = promise, + promise = promise!!, payload = developerPayload, dynamicPriceToken = dynamicPriceToken, ) - } - } + } // end purchaseProduct - @ReactMethod - fun subscribeProduct( - productId: String, - developerPayload: String?, - dynamicPriceToken: String?, - promise: Promise + override fun subscribeProduct( + productId: String?, developerPayload: String?, dynamicPriceToken: String?, promise: Promise? ) { - - check(currentActivity != null) { + check(reactContext.currentActivity != null) { "currentActivity is null" } + if (productId == null) { + promise?.reject(code = "nullException", message = "productId can't be null") + return + } + runIfPaymentInitialized(promise) { startActivity( command = PaymentActivity.Command.Subscribe, productId = productId, - promise = promise, + promise = promise!!, payload = developerPayload, dynamicPriceToken = dynamicPriceToken, ) } - } + } // end subscribeProduct + + override fun consumePurchase(purchaseToken: String?, promise: Promise?) { + if (purchaseToken == null) { + promise?.reject(code = "nullException", message = "purchaseToken can't be null") + return + } - @ReactMethod - fun consumePurchase(purchaseToken: String, promise: Promise) { runIfPaymentInitialized(promise) { payment.consumeProduct(purchaseToken) { - consumeFailed { promise.reject(it) } - consumeSucceed { promise.resolve(null) } + consumeFailed { promise?.reject(it) } + consumeSucceed { promise?.resolve(null) } } } - } + } // end consumePurchase - @ReactMethod - fun getPurchasedProducts(promise: Promise) { + override fun getPurchasedProducts(promise: Promise?) { runIfPaymentInitialized(promise) { payment.getPurchasedProducts { - queryFailed { promise.reject(it) } + queryFailed { + promise?.reject(it) + } querySucceed { - promise.resolve(it.toJsonString()) + promise?.resolve(Arguments.fromList(it)) } } } - } + } // end getPurchasedProducts - @ReactMethod - fun getSubscribedProducts(promise: Promise) { + override fun getSubscribedProducts(promise: Promise?) { runIfPaymentInitialized(promise) { payment.getSubscribedProducts { - queryFailed { promise.reject(it) } + queryFailed { promise?.reject(it) } querySucceed { - promise.resolve(it.toJsonString()) + promise?.resolve(Util.getWritableMapOf(it)) } } } - } - @ReactMethod - fun queryPurchaseProduct(productId: String, promise: Promise) { + } // end getSubscribedProducts + + override fun queryPurchaseProduct(productId: String?, promise: Promise?) { runIfPaymentInitialized(promise) { payment.getPurchasedProducts { - queryFailed { promise.reject(it) } + queryFailed { promise?.reject(it) } querySucceed { purchaseList -> val product = purchaseList.firstOrNull { it.productId == productId } if (product == null) { - promise.reject(NotFoundException) + promise?.reject(code = "notFound", "Product with id $productId not found") } else { - promise.resolve(product.originalJson) + promise?.resolve(Util.getWritableMapOf(product)) } } } } - } + } // end queryPurchaseProduct - @ReactMethod - fun querySubscribeProduct(productId: String, promise: Promise) { + override fun querySubscribeProduct(productId: String?, promise: Promise?) { runIfPaymentInitialized(promise) { payment.getSubscribedProducts { - queryFailed { promise.reject(it) } + queryFailed { promise?.reject(it) } querySucceed { purchaseList -> val product = purchaseList.firstOrNull { it.productId == productId } if (product == null) { - promise.reject(NotFoundException) + promise?.reject(code = "notFound", "Product with id $productId not found") } else { - promise.resolve(product.originalJson) + promise?.resolve(Util.getWritableMapOf(product)) } } } } - } + } // end querySubscribeProduct - @ReactMethod - fun getInAppSkuDetails(productIdsJson: String, promise: Promise) { - val productIds = parseProductIds(productIdsJson) + override fun getInAppSkuDetails(productIds: ReadableArray?, promise: Promise?) { + if (productIds == null || productIds.size() == 0) return + val productIdsList = mutableListOf() + for (productId in productIds.toArrayList()) { + productIdsList.add(productId.toString()) + } // end for runIfPaymentInitialized(promise) { - payment.getInAppSkuDetails(productIds) { - getSkuDetailsFailed { promise.reject(it) } + payment.getInAppSkuDetails(productIdsList) { + getSkuDetailsFailed { promise?.reject(it) } getSkuDetailsSucceed { - promise.resolve(it.toJsonString()) + promise?.resolve(Util.getWritableMapOf(it)) } } } - } + } // end getInAppSkuDetails - @ReactMethod - fun getSubscriptionSkuDetails(productIdsJson: String, promise: Promise) { - val productIds = parseProductIds(productIdsJson) + override fun getSubscriptionSkuDetails(productIds: ReadableArray?, promise: Promise?) { + if (productIds == null || productIds.size() == 0) return + val productIdsList = mutableListOf() + for (productId in productIds.toArrayList()) { + productIdsList.add(productId.toString()) + } // end for runIfPaymentInitialized(promise) { - payment.getSubscriptionSkuDetails(productIds) { - getSkuDetailsFailed { promise.reject(it) } + payment.getSubscriptionSkuDetails(productIdsList) { + getSkuDetailsFailed { promise?.reject(it) } getSkuDetailsSucceed { - promise.resolve(it.toJsonString()) + promise?.resolve(Util.getWritableMapOf(it)) } } } - } + } // end getSubscriptionSkuDetails - private fun runIfPaymentInitialized(promise: Promise?, runner: () -> Unit) { + private fun runIfPaymentInitialized(promise: Promise?, callback: () -> Unit) { if (::payment.isInitialized.not()) { - promise?.reject(IllegalStateException("payment not initialized")) + promise?.reject(IllegalStateException("Payment not initialized")) return } - - runner.invoke() + callback.invoke() } - @ReactMethod - fun addListener(type: String?) { - // Keep: Required for RN built in Event Emitter Calls. + override fun getName(): String { + return NAME } - @ReactMethod - fun removeListeners(type: Int?) { - // Keep: Required for RN built in Event Emitter Calls. + companion object { + const val NAME = "ReactNativePoolakey" } } diff --git a/android/src/main/java/com/cafebazaar/reactnativepoolakey/ReactNativePoolakeyPackage.kt b/android/src/main/java/com/cafebazaar/reactnativepoolakey/ReactNativePoolakeyPackage.kt index 6ee3cdb..b431fd5 100644 --- a/android/src/main/java/com/cafebazaar/reactnativepoolakey/ReactNativePoolakeyPackage.kt +++ b/android/src/main/java/com/cafebazaar/reactnativepoolakey/ReactNativePoolakeyPackage.kt @@ -1,17 +1,33 @@ -package ir.cafebazaar.poolakey.rn +package com.cafebazaar.reactnativepoolakey -import com.facebook.react.ReactPackage +import com.facebook.react.BaseReactPackage import com.facebook.react.bridge.NativeModule import com.facebook.react.bridge.ReactApplicationContext -import com.facebook.react.uimanager.ViewManager +import com.facebook.react.module.model.ReactModuleInfo +import com.facebook.react.module.model.ReactModuleInfoProvider +import java.util.HashMap - -class ReactNativePoolakeyPackage : ReactPackage { - override fun createNativeModules(reactContext: ReactApplicationContext): List { - return listOf(ReactNativePoolakeyModule(reactContext)) +class ReactNativePoolakeyPackage : BaseReactPackage() { + override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? { + return if (name == ReactNativePoolakeyModule.NAME) { + ReactNativePoolakeyModule(reactContext) + } else { + null + } } - override fun createViewManagers(reactContext: ReactApplicationContext): List> { - return emptyList() + override fun getReactModuleInfoProvider(): ReactModuleInfoProvider { + return ReactModuleInfoProvider { + val moduleInfos: MutableMap = HashMap() + moduleInfos[ReactNativePoolakeyModule.NAME] = ReactModuleInfo( + ReactNativePoolakeyModule.NAME, + ReactNativePoolakeyModule.NAME, + false, // canOverrideExistingModule + false, // needsEagerInit + false, // isCxxModule + true // isTurboModule + ) + moduleInfos + } } } diff --git a/android/src/main/java/com/cafebazaar/reactnativepoolakey/SkuDetails.kt b/android/src/main/java/com/cafebazaar/reactnativepoolakey/SkuDetails.kt deleted file mode 100644 index d064e18..0000000 --- a/android/src/main/java/com/cafebazaar/reactnativepoolakey/SkuDetails.kt +++ /dev/null @@ -1,29 +0,0 @@ -package ir.cafebazaar.poolakey.rn - -import ir.cafebazaar.poolakey.entity.SkuDetails -import org.json.JSONArray -import org.json.JSONObject - -fun List.toJsonString(): String { - val originalsJson = map { it.toJson() } - return "[" + originalsJson.joinToString() + "]" -} - -fun SkuDetails.toJson(): String { - val res = JSONObject() - res.put("sku", this.sku) - res.put("title", this.title) - res.put("type", this.type) - res.put("price", this.price) - res.put("description", this.description) - return res.toString() -} - -fun parseProductIds(productIdsJson: String): List { - val ja = JSONArray(productIdsJson); - val result = mutableListOf() - for (i in 0 until ja.length()) { - result.add(ja.getString(i)) - } - return result -} diff --git a/android/src/main/java/com/cafebazaar/reactnativepoolakey/Util.kt b/android/src/main/java/com/cafebazaar/reactnativepoolakey/Util.kt new file mode 100644 index 0000000..012577a --- /dev/null +++ b/android/src/main/java/com/cafebazaar/reactnativepoolakey/Util.kt @@ -0,0 +1,30 @@ +package com.cafebazaar.reactnativepoolakey + +import com.facebook.react.bridge.Arguments +import com.facebook.react.bridge.WritableMap +import kotlin.reflect.full.memberProperties + +object Util { + fun getWritableMapOf(obj: Any): WritableMap { + val writableMap = Arguments.createMap() + val objClass = obj::class + for(property in objClass.memberProperties) { + val key = property.name + val value = property.getter.call(obj) + when(value) { + // add other types in needed to be returned to JS thread + is Int -> writableMap.putInt(key, value) + is String -> writableMap.putString(key, value) + is Enum<*> -> writableMap.putString(key, value.toString()) + is Boolean -> writableMap.putBoolean(key, value) + is Long -> writableMap.putLong(key, value) + else -> { + if(value == null) continue; + val childWritableMap = getWritableMapOf(value) + writableMap.putMap(key, childWritableMap) + } + } // end when + } // end for + return writableMap + } // end getWritableMapOf +} diff --git a/babel.config.js b/babel.config.js index f842b77..0c05fd6 100644 --- a/babel.config.js +++ b/babel.config.js @@ -1,3 +1,12 @@ module.exports = { - presets: ['module:metro-react-native-babel-preset'], + overrides: [ + { + exclude: /\/node_modules\//, + presets: ['module:react-native-builder-bob/babel-preset'], + }, + { + include: /\/node_modules\//, + presets: ['module:@react-native/babel-preset'], + }, + ], }; diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..4f2c9a7 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,29 @@ +import { fixupConfigRules } from '@eslint/compat'; +import { FlatCompat } from '@eslint/eslintrc'; +import js from '@eslint/js'; +import prettier from 'eslint-plugin-prettier'; +import { defineConfig } from 'eslint/config'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const compat = new FlatCompat({ + baseDirectory: __dirname, + recommendedConfig: js.configs.recommended, + allConfig: js.configs.all, +}); + +export default defineConfig([ + { + extends: fixupConfigRules(compat.extends('@react-native', 'prettier')), + plugins: { prettier }, + rules: { + 'react/react-in-jsx-scope': 'off', + 'prettier/prettier': ['error', { endOfLine: 'auto' }], + }, + }, + { + ignores: ['node_modules/', 'lib/'], + }, +]); diff --git a/example/Gemfile b/example/Gemfile index 1fa2c2e..6a4c5f1 100644 --- a/example/Gemfile +++ b/example/Gemfile @@ -3,4 +3,14 @@ source 'https://rubygems.org' # You may use http://rbenv.org/ or https://rvm.io/ to install and use this version ruby ">= 2.6.10" -gem 'cocoapods', '~> 1.12' +# Exclude problematic versions of cocoapods and activesupport that causes build failures. +gem 'cocoapods', '>= 1.13', '!= 1.15.0', '!= 1.15.1' +gem 'activesupport', '>= 6.1.7.5', '!= 7.1.0' +gem 'xcodeproj', '< 1.26.0' +gem 'concurrent-ruby', '< 1.3.4' + +# Ruby 3.4.0 has removed some libraries from the standard library. +gem 'bigdecimal' +gem 'logger' +gem 'benchmark' +gem 'mutex_m' diff --git a/example/README.md b/example/README.md index 12470c3..3e2c3f8 100644 --- a/example/README.md +++ b/example/README.md @@ -2,58 +2,76 @@ This is a new [**React Native**](https://reactnative.dev) project, bootstrapped # Getting Started ->**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. +> **Note**: Make sure you have completed the [Set Up Your Environment](https://reactnative.dev/docs/set-up-your-environment) guide before proceeding. -## Step 1: Start the Metro Server +## Step 1: Start Metro -First, you will need to start **Metro**, the JavaScript _bundler_ that ships _with_ React Native. +First, you will need to run **Metro**, the JavaScript build tool for React Native. -To start Metro, run the following command from the _root_ of your React Native project: +To start the Metro dev server, run the following command from the root of your React Native project: -```bash -# using npm +```sh +# Using npm npm start # OR using Yarn yarn start ``` -## Step 2: Start your Application +## Step 2: Build and run your app -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: +With Metro running, open a new terminal window/pane from the root of your React Native project, and use one of the following commands to build and run your Android or iOS app: -### For Android +### Android -```bash -# using npm +```sh +# Using npm npm run android # OR using Yarn yarn android ``` -### For iOS +### iOS -```bash -# using npm +For iOS, remember to install CocoaPods dependencies (this only needs to be run on first clone or after updating native deps). + +The first time you create a new project, run the Ruby bundler to install CocoaPods itself: + +```sh +bundle install +``` + +Then, and every time you update your native dependencies, run: + +```sh +bundle exec pod install +``` + +For more information, please visit [CocoaPods Getting Started guide](https://guides.cocoapods.org/using/getting-started.html). + +```sh +# Using npm npm run ios # OR using Yarn yarn ios ``` -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. +If everything is set up correctly, you should see your new app running in the Android Emulator, iOS Simulator, or your connected device. + +This is one way to run your app β€” you can also build it directly from Android Studio or Xcode. -This is one way to run your app β€” you can also run it directly from within Android Studio and Xcode respectively. +## Step 3: Modify your app -## Step 3: Modifying your App +Now that you have successfully run the app, let's make changes! -Now that you have successfully run the app, let's modify it. +Open `App.tsx` in your text editor of choice and make some changes. When you save, your app will automatically update and reflect these changes β€”Β this is powered by [Fast Refresh](https://reactnative.dev/docs/fast-refresh). -1. Open `App.tsx` in your text editor of choice and edit some lines. -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! +When you want to forcefully reload, for example to reset the state of your app, you can perform a full reload: - For **iOS**: Hit Cmd ⌘ + R in your iOS Simulator to reload the app and see your changes! +- **Android**: Press the R key twice or select **"Reload"** from the **Dev Menu**, accessed via Ctrl + M (Windows/Linux) or Cmd ⌘ + M (macOS). +- **iOS**: Press R in iOS Simulator. ## Congratulations! :tada: @@ -62,11 +80,11 @@ You've successfully run and modified your React Native App. :partying_face: ### Now what? - 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). -- If you're curious to learn more about React Native, check out the [Introduction to React Native](https://reactnative.dev/docs/getting-started). +- If you're curious to learn more about React Native, check out the [docs](https://reactnative.dev/docs/getting-started). # Troubleshooting -If you can't get this to work, see the [Troubleshooting](https://reactnative.dev/docs/troubleshooting) page. +If you're having issues getting the above steps to work, see the [Troubleshooting](https://reactnative.dev/docs/troubleshooting) page. # Learn More diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index 3719c40..54d7962 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -1,4 +1,5 @@ apply plugin: "com.android.application" +apply plugin: "org.jetbrains.kotlin.android" apply plugin: "com.facebook.react" /** @@ -7,14 +8,14 @@ apply plugin: "com.facebook.react" */ react { /* Folders */ - // The root of your project, i.e. where "package.json" lives. Default is '..' - // root = file("../") - // The folder where the react-native NPM package is. Default is ../node_modules/react-native - // reactNativeDir = file("../node_modules/react-native") - // The folder where the react-native Codegen package is. Default is ../node_modules/@react-native/codegen - // codegenDir = file("../node_modules/@react-native/codegen") - // The cli.js file which is the React Native CLI entrypoint. Default is ../node_modules/react-native/cli.js - // cliFile = file("../node_modules/react-native/cli.js") + // The root of your project, i.e. where "package.json" lives. Default is '../..' + // root = file("../../") + // The folder where the react-native NPM package is. Default is ../../node_modules/react-native + // reactNativeDir = file("../../node_modules/react-native") + // The folder where the react-native Codegen package is. Default is ../../node_modules/@react-native/codegen + // codegenDir = file("../../node_modules/@react-native/codegen") + // The cli.js file which is the React Native CLI entrypoint. Default is ../../node_modules/react-native/cli.js + // cliFile = file("../../node_modules/react-native/cli.js") /* Variants */ // The list of variants to that are debuggable. For those we're going to @@ -48,6 +49,9 @@ react { // // The list of flags to pass to the Hermes compiler. By default is "-O", "-output-source-map" // hermesFlags = ["-O", "-output-source-map"] + + /* Autolinking */ + autolinkLibrariesWithApp() } /** @@ -59,23 +63,23 @@ def enableProguardInReleaseBuilds = false * The preferred build flavor of JavaScriptCore (JSC) * * For example, to use the international variant, you can use: - * `def jscFlavor = 'org.webkit:android-jsc-intl:+'` + * `def jscFlavor = io.github.react-native-community:jsc-android-intl:2026004.+` * * The international variant includes ICU i18n library and necessary data * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that * give correct results when using with locales other than en-US. Note that * this variant is about 6MiB larger per architecture than default. */ -def jscFlavor = 'org.webkit:android-jsc:+' +def jscFlavor = 'io.github.react-native-community:jsc-android:2026004.+' android { ndkVersion rootProject.ext.ndkVersion + buildToolsVersion rootProject.ext.buildToolsVersion + compileSdk rootProject.ext.compileSdkVersion - compileSdkVersion rootProject.ext.compileSdkVersion - - namespace "ir.cafebazaar.poolakey.rn.example" + namespace "cafebazaar.reactnativepoolakey.example" defaultConfig { - applicationId "ir.cafebazaar.poolakey.rn.example" + applicationId "cafebazaar.reactnativepoolakey.example" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionCode 1 @@ -107,17 +111,9 @@ dependencies { // The version of react-native is set by the React Native Gradle Plugin implementation("com.facebook.react:react-android") - debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") - debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") { - exclude group:'com.squareup.okhttp3', module:'okhttp' - } - - debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}") if (hermesEnabled.toBoolean()) { implementation("com.facebook.react:hermes-android") } else { implementation jscFlavor } } - -apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project) diff --git a/example/android/app/src/debug/AndroidManifest.xml b/example/android/app/src/debug/AndroidManifest.xml index 4b185bc..eb98c01 100644 --- a/example/android/app/src/debug/AndroidManifest.xml +++ b/example/android/app/src/debug/AndroidManifest.xml @@ -2,12 +2,8 @@ - - - - + tools:ignore="GoogleAppIndexingWarning"/> diff --git a/example/android/app/src/debug/java/com/reactnativepoolakeyexample/ReactNativeFlipper.java b/example/android/app/src/debug/java/com/reactnativepoolakeyexample/ReactNativeFlipper.java deleted file mode 100644 index 45f612a..0000000 --- a/example/android/app/src/debug/java/com/reactnativepoolakeyexample/ReactNativeFlipper.java +++ /dev/null @@ -1,75 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - *

This source code is licensed under the MIT license found in the LICENSE file in the root - * directory of this source tree. - */ -package ir.cafebazaar.poolakey.rn.example; - -import android.content.Context; -import com.facebook.flipper.android.AndroidFlipperClient; -import com.facebook.flipper.android.utils.FlipperUtils; -import com.facebook.flipper.core.FlipperClient; -import com.facebook.flipper.plugins.crashreporter.CrashReporterPlugin; -import com.facebook.flipper.plugins.databases.DatabasesFlipperPlugin; -import com.facebook.flipper.plugins.fresco.FrescoFlipperPlugin; -import com.facebook.flipper.plugins.inspector.DescriptorMapping; -import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin; -import com.facebook.flipper.plugins.network.FlipperOkhttpInterceptor; -import com.facebook.flipper.plugins.network.NetworkFlipperPlugin; -import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin; -import com.facebook.react.ReactInstanceEventListener; -import com.facebook.react.ReactInstanceManager; -import com.facebook.react.bridge.ReactContext; -import com.facebook.react.modules.network.NetworkingModule; -import okhttp3.OkHttpClient; - -/** - * Class responsible of loading Flipper inside your React Native application. This is the debug - * flavor of it. Here you can add your own plugins and customize the Flipper setup. - */ -public class ReactNativeFlipper { - public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) { - if (FlipperUtils.shouldEnableFlipper(context)) { - final FlipperClient client = AndroidFlipperClient.getInstance(context); - - client.addPlugin(new InspectorFlipperPlugin(context, DescriptorMapping.withDefaults())); - client.addPlugin(new DatabasesFlipperPlugin(context)); - client.addPlugin(new SharedPreferencesFlipperPlugin(context)); - client.addPlugin(CrashReporterPlugin.getInstance()); - - NetworkFlipperPlugin networkFlipperPlugin = new NetworkFlipperPlugin(); - NetworkingModule.setCustomClientBuilder( - new NetworkingModule.CustomClientBuilder() { - @Override - public void apply(OkHttpClient.Builder builder) { - builder.addNetworkInterceptor(new FlipperOkhttpInterceptor(networkFlipperPlugin)); - } - }); - client.addPlugin(networkFlipperPlugin); - client.start(); - - // Fresco Plugin needs to ensure that ImagePipelineFactory is initialized - // Hence we run if after all native modules have been initialized - ReactContext reactContext = reactInstanceManager.getCurrentReactContext(); - if (reactContext == null) { - reactInstanceManager.addReactInstanceEventListener( - new ReactInstanceEventListener() { - @Override - public void onReactContextInitialized(ReactContext reactContext) { - reactInstanceManager.removeReactInstanceEventListener(this); - reactContext.runOnNativeModulesQueueThread( - new Runnable() { - @Override - public void run() { - client.addPlugin(new FrescoFlipperPlugin()); - } - }); - } - }); - } else { - client.addPlugin(new FrescoFlipperPlugin()); - } - } - } -} diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml index 4122f36..e189252 100644 --- a/example/android/app/src/main/AndroidManifest.xml +++ b/example/android/app/src/main/AndroidManifest.xml @@ -8,7 +8,8 @@ android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round" android:allowBackup="false" - android:theme="@style/AppTheme"> + android:theme="@style/AppTheme" + android:supportsRtl="true"> = + PackageList(this).packages.apply { + // Packages that cannot be autolinked yet can be added manually here, for example: + // add(MyReactNativePackage()) + } + + override fun getJSMainModuleName(): String = "index" + + override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG + + override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED + override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED + } + + override val reactHost: ReactHost + get() = getDefaultReactHost(applicationContext, reactNativeHost) + + override fun onCreate() { + super.onCreate() + loadReactNative(this) + } +} diff --git a/example/android/app/src/main/java/com/reactnativepoolakeyexample/MainActivity.java b/example/android/app/src/main/java/com/reactnativepoolakeyexample/MainActivity.java deleted file mode 100644 index 2611f47..0000000 --- a/example/android/app/src/main/java/com/reactnativepoolakeyexample/MainActivity.java +++ /dev/null @@ -1,32 +0,0 @@ -package ir.cafebazaar.poolakey.rn.example; - -import com.facebook.react.ReactActivity; -import com.facebook.react.ReactActivityDelegate; -import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint; -import com.facebook.react.defaults.DefaultReactActivityDelegate; - -public class MainActivity extends ReactActivity { - - /** - * Returns the name of the main component registered from JavaScript. This is used to schedule - * rendering of the component. - */ - @Override - protected String getMainComponentName() { - return "ReactNativePoolakeyExample"; - } - - /** - * Returns the instance of the {@link ReactActivityDelegate}. Here we use a util class {@link - * DefaultReactActivityDelegate} which allows you to easily enable Fabric and Concurrent React - * (aka React 18) with two boolean flags. - */ - @Override - protected ReactActivityDelegate createReactActivityDelegate() { - return new DefaultReactActivityDelegate( - this, - getMainComponentName(), - // If you opted-in for the New Architecture, we enable the Fabric Renderer. - DefaultNewArchitectureEntryPoint.getFabricEnabled()); - } -} diff --git a/example/android/app/src/main/java/com/reactnativepoolakeyexample/MainApplication.java b/example/android/app/src/main/java/com/reactnativepoolakeyexample/MainApplication.java deleted file mode 100644 index 4219df8..0000000 --- a/example/android/app/src/main/java/com/reactnativepoolakeyexample/MainApplication.java +++ /dev/null @@ -1,64 +0,0 @@ -package ir.cafebazaar.poolakey.rn.example; - -import android.app.Application; -import com.facebook.react.PackageList; -import com.facebook.react.ReactApplication; -import com.facebook.react.ReactNativeHost; -import com.facebook.react.ReactPackage; -import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint; -import com.facebook.react.defaults.DefaultReactNativeHost; -import com.facebook.soloader.SoLoader; -import java.util.List; -import ir.cafebazaar.poolakey.rn.ReactNativePoolakeyPackage; - - -public class MainApplication extends Application implements ReactApplication { - - private final ReactNativeHost mReactNativeHost = - new DefaultReactNativeHost(this) { - @Override - public boolean getUseDeveloperSupport() { - return BuildConfig.DEBUG; - } - - @Override - protected List getPackages() { - @SuppressWarnings("UnnecessaryLocalVariable") - List packages = new PackageList(this).getPackages(); - // Packages that cannot be autolinked yet can be added manually here, for example: - // packages.add(new ReactNativePoolakeyPackage()); - return packages; - } - - @Override - protected String getJSMainModuleName() { - return "index"; - } - - @Override - protected boolean isNewArchEnabled() { - return BuildConfig.IS_NEW_ARCHITECTURE_ENABLED; - } - - @Override - protected Boolean isHermesEnabled() { - return BuildConfig.IS_HERMES_ENABLED; - } - }; - - @Override - public ReactNativeHost getReactNativeHost() { - return mReactNativeHost; - } - - @Override - public void onCreate() { - super.onCreate(); - SoLoader.init(this, /* native exopackage */ false); - if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { - // If you opted-in for the New Architecture, we load the native entry point for this app. - DefaultNewArchitectureEntryPoint.load(); - } - ReactNativeFlipper.initializeFlipper(this, getReactNativeHost().getReactInstanceManager()); - } -} diff --git a/example/android/app/src/main/res/drawable/rn_edit_text_material.xml b/example/android/app/src/main/res/drawable/rn_edit_text_material.xml index 73b37e4..5c25e72 100644 --- a/example/android/app/src/main/res/drawable/rn_edit_text_material.xml +++ b/example/android/app/src/main/res/drawable/rn_edit_text_material.xml @@ -17,7 +17,8 @@ android:insetLeft="@dimen/abc_edit_text_inset_horizontal_material" android:insetRight="@dimen/abc_edit_text_inset_horizontal_material" android:insetTop="@dimen/abc_edit_text_inset_top_material" - android:insetBottom="@dimen/abc_edit_text_inset_bottom_material"> + android:insetBottom="@dimen/abc_edit_text_inset_bottom_material" + > + NSAllowsArbitraryLoads + + NSAllowsLocalNetworking + NSLocationWhenInUseUsageDescription @@ -41,7 +38,7 @@ LaunchScreen UIRequiredDeviceCapabilities - armv7 + arm64 UISupportedInterfaceOrientations diff --git a/example/ios/ReactNativePoolakeyExample/PrivacyInfo.xcprivacy b/example/ios/ReactNativePoolakeyExample/PrivacyInfo.xcprivacy new file mode 100644 index 0000000..41b8317 --- /dev/null +++ b/example/ios/ReactNativePoolakeyExample/PrivacyInfo.xcprivacy @@ -0,0 +1,37 @@ + + + + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryFileTimestamp + NSPrivacyAccessedAPITypeReasons + + C617.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + NSPrivacyAccessedAPITypeReasons + + CA92.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategorySystemBootTime + NSPrivacyAccessedAPITypeReasons + + 35F9.1 + + + + NSPrivacyCollectedDataTypes + + NSPrivacyTracking + + + diff --git a/example/ios/ReactNativePoolakeyExample/main.m b/example/ios/ReactNativePoolakeyExample/main.m deleted file mode 100644 index d645c72..0000000 --- a/example/ios/ReactNativePoolakeyExample/main.m +++ /dev/null @@ -1,10 +0,0 @@ -#import - -#import "AppDelegate.h" - -int main(int argc, char *argv[]) -{ - @autoreleasepool { - return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); - } -} diff --git a/example/ios/ReactNativePoolakeyExampleTests/Info.plist b/example/ios/ReactNativePoolakeyExampleTests/Info.plist deleted file mode 100644 index ba72822..0000000 --- a/example/ios/ReactNativePoolakeyExampleTests/Info.plist +++ /dev/null @@ -1,24 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - BNDL - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - 1 - - diff --git a/example/ios/ReactNativePoolakeyExampleTests/ReactNativePoolakeyExampleTests.m b/example/ios/ReactNativePoolakeyExampleTests/ReactNativePoolakeyExampleTests.m deleted file mode 100644 index 00339cc..0000000 --- a/example/ios/ReactNativePoolakeyExampleTests/ReactNativePoolakeyExampleTests.m +++ /dev/null @@ -1,66 +0,0 @@ -#import -#import - -#import -#import - -#define TIMEOUT_SECONDS 600 -#define TEXT_TO_LOOK_FOR @"Welcome to React" - -@interface ReactNativePoolakeyExampleTests : XCTestCase - -@end - -@implementation ReactNativePoolakeyExampleTests - -- (BOOL)findSubviewInView:(UIView *)view matching:(BOOL (^)(UIView *view))test -{ - if (test(view)) { - return YES; - } - for (UIView *subview in [view subviews]) { - if ([self findSubviewInView:subview matching:test]) { - return YES; - } - } - return NO; -} - -- (void)testRendersWelcomeScreen -{ - UIViewController *vc = [[[RCTSharedApplication() delegate] window] rootViewController]; - NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS]; - BOOL foundElement = NO; - - __block NSString *redboxError = nil; -#ifdef DEBUG - RCTSetLogFunction( - ^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) { - if (level >= RCTLogLevelError) { - redboxError = message; - } - }); -#endif - - while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) { - [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; - [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; - - foundElement = [self findSubviewInView:vc.view - matching:^BOOL(UIView *view) { - if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) { - return YES; - } - return NO; - }]; - } - -#ifdef DEBUG - RCTSetLogFunction(RCTDefaultLogFunction); -#endif - - XCTAssertNil(redboxError, @"RedBox error: %@", redboxError); - XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS); -} - -@end diff --git a/example/metro.config.js b/example/metro.config.js index b823214..2da198e 100644 --- a/example/metro.config.js +++ b/example/metro.config.js @@ -1,11 +1,8 @@ -const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config'); const path = require('path'); -const escape = require('escape-string-regexp'); -const exclusionList = require('metro-config/src/defaults/exclusionList'); -const pak = require('../package.json'); +const { getDefaultConfig } = require('@react-native/metro-config'); +const { withMetroConfig } = require('react-native-monorepo-config'); const root = path.resolve(__dirname, '..'); -const modules = Object.keys({ ...pak.peerDependencies }); /** * Metro configuration @@ -13,33 +10,7 @@ const modules = Object.keys({ ...pak.peerDependencies }); * * @type {import('metro-config').MetroConfig} */ -const config = { - watchFolders: [root], - - // We need to make sure that only one version is loaded for peerDependencies - // So we block them at the root, and alias them to the versions in example's node_modules - resolver: { - blacklistRE: exclusionList( - modules.map( - (m) => - new RegExp(`^${escape(path.join(root, 'node_modules', m))}\\/.*$`) - ) - ), - - extraNodeModules: modules.reduce((acc, name) => { - acc[name] = path.join(__dirname, 'node_modules', name); - return acc; - }, {}), - }, - - transformer: { - getTransformOptions: async () => ({ - transform: { - experimentalImportSupport: false, - inlineRequires: true, - }, - }), - }, -}; - -module.exports = mergeConfig(getDefaultConfig(__dirname), config); +module.exports = withMetroConfig(getDefaultConfig(__dirname), { + root, + dirname: __dirname, +}); diff --git a/example/package.json b/example/package.json index 803e0e2..f63c086 100644 --- a/example/package.json +++ b/example/package.json @@ -6,23 +6,30 @@ "android": "react-native run-android", "ios": "react-native run-ios", "start": "react-native start", - "build:android": "cd android && ./gradlew assembleDebug --no-daemon --console=plain -PreactNativeArchitectures=arm64-v8a", - "build:ios": "cd ios && xcodebuild -workspace ReactNativePoolakeyExample.xcworkspace -scheme ReactNativePoolakeyExample -configuration Debug -sdk iphonesimulator CC=clang CPLUSPLUS=clang++ LD=clang LDPLUSPLUS=clang++ GCC_OPTIMIZATION_LEVEL=0 GCC_PRECOMPILE_PREFIX_HEADER=YES ASSETCATALOG_COMPILER_OPTIMIZATION=time DEBUG_INFORMATION_FORMAT=dwarf COMPILER_INDEX_STORE_ENABLE=NO" + "build:android": "react-native build-android --extra-params \"--no-daemon --console=plain -PreactNativeArchitectures=arm64-v8a\"", + "build:ios": "react-native build-ios --mode Debug" }, "dependencies": { - "react": "18.2.0", - "react-native": "0.72.5" + "@react-native/new-app-screen": "0.81.1", + "react": "19.1.0", + "react-native": "0.81.1", + "react-native-safe-area-context": "^5.5.2" }, "devDependencies": { - "@babel/core": "^7.20.0", - "@babel/preset-env": "^7.20.0", - "@babel/runtime": "^7.20.0", - "@react-native/metro-config": "^0.72.11", - "babel-plugin-module-resolver": "^5.0.0", - "metro-react-native-babel-preset": "0.76.8", - "pod-install": "^0.1.0" + "@babel/core": "^7.25.2", + "@babel/preset-env": "^7.25.3", + "@babel/runtime": "^7.25.0", + "@react-native-community/cli": "20.0.0", + "@react-native-community/cli-platform-android": "20.0.0", + "@react-native-community/cli-platform-ios": "20.0.0", + "@react-native/babel-preset": "0.81.1", + "@react-native/metro-config": "0.81.1", + "@react-native/typescript-config": "0.81.1", + "@types/react": "^19.1.0", + "react-native-builder-bob": "^0.40.13", + "react-native-monorepo-config": "^0.1.9" }, "engines": { - "node": ">=16" + "node": ">=20" } } diff --git a/example/react-native.config.js b/example/react-native.config.js index a516695..59d9698 100644 --- a/example/react-native.config.js +++ b/example/react-native.config.js @@ -1,10 +1,21 @@ const path = require('path'); -const pak = require('../package.json'); +const pkg = require('../package.json'); module.exports = { + project: { + ios: { + automaticPodsInstallation: true, + }, + }, dependencies: { - [pak.name]: { + [pkg.name]: { root: path.join(__dirname, '..'), + platforms: { + // Codegen script incorrectly fails without this + // So we explicitly specify the platforms with empty object + ios: {}, + android: {}, + }, }, }, }; diff --git a/example/src/App.jsx b/example/src/App.jsx deleted file mode 100644 index 8cc4d5a..0000000 --- a/example/src/App.jsx +++ /dev/null @@ -1,266 +0,0 @@ -/* eslint-disable react-native/no-inline-styles */ -import React, { useEffect, useRef, useState } from 'react'; - -import { - SafeAreaView, - ScrollView, - FlatList, - TouchableNativeFeedback, - Text, - TextInput, - Button, - Image, - View, - ActivityIndicator, -} from 'react-native'; - -import Poolakey from '@cafebazaar/react-native-poolakey'; -import { APP_PRODUCTS, RSA_KEY } from './configs'; -import styles from './styles'; - -const productsBySku = new Map(); -APP_PRODUCTS.map((p) => productsBySku.set(p.sku, p)); - -const icons = { - free: require('../assets/free.png'), - gas_inf: require('../assets/gas_inf.png'), - premiumSkin: require('../assets/premium.png'), - gas0: require('../assets/gas0.png'), - gas1: require('../assets/gas1.png'), - gas2: require('../assets/gas2.png'), - gas3: require('../assets/gas3.png'), - gas4: require('../assets/gas4.png'), - gas5: require('../assets/gas_inf.png'), -}; - -export default function App() { - const mounted = useRef(false); - const logIndex = useRef(1); - const [waiting, setWaiting] = useState(true); - const [dynamicPriceToken, setDynamicPriceToken] = useState(''); - const [log, setLog] = useState(''); - const [products, setProducts] = useState([]); - - const [vehicle, setVehicleData] = useState({ - gas: 4, - gasIcon: icons.gas4, - skin: icons.free, - }); - - useEffect(() => { - mounted.current = true; - - Poolakey.connect(RSA_KEY) - .then(() => { - console.log('@ SDK Connected'); - fetchProductDetails(); - }) - .catch(recordLog); // bazaar is not installed or what?! - - return () => { - console.log('@ SDK Disconnected'); - Poolakey.disconnect(); - mounted.current = false; - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - function setVehicleGas(gas) { - if (gas < 0) { - recordLog(`Fuel tank is empty !`); - return; - } - - setVehicleData({ - ...vehicle, - gas, - gasIcon: icons[`gas${gas}`], - }); - } - - function drive() { - if (vehicle.gas < 5) { - setVehicleGas(vehicle.gas - 1); - } - } - - function recordLog(message) { - const messageStr = - typeof message === 'object' ? JSON.stringify(message) : message; - - console.log(message); - - if (mounted.current) { - const logSep = log ? '\n\n' : ''; - setLog(`${log}${logSep}${logIndex.current}: \n${messageStr}`); - logIndex.current = logIndex.current + 1; - } - } - - async function fetchProductDetails() { - try { - const productIds = APP_PRODUCTS.map((x) => x.sku); - const skuDetails = await Poolakey.getInAppSkuDetails(productIds); - const _products = skuDetails.map((p) => { - // if (!productsBySku[s.sku]) return; - // const - // productsBySku[s.sku].title = s.title; - // productsBySku[s.sku].price = s.price; - // productsBySku[s.sku].description = s.description; - return { ...p, icon: productsBySku.get(p.sku)?.icon }; - }); - // console.log('@ skuDetails :>> ', skuDetails); - setProducts(_products); - console.log('_products :>> ', _products); - // Update state based on old purchases and subscriptions - const purchases = await Poolakey.getPurchasedProducts(); - console.log('@ purchases :>> ', purchases); - purchases.map((p) => { - handlePurchase(p); - }); - let subscribes = await Poolakey.getSubscribedProducts(); - subscribes.map((p) => { - handlePurchase(p); - }); - } catch (error) { - recordLog(`@ fetchProductDetails >> ${error}`); - } finally { - setWaiting(false); - } - } - - async function purchase(item) { - if (item.sku === 'gas' || item.sku === 'dynamic_price') { - if (vehicle.gas >= 4) { - recordLog(`Fuel tank is full !`); - return; - } - } - - const _dynamicPriceToken = - item.sku === 'dynamic_price' ? dynamicPriceToken : ''; - - const purchaseInfo = await Poolakey.purchaseProduct( - item.sku, - '', - _dynamicPriceToken - ).catch((e) => { - recordLog(`Purchase error => ${e.message}.`); - }); - - if (purchaseInfo === null || purchaseInfo.purchaseState !== 0) { - recordLog(`Purchase failed.`); - return; - } - handlePurchase(purchaseInfo); - } - - async function handlePurchase(purchaseInfo) { - recordLog(purchaseInfo); - // Consume purchase - if (productsBySku[purchaseInfo.productId]?.consumable) { - await Poolakey.consumePurchase(purchaseInfo.purchaseToken); - } - - // Change assets value - if ( - purchaseInfo.productId === 'gas' || - purchaseInfo.productId === 'dynamic_price' - ) { - setVehicleGas(vehicle.gas + 1); - } else if (purchaseInfo.productId === 'infinite_gas_monthly') { - setVehicleGas(5); - } else if (purchaseInfo.productId === 'premium') { - setVehicleData({ - ...vehicle, - skin: icons.premiumSkin, - }); - } - } - - if (waiting) { - return ( - - - Ψ―Ψ±Ψ­Ψ§Ω„ دریافΨͺ Ψ§Ψ·Ω„Ψ§ΨΉΨ§Ψͺ - - - - ); - } - - return ( - - - - - - - - -