From 79b2ab954484bba9b7969906ea5e352ee9a46e79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yusuf=20To=CC=88r?= Date: Fri, 16 Feb 2024 13:38:45 -0500 Subject: [PATCH] Makes configure, register, and the purchase controller work --- android/build.gradle | 1 - .../PurchaseControllerModule.kt | 26 - .../ReactNativeActivityProvider.kt | 13 + .../SuperwallReactNativeModule.kt | 169 +- .../bridges/PurchaseControllerBridge.kt | 59 +- .../bridges/PurchaseResultBridge.kt | 65 - .../superwallreactnative/models/Experiment.kt | 24 + .../models/PaywallInfo.kt | 111 + .../models/PaywallSkippedReason.kt | 34 + .../models/PurchaseResult.kt | 24 + .../models/RestorationResult.kt | 19 + .../models/SubscriptionStatus.kt | 16 + .../models/SuperwallOptions.kt | 83 + example/android/app/build.gradle | 2 +- .../android/app/src/main/AndroidManifest.xml | 1 + example/android/build.gradle | 8 + example/package.json | 3 +- example/src/App.tsx | 23 +- example/src/RCPurchaseController.tsx | 276 + package-lock.json | 22485 ++++++++++++++++ package.json | 3 + src/index.tsx | 129 +- src/public/ComputedPropertyRequest.ts | 11 +- src/public/LogLevel.ts | 20 +- src/public/PaywallOptions.ts | 11 +- .../PaywallPresentationRequestStatus.ts | 48 +- src/public/PaywallSkippedReason.ts | 16 + src/public/PurchaseResult.ts | 49 +- src/public/RestorationResult.ts | 20 +- src/public/SuperwallOptions.ts | 16 +- yarn.lock | 385 +- 31 files changed, 23738 insertions(+), 412 deletions(-) delete mode 100644 android/src/main/java/com/superwallreactnative/PurchaseControllerModule.kt create mode 100644 android/src/main/java/com/superwallreactnative/ReactNativeActivityProvider.kt delete mode 100644 android/src/main/java/com/superwallreactnative/bridges/PurchaseResultBridge.kt create mode 100644 android/src/main/java/com/superwallreactnative/models/Experiment.kt create mode 100644 android/src/main/java/com/superwallreactnative/models/PaywallInfo.kt create mode 100644 android/src/main/java/com/superwallreactnative/models/PaywallSkippedReason.kt create mode 100644 android/src/main/java/com/superwallreactnative/models/PurchaseResult.kt create mode 100644 android/src/main/java/com/superwallreactnative/models/RestorationResult.kt create mode 100644 android/src/main/java/com/superwallreactnative/models/SubscriptionStatus.kt create mode 100644 android/src/main/java/com/superwallreactnative/models/SuperwallOptions.kt create mode 100644 example/src/RCPurchaseController.tsx create mode 100644 package-lock.json diff --git a/android/build.gradle b/android/build.gradle index 8cb9b53..4a0db4b 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -95,4 +95,3 @@ dependencies { implementation "com.superwall.sdk:superwall-android:1.0.0-alpha.45" implementation 'com.android.billingclient:billing:6.1.0' } - diff --git a/android/src/main/java/com/superwallreactnative/PurchaseControllerModule.kt b/android/src/main/java/com/superwallreactnative/PurchaseControllerModule.kt deleted file mode 100644 index 5d37d34..0000000 --- a/android/src/main/java/com/superwallreactnative/PurchaseControllerModule.kt +++ /dev/null @@ -1,26 +0,0 @@ -package com.superwallreactnative - -import com.facebook.react.bridge.* - -class PurchaseControllerModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) { - override fun getName(): String { - return "PurchaseController" - } - - @ReactMethod - fun initialize(purchaseControllerId: String, promise: Promise) { - try { - val purchaseController = PurchaseController(purchaseControllerId) - // Do additional setup if needed - - // Store the purchaseController instance in a map if you need to reference it later - // myControllersMap[purchaseControllerId] = purchaseController - - promise.resolve(null) - } catch (e: Exception) { - promise.reject(e) - } - } - - // Add additional methods to manipulate PurchaseController instances if needed -} diff --git a/android/src/main/java/com/superwallreactnative/ReactNativeActivityProvider.kt b/android/src/main/java/com/superwallreactnative/ReactNativeActivityProvider.kt new file mode 100644 index 0000000..5b299a2 --- /dev/null +++ b/android/src/main/java/com/superwallreactnative/ReactNativeActivityProvider.kt @@ -0,0 +1,13 @@ +package com.superwallreactnative + +import android.app.Activity +import com.facebook.react.bridge.ReactApplicationContext +import com.superwall.sdk.misc.ActivityProvider + +class ReactNativeActivityProvider(private val reactContext: ReactApplicationContext) : ActivityProvider { + + override fun getCurrentActivity(): Activity? { + // Utilizes ReactContext to get the current activity + return reactContext.currentActivity + } +} diff --git a/android/src/main/java/com/superwallreactnative/SuperwallReactNativeModule.kt b/android/src/main/java/com/superwallreactnative/SuperwallReactNativeModule.kt index 85310a3..7204513 100644 --- a/android/src/main/java/com/superwallreactnative/SuperwallReactNativeModule.kt +++ b/android/src/main/java/com/superwallreactnative/SuperwallReactNativeModule.kt @@ -1,93 +1,158 @@ package com.superwallreactnative -import android.content.Context +import android.os.Debug +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.bridge.ReadableMap +import com.facebook.react.modules.core.DeviceEventManagerModule import com.superwall.sdk.Superwall -import com.superwall.sdk.config.options.SuperwallOptions -import com.superwall.sdk.delegate.subscription_controller.PurchaseController import com.superwall.sdk.misc.ActivityProvider - - -class PurchaseControllerBridge(nativeModule: SuperwallReactNativeModule): PurchaseController { - - - - // Implement the PurchaseController interface - override suspend fun purchase( - activity: Activity, - productDetails: ProductDetails, - basePlanId: String?, - offerId: String? - ): PurchaseResult { - - - } - - override suspend fun restorePurchases(): RestorationResult { - - } -} +import com.superwall.sdk.paywall.presentation.PaywallPresentationHandler +import com.superwall.sdk.paywall.presentation.register +import com.superwallreactnative.models.PaywallInfo +import com.superwallreactnative.models.PaywallSkippedReason +import com.superwallreactnative.models.PurchaseResult +import com.superwallreactnative.models.RestorationResult +import com.superwallreactnative.models.SubscriptionStatus +import com.superwallreactnative.models.SuperwallOptions class SuperwallReactNativeModule(private val reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) { + private val purchaseController = PurchaseControllerBridge(reactContext) + private val activityProvider: ActivityProvider = ReactNativeActivityProvider(reactContext) override fun getName(): String { - return "Superwall" - } - - - var purchasePromise?: CompletableFuture = null - fun purchaseFromGooglePlayInRN( - productId: String, - basePlanId: String?, - offerId: String? - ): CompletableFuture { - purchasePromise = CompletableFuture() - - // Emit event to JS - - return promise - - } - - fun purchaseResult (result: PurchaseResult) { - purchasePromise?.complete(result) + return "SuperwallReactNative" } - @ReactMethod fun configure( apiKey: String, - options: ReadableMap? = null - usingPurchaseController: Bool? = null + options: ReadableMap? = null, + usingPurchaseController: Boolean, + completion: Promise ) { val options = options?.let { - SuperwallOptions.fromJson(options.toJson()) + SuperwallOptions.fromJson(options) } if (usingPurchaseController) { - val purchaseController = PurchaseControllerBridge(this) Superwall.configure( applicationContext = reactContext, apiKey = apiKey, options = options, - purchaseController = purchaseController + activityProvider = activityProvider, + purchaseController = purchaseController, + completion = { + completion.resolve(null) + } ) - } else { Superwall.configure( applicationContext = reactContext, apiKey = apiKey, - options = options + options = options, + activityProvider = activityProvider, + completion = { + completion.resolve(null) + } ) } Superwall.instance.setPlatformWrapper("React Native"); } + @ReactMethod + fun register( + event: String, + params: ReadableMap?, + handlerId: String?, + feature: Promise? + ) { + var handler: PaywallPresentationHandler? = null + + if (handlerId != null) { + handler = PaywallPresentationHandler() + handler.onPresent { + val data = Arguments.createMap().apply { + putMap("paywallInfoJson", PaywallInfo.toJson(it)) // Implement this method + putString("method", "onPresent") + putString("handlerId", handlerId) + } + + reactContext + .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) + .emit("paywallPresentationHandler", data) + } + + handler.onDismiss { + val data = Arguments.createMap().apply { + putMap("paywallInfoJson", PaywallInfo.toJson(it)) // Implement this method + putString("method", "onDismiss") + putString("handlerId", handlerId) + } + + reactContext + .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) + .emit("paywallPresentationHandler", data) + } + + handler.onError { + val data = Arguments.createMap().apply { + putString("method", "onError") + putString("errorString", it.message) + putString("handlerId", handlerId) + } + + reactContext + .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) + .emit("paywallPresentationHandler", data) + } + + handler.onSkip { + val data = Arguments.createMap().apply { + putString("method", "onSkip") + putMap("skippedReason", PaywallSkippedReason.toJson(it)) + putString("handlerId", handlerId) + } + + reactContext + .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) + .emit("paywallPresentationHandler", data) + } + } + + Superwall.instance.register( + event = event, + params = params?.toHashMap(), + handler = handler, + feature = { + feature?.resolve(null) + } + ) + } + + @ReactMethod + fun setSubscriptionStatus(status: String) { + val subscriptionStatus = SubscriptionStatus.fromString(status) + Superwall.instance.setSubscriptionStatus(subscriptionStatus) + } + + @ReactMethod + fun didPurchase(result: ReadableMap) { + val purchaseResult = PurchaseResult.fromJson(result) + purchaseController.purchasePromise?.complete(purchaseResult) + } + + @ReactMethod + fun didRestore(result: ReadableMap) { + val restorationResult = RestorationResult.fromJson(result) + purchaseController.restorePromise?.complete(restorationResult) + } + // @ReactMethod // fun configure( diff --git a/android/src/main/java/com/superwallreactnative/bridges/PurchaseControllerBridge.kt b/android/src/main/java/com/superwallreactnative/bridges/PurchaseControllerBridge.kt index 9f0d926..6d9d9a4 100644 --- a/android/src/main/java/com/superwallreactnative/bridges/PurchaseControllerBridge.kt +++ b/android/src/main/java/com/superwallreactnative/bridges/PurchaseControllerBridge.kt @@ -10,25 +10,40 @@ import com.superwall.sdk.delegate.subscription_controller.PurchaseController import kotlinx.coroutines.future.await import java.util.concurrent.CompletableFuture -//class PurchaseControllerBridge( -// private val purchaseController: PurchaseController -//) : PurchaseController { -// -// // Implement the PurchaseController interface -// override suspend fun purchase( -// activity: Activity, -// productDetails: ProductDetails, -// basePlanId: String?, -// offerId: String? -// ): PurchaseResult { -// return purchaseController.purchaseFromGooglePlay( -// productId = productDetails.productId, -// basePlanId = basePlanId, -// offerId = offerId -// ) -// } -// -// override suspend fun restorePurchases(): RestorationResult { -// return purchaseController.restorePurchases() -// } -//} +class PurchaseControllerBridge( + private val reactContext: ReactContext +): PurchaseController { + var purchasePromise: CompletableFuture? = null + var restorePromise: CompletableFuture? = null + + override suspend fun purchase( + activity: Activity, + productDetails: ProductDetails, + basePlanId: String?, + offerId: String? + ): PurchaseResult { + purchasePromise = CompletableFuture() + + val productData = Arguments.createMap().apply { + putString("productId", productDetails.productId) // Implement this method + putString("basePlanId", basePlanId) + putString("offerId", offerId) + } + + reactContext + .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) + .emit("purchaseFromGooglePlay", productData) + + return purchasePromise!!.await() + } + + override suspend fun restorePurchases(): RestorationResult { + restorePromise = CompletableFuture() + + reactContext + .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) + .emit("restore", null) + + return restorePromise!!.await() + } +} diff --git a/android/src/main/java/com/superwallreactnative/bridges/PurchaseResultBridge.kt b/android/src/main/java/com/superwallreactnative/bridges/PurchaseResultBridge.kt deleted file mode 100644 index c52d394..0000000 --- a/android/src/main/java/com/superwallreactnative/bridges/PurchaseResultBridge.kt +++ /dev/null @@ -1,65 +0,0 @@ -package com.superwallreactnative.bridges - -abstract class PurchaseResultBridge( - context: Context, - bridgeId: BridgeId, - initializationArgs: Map? = null -) : BridgeInstance(context, bridgeId, initializationArgs), MethodChannel.MethodCallHandler { - - abstract val purchaseResult: PurchaseResult - - override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { - result.notImplemented() - } -} - -class PurchaseResultCancelledBridge( - context: Context, - bridgeId: BridgeId, - initializationArgs: Map? = null -) : PurchaseResultBridge(context, bridgeId, initializationArgs) { - companion object { fun bridgeClass(): BridgeClass = "PurchaseResultCancelledBridge" } - override val purchaseResult: PurchaseResult = PurchaseResult.Cancelled() -} - -class PurchaseResultPurchasedBridge( - context: Context, - bridgeId: BridgeId, - initializationArgs: Map? = null -) : PurchaseResultBridge(context, bridgeId, initializationArgs) { - companion object { fun bridgeClass(): BridgeClass = "PurchaseResultPurchasedBridge" } - override val purchaseResult: PurchaseResult = PurchaseResult.Purchased() -} - -class PurchaseResultRestoredBridge( - context: Context, - bridgeId: BridgeId, - initializationArgs: Map? = null -) : PurchaseResultBridge(context, bridgeId, initializationArgs) { - companion object { fun bridgeClass(): BridgeClass = "PurchaseResultRestoredBridge" } - override val purchaseResult: PurchaseResult = PurchaseResult.Restored() -} - -class PurchaseResultPendingBridge( - context: Context, - bridgeId: BridgeId, - initializationArgs: Map? = null -) : PurchaseResultBridge(context, bridgeId, initializationArgs) { - companion object { fun bridgeClass(): BridgeClass = "PurchaseResultPendingBridge" } - override val purchaseResult: PurchaseResult = PurchaseResult.Pending() -} - -class PurchaseResultFailedBridge( - context: Context, - bridgeId: BridgeId, - initializationArgs: Map? = null -) : PurchaseResultBridge(context, bridgeId, initializationArgs) { - companion object { fun bridgeClass(): BridgeClass = "PurchaseResultFailedBridge" } - override val purchaseResult: PurchaseResult - - init { - val errorString = initializationArgs?.get("error") as? String - ?: throw IllegalArgumentException("Attempting to create `PurchaseResultFailedBridge` without providing `error`.") - purchaseResult = PurchaseResult.Failed(errorString) - } -} diff --git a/android/src/main/java/com/superwallreactnative/models/Experiment.kt b/android/src/main/java/com/superwallreactnative/models/Experiment.kt new file mode 100644 index 0000000..7c660c5 --- /dev/null +++ b/android/src/main/java/com/superwallreactnative/models/Experiment.kt @@ -0,0 +1,24 @@ +package com.superwallreactnative.models + +import com.facebook.react.bridge.Arguments +import com.facebook.react.bridge.ReadableMap +import com.superwall.sdk.models.triggers.Experiment +import com.superwall.sdk.paywall.presentation.internal.state.PaywallSkippedReason + +class Experiment { + companion object { + fun toJson(experiment: Experiment): ReadableMap { + val variantMap = Arguments.createMap() + variantMap.putString("id",experiment.variant.id) + variantMap.putString("type", experiment.variant.type.toString()) + variantMap.putString("paywallId", experiment.variant.paywallId) + + val experimentMap = Arguments.createMap() + experimentMap.putString("id", experiment.id) + experimentMap.putString("groupId", experiment.groupId) + experimentMap.putMap("variant", variantMap) + + return experimentMap + } + } +} diff --git a/android/src/main/java/com/superwallreactnative/models/PaywallInfo.kt b/android/src/main/java/com/superwallreactnative/models/PaywallInfo.kt new file mode 100644 index 0000000..27faab6 --- /dev/null +++ b/android/src/main/java/com/superwallreactnative/models/PaywallInfo.kt @@ -0,0 +1,111 @@ +package com.superwallreactnative.models + +import com.facebook.react.bridge.Arguments +import com.facebook.react.bridge.ReadableMap +import com.superwall.sdk.paywall.presentation.PaywallInfo + +class PaywallInfo { + companion object { + fun toJson(paywallInfo: PaywallInfo): ReadableMap { + val map = Arguments.createMap() + + map.putString("databaseId", paywallInfo.databaseId) + map.putString("identifier", paywallInfo.identifier) + map.putString("name", paywallInfo.name) + map.putString("url", paywallInfo.url.toString()) + paywallInfo.experiment?.let { + map.putMap("experiment", Experiment.toJson(it)) + } + paywallInfo.triggerSessionId?.let { map.putString("triggerSessionId", it) } + + val productsArray = Arguments.createArray() + paywallInfo.products.forEach { product -> + val productMap = Arguments.createMap() + productMap.putString("type", product.type.toString()) + productMap.putString("id", product.id) + productsArray.pushMap(productMap) + } + map.putArray("products", productsArray) + + val productIdsArray = Arguments.createArray() + paywallInfo.productIds.forEach { productId -> + productIdsArray.pushString(productId) + } + map.putArray("productIds", productIdsArray) + + paywallInfo.presentedByEventWithName?.let { map.putString("presentedByEventWithName", it) } + paywallInfo.presentedByEventWithId?.let { map.putString("presentedByEventWithId", it) } + paywallInfo.presentedByEventAt?.let { map.putString("presentedByEventAt", it) } + map.putString("presentedBy", paywallInfo.presentedBy) + paywallInfo.presentationSourceType?.let { map.putString("presentationSourceType", it) } + paywallInfo.responseLoadStartTime?.let { map.putString("responseLoadStartTime", it) } + paywallInfo.responseLoadCompleteTime?.let { map.putString("responseLoadCompleteTime", it) } + paywallInfo.responseLoadFailTime?.let { map.putString("responseLoadFailTime", it) } + paywallInfo.responseLoadDuration?.let { map.putDouble("responseLoadDuration", it.toDouble()) } + + map.putBoolean("isFreeTrialAvailable", paywallInfo.isFreeTrialAvailable) + map.putString("featureGatingBehavior", paywallInfo.featureGatingBehavior.toString()) + map.putString("closeReason", paywallInfo.closeReason.toString()) + + paywallInfo.webViewLoadStartTime?.let { map.putString("webViewLoadStartTime", it) } + paywallInfo.webViewLoadCompleteTime?.let { map.putString("webViewLoadCompleteTime", it) } + paywallInfo.webViewLoadFailTime?.let { map.putString("webViewLoadFailTime", it) } + paywallInfo.webViewLoadDuration?.let { map.putDouble("webViewLoadDuration", it.toDouble()) } + + paywallInfo.productsLoadStartTime?.let { map.putString("productsLoadStartTime", it) } + paywallInfo.productsLoadCompleteTime?.let { map.putString("productsLoadCompleteTime", it) } + paywallInfo.productsLoadFailTime?.let { map.putString("productsLoadFailTime", it) } + paywallInfo.productsLoadDuration?.let { map.putDouble("productsLoadDuration", it.toDouble()) } + + paywallInfo.paywalljsVersion?.let { map.putString("paywalljsVersion", it) } + + val computedPropertyRequestsArray = Arguments.createArray() + paywallInfo.computedPropertyRequests.forEach { request -> + val computedPropertyRequestsMap = Arguments.createMap() + computedPropertyRequestsMap.putString("eventName", request.eventName) + computedPropertyRequestsMap.putString("type", request.type.toString()) + computedPropertyRequestsArray.pushMap(computedPropertyRequestsMap) + } + map.putArray("computedPropertyRequests", computedPropertyRequestsArray) + + val surveysArray = Arguments.createArray() + paywallInfo.surveys.forEach { survey -> + val surveysMap = Arguments.createMap() + surveysMap.putString("id", survey.id) + surveysMap.putString("message", survey.message) + surveysMap.putString("title", survey.title) + surveysMap.putString("assignmentKey", survey.assignmentKey) + surveysMap.putBoolean("includeCloseOption", survey.includeCloseOption) + surveysMap.putBoolean("includeOtherOption", survey.includeOtherOption) + surveysMap.putDouble("presentationProbability", survey.presentationProbability) + surveysMap.putString("presentationCondition", survey.presentationCondition.rawValue) + + val optionsArray = Arguments.createArray() + survey.options.forEach { option -> + val optionsMap = Arguments.createMap() + optionsMap.putString("id", option.id) + optionsMap.putString("title", option.title) + optionsArray.pushMap(optionsMap) + } + surveysMap.putArray("options", optionsArray) + + surveysArray.pushMap(surveysMap) + } + map.putArray("surveys", surveysArray) + + val localNotificationsArray = Arguments.createArray() + paywallInfo.localNotifications.forEach { notification -> + val notificationsMap = Arguments.createMap() + notificationsMap.putInt("id", notification.id) + notificationsMap.putString("title", notification.title) + notificationsMap.putString("body", notification.body) + notificationsMap.putString("type", notification.type.toString()) + notificationsMap.putDouble("delay", notification.delay.toDouble()) + localNotificationsArray.pushMap(notificationsMap) + } + map.putArray("localNotifications", localNotificationsArray) + + return map + } + } +} diff --git a/android/src/main/java/com/superwallreactnative/models/PaywallSkippedReason.kt b/android/src/main/java/com/superwallreactnative/models/PaywallSkippedReason.kt new file mode 100644 index 0000000..bee480a --- /dev/null +++ b/android/src/main/java/com/superwallreactnative/models/PaywallSkippedReason.kt @@ -0,0 +1,34 @@ +package com.superwallreactnative.models + +import com.facebook.react.bridge.Arguments +import com.facebook.react.bridge.ReadableMap +import com.superwall.sdk.paywall.presentation.internal.state.PaywallSkippedReason + +class PaywallSkippedReason { + companion object { + fun toJson(skippedReason: PaywallSkippedReason): ReadableMap { + val map = Arguments.createMap() + + when (skippedReason) { + is PaywallSkippedReason.Holdout -> { + map.putString("type", "Holdout") + map.putMap("experiment", Experiment.toJson(skippedReason.experiment)) + } + + is PaywallSkippedReason.NoRuleMatch -> { + map.putString("type", "NoRuleMatch") + } + + is PaywallSkippedReason.EventNotFound -> { + map.putString("type", "EventNotFound") + } + + is PaywallSkippedReason.UserIsSubscribed -> { + map.putString("type", "UserIsSubscribed") + } + } + + return map + } + } +} diff --git a/android/src/main/java/com/superwallreactnative/models/PurchaseResult.kt b/android/src/main/java/com/superwallreactnative/models/PurchaseResult.kt new file mode 100644 index 0000000..c182ba9 --- /dev/null +++ b/android/src/main/java/com/superwallreactnative/models/PurchaseResult.kt @@ -0,0 +1,24 @@ +package com.superwallreactnative.models + +import com.facebook.react.bridge.ReadableMap +import com.superwall.sdk.delegate.PurchaseResult + +class PurchaseResult { + companion object { + fun fromJson(json: ReadableMap): PurchaseResult { + return when (json.getString("type")) { + "cancelled" -> PurchaseResult.Cancelled() + "purchased" -> PurchaseResult.Purchased() + "pending" -> PurchaseResult.Pending() + "restored" -> PurchaseResult.Restored() + "failed" -> { + // Assuming there's an error message for failed purchases + val errorMessage = json.getString("error") ?: "Unknown error" + PurchaseResult.Failed(errorMessage) + } + + else -> PurchaseResult.Failed("Unknown Purchase Result type") + } + } + } +} diff --git a/android/src/main/java/com/superwallreactnative/models/RestorationResult.kt b/android/src/main/java/com/superwallreactnative/models/RestorationResult.kt new file mode 100644 index 0000000..36c8996 --- /dev/null +++ b/android/src/main/java/com/superwallreactnative/models/RestorationResult.kt @@ -0,0 +1,19 @@ +package com.superwallreactnative.models + +import com.facebook.react.bridge.ReadableMap +import com.superwall.sdk.delegate.RestorationResult + +class RestorationResult { + companion object { + fun fromJson(json: ReadableMap): RestorationResult { + return when (json.getString("result")) { + "restored" -> RestorationResult.Restored() + "failed" -> { + val errorMessage = json.getString("errorMessage") ?: "Unknown error" + RestorationResult.Failed(Error(errorMessage)) + } + else -> RestorationResult.Failed(Error("Unknown restoration result")) + } + } + } +} diff --git a/android/src/main/java/com/superwallreactnative/models/SubscriptionStatus.kt b/android/src/main/java/com/superwallreactnative/models/SubscriptionStatus.kt new file mode 100644 index 0000000..4811939 --- /dev/null +++ b/android/src/main/java/com/superwallreactnative/models/SubscriptionStatus.kt @@ -0,0 +1,16 @@ +package com.superwallreactnative.models + +import com.superwall.sdk.delegate.SubscriptionStatus + +class SubscriptionStatus { + companion object { + fun fromString(subscriptionStatus: String): SubscriptionStatus { + return when (subscriptionStatus) { + "ACTIVE" -> SubscriptionStatus.ACTIVE + "INACTIVE" -> SubscriptionStatus.INACTIVE + "UNKNOWN" -> SubscriptionStatus.UNKNOWN + else -> SubscriptionStatus.UNKNOWN // Default case to handle unexpected values + } + } + } +} diff --git a/android/src/main/java/com/superwallreactnative/models/SuperwallOptions.kt b/android/src/main/java/com/superwallreactnative/models/SuperwallOptions.kt new file mode 100644 index 0000000..3e6048e --- /dev/null +++ b/android/src/main/java/com/superwallreactnative/models/SuperwallOptions.kt @@ -0,0 +1,83 @@ +package com.superwallreactnative.models + +import LogLevel +import LogScope +import com.facebook.react.bridge.ReadableMap +import com.superwall.sdk.config.options.PaywallOptions +import com.superwall.sdk.config.options.SuperwallOptions +import java.util.EnumSet + +class SuperwallOptions { + companion object { + fun fromJson(json: ReadableMap): SuperwallOptions { + val options = SuperwallOptions() + options.localeIdentifier = json.getString("localeIdentifier") + options.isExternalDataCollectionEnabled = json.getBoolean("isExternalDataCollectionEnabled") + options.isGameControllerEnabled = json.getBoolean("isGameControllerEnabled") + + val networkEnvironment = when (json.getString("networkEnvironment")) { + "release" -> SuperwallOptions.NetworkEnvironment.Release() + "releaseCandidate" -> SuperwallOptions.NetworkEnvironment.ReleaseCandidate() + "developer" -> SuperwallOptions.NetworkEnvironment.Developer() + else -> options.networkEnvironment + } + options.networkEnvironment = networkEnvironment + + // Logging + val loggingMap = json.getMap("logging") + val scopesArray = loggingMap?.getArray("scopes") + val scopes = EnumSet.noneOf(LogScope::class.java) + + if (scopesArray != null) { + for (i in 0 until scopesArray.size()) { + val scopeStr = scopesArray.getString(i) + try { + scopes.add(enumValueOf(scopeStr)) + } catch (e: IllegalArgumentException) {} + } + } + + val levelStr = loggingMap?.getString("level") + val level = LogLevel.values().find { it.toString().equals(levelStr, ignoreCase = true) } + ?: LogLevel.warn + + val logging = SuperwallOptions.Logging() + logging.scopes = scopes + logging.level = level + options.logging = logging + + // PaywallOptions + val paywallsMap = json.getMap("paywalls") + val isHapticFeedbackEnabled = paywallsMap?.getBoolean("isHapticFeedbackEnabled") + val shouldShowPurchaseFailureAlert = paywallsMap?.getBoolean("shouldShowPurchaseFailureAlert") + val shouldPreload = paywallsMap?.getBoolean("shouldPreload") + val automaticallyDismiss = paywallsMap?.getBoolean("automaticallyDismiss") + + val paywalls = PaywallOptions() + paywalls.isHapticFeedbackEnabled = isHapticFeedbackEnabled ?: paywalls.isHapticFeedbackEnabled + paywalls.shouldShowPurchaseFailureAlert = shouldShowPurchaseFailureAlert ?: paywalls.shouldShowPurchaseFailureAlert + paywalls.shouldPreload = shouldPreload ?: paywalls.shouldPreload + paywalls.automaticallyDismiss = automaticallyDismiss ?: paywalls.automaticallyDismiss + + val restoreFailedMap = paywallsMap?.getMap("restoreFailed") + val restoreFailed = PaywallOptions.RestoreFailed().apply { + restoreFailedMap?.let { + title = it.getString("title") ?: title // Keep default if not found + message = it.getString("message") ?: message // Keep default if not found + closeButtonTitle = it.getString("closeButtonTitle") ?: closeButtonTitle // Keep default if not found + } + } + paywalls.restoreFailed = restoreFailed + + val transactionBackgroundViewStr = paywallsMap?.getString("transactionBackgroundView") ?: paywalls.transactionBackgroundView + paywalls.transactionBackgroundView = when (transactionBackgroundViewStr) { + "spinner" -> PaywallOptions.TransactionBackgroundView.SPINNER + else -> null + } + + options.paywalls = paywalls + + return options + } + } +} diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index 7738829..17195ec 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -76,7 +76,7 @@ android { namespace "com.superwallreactnativeexample" defaultConfig { - applicationId "com.superwallreactnativeexample" + applicationId "com.superwall.superapp" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionCode 1 diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml index 4122f36..1716c14 100644 --- a/example/android/app/src/main/AndroidManifest.xml +++ b/example/android/app/src/main/AndroidManifest.xml @@ -1,6 +1,7 @@ + (); - React.useEffect(() => { - multiply(3, 7).then(setResult); + var apiKey = + Platform.OS === 'ios' + ? 'pk_5f6d9ae96b889bc2c36ca0f2368de2c4c3d5f6119aacd3d2' + : 'pk_d1f0959f70c761b1d55bb774a03e22b2b6ed290ce6561f85'; + + var purchaseController = new RCPurchaseController(); + Superwall.configure(apiKey, undefined, purchaseController); + purchaseController.configureAndSyncSubscriptionStatus(); }, []); + // Function to call when the button is pressed + const handlePress = () => { + Superwall.shared.register('campaign_trigger'); + }; + return ( - Result: {result} +