From 53e264a2d544a1637e2aa67242d3e2140b0d2794 Mon Sep 17 00:00:00 2001 From: Facundo Menzella Date: Wed, 10 Sep 2025 12:16:15 +0200 Subject: [PATCH 01/65] Add RevenueCatUI package --- RevenueCat/Scripts/PurchasesWrapperAndroid.cs | 1 + RevenueCatUI/Editor.meta | 8 + RevenueCatUI/Plugins.meta | 8 + RevenueCatUI/Plugins/Android.meta | 8 + .../Plugins/Android/AndroidManifest.xml | 10 + .../Plugins/Android/AndroidManifest.xml.meta | 7 + RevenueCatUI/Plugins/Android/build.gradle | 25 + .../Plugins/Android/build.gradle.meta | 7 + .../Plugins/Android/proguard-rules.pro | 12 + .../Plugins/Android/proguard-rules.pro.meta | 7 + RevenueCatUI/Plugins/Android/src.meta | 8 + RevenueCatUI/Plugins/Android/src/main.meta | 8 + .../Plugins/Android/src/main/java.meta | 8 + .../Plugins/Android/src/main/java/com.meta | 8 + .../Android/src/main/java/com/revenuecat.meta | 8 + .../src/main/java/com/revenuecat/unity.meta | 8 + .../unity/PaywallBridgeActivity.java | 183 ++++ .../unity/PaywallBridgeActivity.java.meta | 32 + .../revenuecat/unity/RevenueCatUIPlugin.java | 135 +++ .../unity/RevenueCatUIPlugin.java.meta | 32 + RevenueCatUI/Plugins/Editor.meta | 8 + .../Editor/RevenueCat.UI.Editor.asmdef | 18 + .../Editor/RevenueCat.UI.Editor.asmdef.meta | 7 + .../Editor/RevenueCatUIDependencies.xml | 36 + .../Editor/RevenueCatUIDependencies.xml.meta | 7 + RevenueCatUI/Plugins/iOS.meta | 8 + RevenueCatUI/Plugins/iOS/RevenueCatUIBridge.m | 166 +++ .../Plugins/iOS/RevenueCatUIBridge.m.meta | 37 + .../Plugins/iOS/RevenueCatUIDummy.swift | 9 + .../Plugins/iOS/RevenueCatUIDummy.swift.meta | 37 + RevenueCatUI/README.md | 194 ++++ RevenueCatUI/README.md.meta | 7 + RevenueCatUI/Resources.meta | 8 + RevenueCatUI/Runtime.meta | 8 + RevenueCatUI/Runtime/Examples.meta | 8 + .../Examples/RevenueCatUICompleteExample.cs | 213 ++++ .../RevenueCatUICompleteExample.cs.meta | 11 + .../Runtime/Examples/RevenueCatUIExample.cs | 183 ++++ .../Examples/RevenueCatUIExample.cs.meta | 11 + RevenueCatUI/Runtime/Internal.meta | 8 + .../Internal/CustomerCenterPresenter.cs | 58 + .../Internal/CustomerCenterPresenter.cs.meta | 11 + .../Internal/ICustomerCenterPresenter.cs | 23 + .../Internal/ICustomerCenterPresenter.cs.meta | 11 + .../Runtime/Internal/IPaywallPresenter.cs | 32 + .../Internal/IPaywallPresenter.cs.meta | 11 + RevenueCatUI/Runtime/Internal/JsonUtility.cs | 41 + .../Runtime/Internal/JsonUtility.cs.meta | 11 + .../Runtime/Internal/PaywallPresenter.cs | 64 ++ .../Runtime/Internal/PaywallPresenter.cs.meta | 11 + .../Internal/RevenueCatUICallbackHandler.cs | 55 + .../RevenueCatUICallbackHandler.cs.meta | 11 + RevenueCatUI/Runtime/PaywallOptions.cs | 50 + RevenueCatUI/Runtime/PaywallOptions.cs.meta | 11 + RevenueCatUI/Runtime/PaywallResult.cs | 110 ++ RevenueCatUI/Runtime/PaywallResult.cs.meta | 11 + RevenueCatUI/Runtime/Platforms.meta | 8 + .../AndroidCustomerCenterPresenter.cs | 83 ++ .../AndroidCustomerCenterPresenter.cs.meta | 11 + .../Platforms/AndroidPaywallPresenter.cs | 139 +++ .../Platforms/AndroidPaywallPresenter.cs.meta | 11 + RevenueCatUI/Runtime/Platforms/Stub.meta | 8 + .../Stub/StubCustomerCenterPresenter.cs | 24 + .../Stub/StubCustomerCenterPresenter.cs.meta | 11 + .../Platforms/Stub/StubPaywallPresenter.cs | 33 + .../Stub/StubPaywallPresenter.cs.meta | 11 + RevenueCatUI/Runtime/Platforms/iOS.meta | 8 + .../iOS/IOSCustomerCenterPresenter.cs | 92 ++ .../iOS/IOSCustomerCenterPresenter.cs.meta | 11 + .../Platforms/iOS/IOSPaywallPresenter.cs | 183 ++++ .../Platforms/iOS/IOSPaywallPresenter.cs.meta | 11 + RevenueCatUI/Runtime/RevenueCat.UI.asmdef | 20 + .../Runtime/RevenueCat.UI.asmdef.meta | 7 + RevenueCatUI/Runtime/RevenueCatUI.cs | 102 ++ RevenueCatUI/Runtime/RevenueCatUI.cs.meta | 11 + RevenueCatUI/UI.meta | 8 + RevenueCatUI/package.json | 32 + RevenueCatUI/package.json.meta | 7 + .../Assets/Scripts/RevenueCatUITestButtons.cs | 59 ++ .../Scripts/RevenueCatUITestButtons.cs.meta | 11 + Subtester/Packages/manifest.json | 1 + Subtester/Packages/packages-lock.json | 18 +- .../ProjectSettings/GvhProjectSettings.xml | 26 +- .../UserSettings/Layouts/default-2021.dwlt | 999 ++++++++++++++++++ 84 files changed, 3943 insertions(+), 30 deletions(-) create mode 100644 RevenueCatUI/Editor.meta create mode 100644 RevenueCatUI/Plugins.meta create mode 100644 RevenueCatUI/Plugins/Android.meta create mode 100644 RevenueCatUI/Plugins/Android/AndroidManifest.xml create mode 100644 RevenueCatUI/Plugins/Android/AndroidManifest.xml.meta create mode 100644 RevenueCatUI/Plugins/Android/build.gradle create mode 100644 RevenueCatUI/Plugins/Android/build.gradle.meta create mode 100644 RevenueCatUI/Plugins/Android/proguard-rules.pro create mode 100644 RevenueCatUI/Plugins/Android/proguard-rules.pro.meta create mode 100644 RevenueCatUI/Plugins/Android/src.meta create mode 100644 RevenueCatUI/Plugins/Android/src/main.meta create mode 100644 RevenueCatUI/Plugins/Android/src/main/java.meta create mode 100644 RevenueCatUI/Plugins/Android/src/main/java/com.meta create mode 100644 RevenueCatUI/Plugins/Android/src/main/java/com/revenuecat.meta create mode 100644 RevenueCatUI/Plugins/Android/src/main/java/com/revenuecat/unity.meta create mode 100644 RevenueCatUI/Plugins/Android/src/main/java/com/revenuecat/unity/PaywallBridgeActivity.java create mode 100644 RevenueCatUI/Plugins/Android/src/main/java/com/revenuecat/unity/PaywallBridgeActivity.java.meta create mode 100644 RevenueCatUI/Plugins/Android/src/main/java/com/revenuecat/unity/RevenueCatUIPlugin.java create mode 100644 RevenueCatUI/Plugins/Android/src/main/java/com/revenuecat/unity/RevenueCatUIPlugin.java.meta create mode 100644 RevenueCatUI/Plugins/Editor.meta create mode 100644 RevenueCatUI/Plugins/Editor/RevenueCat.UI.Editor.asmdef create mode 100644 RevenueCatUI/Plugins/Editor/RevenueCat.UI.Editor.asmdef.meta create mode 100644 RevenueCatUI/Plugins/Editor/RevenueCatUIDependencies.xml create mode 100644 RevenueCatUI/Plugins/Editor/RevenueCatUIDependencies.xml.meta create mode 100644 RevenueCatUI/Plugins/iOS.meta create mode 100644 RevenueCatUI/Plugins/iOS/RevenueCatUIBridge.m create mode 100644 RevenueCatUI/Plugins/iOS/RevenueCatUIBridge.m.meta create mode 100644 RevenueCatUI/Plugins/iOS/RevenueCatUIDummy.swift create mode 100644 RevenueCatUI/Plugins/iOS/RevenueCatUIDummy.swift.meta create mode 100644 RevenueCatUI/README.md create mode 100644 RevenueCatUI/README.md.meta create mode 100644 RevenueCatUI/Resources.meta create mode 100644 RevenueCatUI/Runtime.meta create mode 100644 RevenueCatUI/Runtime/Examples.meta create mode 100644 RevenueCatUI/Runtime/Examples/RevenueCatUICompleteExample.cs create mode 100644 RevenueCatUI/Runtime/Examples/RevenueCatUICompleteExample.cs.meta create mode 100644 RevenueCatUI/Runtime/Examples/RevenueCatUIExample.cs create mode 100644 RevenueCatUI/Runtime/Examples/RevenueCatUIExample.cs.meta create mode 100644 RevenueCatUI/Runtime/Internal.meta create mode 100644 RevenueCatUI/Runtime/Internal/CustomerCenterPresenter.cs create mode 100644 RevenueCatUI/Runtime/Internal/CustomerCenterPresenter.cs.meta create mode 100644 RevenueCatUI/Runtime/Internal/ICustomerCenterPresenter.cs create mode 100644 RevenueCatUI/Runtime/Internal/ICustomerCenterPresenter.cs.meta create mode 100644 RevenueCatUI/Runtime/Internal/IPaywallPresenter.cs create mode 100644 RevenueCatUI/Runtime/Internal/IPaywallPresenter.cs.meta create mode 100644 RevenueCatUI/Runtime/Internal/JsonUtility.cs create mode 100644 RevenueCatUI/Runtime/Internal/JsonUtility.cs.meta create mode 100644 RevenueCatUI/Runtime/Internal/PaywallPresenter.cs create mode 100644 RevenueCatUI/Runtime/Internal/PaywallPresenter.cs.meta create mode 100644 RevenueCatUI/Runtime/Internal/RevenueCatUICallbackHandler.cs create mode 100644 RevenueCatUI/Runtime/Internal/RevenueCatUICallbackHandler.cs.meta create mode 100644 RevenueCatUI/Runtime/PaywallOptions.cs create mode 100644 RevenueCatUI/Runtime/PaywallOptions.cs.meta create mode 100644 RevenueCatUI/Runtime/PaywallResult.cs create mode 100644 RevenueCatUI/Runtime/PaywallResult.cs.meta create mode 100644 RevenueCatUI/Runtime/Platforms.meta create mode 100644 RevenueCatUI/Runtime/Platforms/AndroidCustomerCenterPresenter.cs create mode 100644 RevenueCatUI/Runtime/Platforms/AndroidCustomerCenterPresenter.cs.meta create mode 100644 RevenueCatUI/Runtime/Platforms/AndroidPaywallPresenter.cs create mode 100644 RevenueCatUI/Runtime/Platforms/AndroidPaywallPresenter.cs.meta create mode 100644 RevenueCatUI/Runtime/Platforms/Stub.meta create mode 100644 RevenueCatUI/Runtime/Platforms/Stub/StubCustomerCenterPresenter.cs create mode 100644 RevenueCatUI/Runtime/Platforms/Stub/StubCustomerCenterPresenter.cs.meta create mode 100644 RevenueCatUI/Runtime/Platforms/Stub/StubPaywallPresenter.cs create mode 100644 RevenueCatUI/Runtime/Platforms/Stub/StubPaywallPresenter.cs.meta create mode 100644 RevenueCatUI/Runtime/Platforms/iOS.meta create mode 100644 RevenueCatUI/Runtime/Platforms/iOS/IOSCustomerCenterPresenter.cs create mode 100644 RevenueCatUI/Runtime/Platforms/iOS/IOSCustomerCenterPresenter.cs.meta create mode 100644 RevenueCatUI/Runtime/Platforms/iOS/IOSPaywallPresenter.cs create mode 100644 RevenueCatUI/Runtime/Platforms/iOS/IOSPaywallPresenter.cs.meta create mode 100644 RevenueCatUI/Runtime/RevenueCat.UI.asmdef create mode 100644 RevenueCatUI/Runtime/RevenueCat.UI.asmdef.meta create mode 100644 RevenueCatUI/Runtime/RevenueCatUI.cs create mode 100644 RevenueCatUI/Runtime/RevenueCatUI.cs.meta create mode 100644 RevenueCatUI/UI.meta create mode 100644 RevenueCatUI/package.json create mode 100644 RevenueCatUI/package.json.meta create mode 100644 Subtester/Assets/Scripts/RevenueCatUITestButtons.cs create mode 100644 Subtester/Assets/Scripts/RevenueCatUITestButtons.cs.meta create mode 100644 Subtester/UserSettings/Layouts/default-2021.dwlt diff --git a/RevenueCat/Scripts/PurchasesWrapperAndroid.cs b/RevenueCat/Scripts/PurchasesWrapperAndroid.cs index 23f40e1a..7fcf67df 100644 --- a/RevenueCat/Scripts/PurchasesWrapperAndroid.cs +++ b/RevenueCat/Scripts/PurchasesWrapperAndroid.cs @@ -442,5 +442,6 @@ private static ReturnType CallPurchases(string methodName, params ob return purchases.CallStatic(methodName, args); } } + } #endif diff --git a/RevenueCatUI/Editor.meta b/RevenueCatUI/Editor.meta new file mode 100644 index 00000000..6af1502d --- /dev/null +++ b/RevenueCatUI/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 0629f5006c10940828fdfc2b9e3e2bf2 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/RevenueCatUI/Plugins.meta b/RevenueCatUI/Plugins.meta new file mode 100644 index 00000000..87a8a2ee --- /dev/null +++ b/RevenueCatUI/Plugins.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ebbdc005f0bc94b24be9b3d15f2d58db +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/RevenueCatUI/Plugins/Android.meta b/RevenueCatUI/Plugins/Android.meta new file mode 100644 index 00000000..80c4953e --- /dev/null +++ b/RevenueCatUI/Plugins/Android.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 6b10b69268c67402da4a4d5750cd5ffa +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/RevenueCatUI/Plugins/Android/AndroidManifest.xml b/RevenueCatUI/Plugins/Android/AndroidManifest.xml new file mode 100644 index 00000000..4a790bb9 --- /dev/null +++ b/RevenueCatUI/Plugins/Android/AndroidManifest.xml @@ -0,0 +1,10 @@ + + + + + + + \ No newline at end of file diff --git a/RevenueCatUI/Plugins/Android/AndroidManifest.xml.meta b/RevenueCatUI/Plugins/Android/AndroidManifest.xml.meta new file mode 100644 index 00000000..cb3a1478 --- /dev/null +++ b/RevenueCatUI/Plugins/Android/AndroidManifest.xml.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 6b5f674826a2d4d87b06c76c6af9ebdb +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/RevenueCatUI/Plugins/Android/build.gradle b/RevenueCatUI/Plugins/Android/build.gradle new file mode 100644 index 00000000..ec280c8b --- /dev/null +++ b/RevenueCatUI/Plugins/Android/build.gradle @@ -0,0 +1,25 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion 33 + defaultConfig { + minSdkVersion 21 + targetSdkVersion 33 + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +dependencies { + // Use hybrid-common-ui aligned with core PurchasesHybridCommon version + implementation 'com.revenuecat.purchases:purchases-hybrid-common-ui:[17.0.0]' + + // AppCompat for the bridge activity + implementation 'androidx.appcompat:appcompat:1.6.1' + + // Fragment support for the bridge pattern + implementation 'androidx.fragment:fragment:1.6.2' +} diff --git a/RevenueCatUI/Plugins/Android/build.gradle.meta b/RevenueCatUI/Plugins/Android/build.gradle.meta new file mode 100644 index 00000000..cf26f23c --- /dev/null +++ b/RevenueCatUI/Plugins/Android/build.gradle.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 285ca726e6cfc4f029c1d50052d6d9d3 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/RevenueCatUI/Plugins/Android/proguard-rules.pro b/RevenueCatUI/Plugins/Android/proguard-rules.pro new file mode 100644 index 00000000..9d3d59d4 --- /dev/null +++ b/RevenueCatUI/Plugins/Android/proguard-rules.pro @@ -0,0 +1,12 @@ +# Keep RevenueCat UI Unity plugin classes +-keep class com.revenuecat.purchases.ui.unity.** { *; } + +# Keep RevenueCat classes that we depend on +-keep class com.revenuecat.purchases.** { *; } +-keep class com.revenuecat.purchases.hybridcommon.** { *; } + +# Keep Unity player classes +-keep class com.unity3d.player.** { *; } + +# Keep AndroidX Fragment classes +-keep class androidx.fragment.** { *; } \ No newline at end of file diff --git a/RevenueCatUI/Plugins/Android/proguard-rules.pro.meta b/RevenueCatUI/Plugins/Android/proguard-rules.pro.meta new file mode 100644 index 00000000..4de5efc1 --- /dev/null +++ b/RevenueCatUI/Plugins/Android/proguard-rules.pro.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 17bdc4ea2e6df465f8dff3ab423da4ae +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/RevenueCatUI/Plugins/Android/src.meta b/RevenueCatUI/Plugins/Android/src.meta new file mode 100644 index 00000000..e7ba918d --- /dev/null +++ b/RevenueCatUI/Plugins/Android/src.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 58f0e6ae8c5614cafbaabc7e8eea30f9 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/RevenueCatUI/Plugins/Android/src/main.meta b/RevenueCatUI/Plugins/Android/src/main.meta new file mode 100644 index 00000000..c9fbbc2f --- /dev/null +++ b/RevenueCatUI/Plugins/Android/src/main.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7d22fa946b31b46dbbce37b55960c4fa +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/RevenueCatUI/Plugins/Android/src/main/java.meta b/RevenueCatUI/Plugins/Android/src/main/java.meta new file mode 100644 index 00000000..5eefe323 --- /dev/null +++ b/RevenueCatUI/Plugins/Android/src/main/java.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d802b640849df4118b4cf2c315d23570 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/RevenueCatUI/Plugins/Android/src/main/java/com.meta b/RevenueCatUI/Plugins/Android/src/main/java/com.meta new file mode 100644 index 00000000..e25de037 --- /dev/null +++ b/RevenueCatUI/Plugins/Android/src/main/java/com.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: eb85ab56e19354607a0a5896d7d08885 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/RevenueCatUI/Plugins/Android/src/main/java/com/revenuecat.meta b/RevenueCatUI/Plugins/Android/src/main/java/com/revenuecat.meta new file mode 100644 index 00000000..f3b2d6e1 --- /dev/null +++ b/RevenueCatUI/Plugins/Android/src/main/java/com/revenuecat.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d85f5936727a84c5e87378f1bfe2e414 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/RevenueCatUI/Plugins/Android/src/main/java/com/revenuecat/unity.meta b/RevenueCatUI/Plugins/Android/src/main/java/com/revenuecat/unity.meta new file mode 100644 index 00000000..287ca030 --- /dev/null +++ b/RevenueCatUI/Plugins/Android/src/main/java/com/revenuecat/unity.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 6e5b37958fc5346b2a411128b899edbc +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/RevenueCatUI/Plugins/Android/src/main/java/com/revenuecat/unity/PaywallBridgeActivity.java b/RevenueCatUI/Plugins/Android/src/main/java/com/revenuecat/unity/PaywallBridgeActivity.java new file mode 100644 index 00000000..08b9e22e --- /dev/null +++ b/RevenueCatUI/Plugins/Android/src/main/java/com/revenuecat/unity/PaywallBridgeActivity.java @@ -0,0 +1,183 @@ +package com.revenuecat.unity; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import androidx.appcompat.app.AppCompatActivity; +import com.revenuecat.purchases.ui.revenuecatui.activity.PaywallActivityLauncher; +import com.revenuecat.purchases.ui.revenuecatui.activity.PaywallResultHandler; +import com.revenuecat.purchases.ui.revenuecatui.activity.PaywallResult; +import com.unity3d.player.UnityPlayer; + +/** + * Optional host-wrapper activity that extends AppCompatActivity + * This is used as a fallback when the Unity activity is not a FragmentActivity + * Because it extends AppCompatActivity, it already implements the ActivityResultCaller interface + * that PaywallActivityLauncher needs. + */ +public class PaywallBridgeActivity extends AppCompatActivity { + + public static final String EXTRA_OFFERING_ID = "offering_id"; + public static final String EXTRA_REQUIRED_ENTITLEMENT = "required_entitlement"; + public static final String EXTRA_ONLY_IF_NEEDED = "only_if_needed"; + public static final String EXTRA_IS_CUSTOMER_CENTER = "is_customer_center"; + + private static final String UNITY_CALLBACK_OBJECT = "RevenueCatUI"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Get parameters from intent + String offeringId = getIntent().getStringExtra(EXTRA_OFFERING_ID); + String requiredEntitlement = getIntent().getStringExtra(EXTRA_REQUIRED_ENTITLEMENT); + boolean onlyIfNeeded = getIntent().getBooleanExtra(EXTRA_ONLY_IF_NEEDED, false); + boolean isCustomerCenter = getIntent().getBooleanExtra(EXTRA_IS_CUSTOMER_CENTER, false); + + try { + if (isCustomerCenter) { + // For now, immediately report not implemented to customer center handler + sendCustomerCenterResultToUnity("ERROR|Customer center not yet implemented"); + finish(); + } else { + // Launch RevenueCat paywall + PaywallActivityLauncher launcher = new PaywallActivityLauncher(this, new PaywallResultHandler() { + @Override + public void onActivityResult(PaywallResult result) { + handlePaywallResult(result); + finish(); // Close this bridge activity + } + }); + + // Configure the launcher based on parameters (use reflection for API compatibility) + try { + if (offeringId != null && !offeringId.isEmpty()) { + boolean invoked = false; + try { + PaywallActivityLauncher.class + .getMethod("setOfferingIdentifier", String.class) + .invoke(launcher, offeringId); + invoked = true; + } catch (NoSuchMethodException ignored) {} + if (!invoked) { + try { + PaywallActivityLauncher.class + .getMethod("setOfferingId", String.class) + .invoke(launcher, offeringId); + invoked = true; + } catch (NoSuchMethodException ignored) {} + } + if (!invoked) { + try { + PaywallActivityLauncher.class + .getMethod("setOffering", String.class) + .invoke(launcher, offeringId); + } catch (NoSuchMethodException ignored) {} + } + } + if (onlyIfNeeded && requiredEntitlement != null && !requiredEntitlement.isEmpty()) { + boolean invokedReq = false; + try { + PaywallActivityLauncher.class + .getMethod("setRequiredEntitlementIdentifier", String.class) + .invoke(launcher, requiredEntitlement); + invokedReq = true; + } catch (NoSuchMethodException ignored) {} + if (!invokedReq) { + try { + PaywallActivityLauncher.class + .getMethod("setRequiredEntitlementId", String.class) + .invoke(launcher, requiredEntitlement); + invokedReq = true; + } catch (NoSuchMethodException ignored) {} + } + if (!invokedReq) { + try { + PaywallActivityLauncher.class + .getMethod("setRequiredEntitlement", String.class) + .invoke(launcher, requiredEntitlement); + } catch (NoSuchMethodException ignored) {} + } + } + } catch (Exception reflectionError) { + android.util.Log.w("RevenueCatUI", "Optional configuration via reflection failed: " + reflectionError.getMessage()); + } + + launcher.launch(); // Launch full-screen paywall + } + } catch (Exception e) { + sendResultToUnity("ERROR|Failed to launch paywall: " + e.getMessage()); + finish(); + } + } + + /** + * Handle the result from PaywallActivityLauncher + */ + private void handlePaywallResult(PaywallResult result) { + try { + String resultString; + String simple = result != null ? result.getClass().getSimpleName() : ""; + if ("Purchased".equalsIgnoreCase(simple)) { + resultString = "PURCHASED|Purchase completed successfully"; + } else if ("Cancelled".equalsIgnoreCase(simple) || "Canceled".equalsIgnoreCase(simple)) { + resultString = "CANCELLED|Paywall was cancelled by user"; + } else if ("Restored".equalsIgnoreCase(simple)) { + resultString = "RESTORED|Purchases restored successfully"; + } else if ("NotPresented".equalsIgnoreCase(simple) || "NotShown".equalsIgnoreCase(simple)) { + resultString = "NOT_PRESENTED|Paywall was not needed"; + } else { + // Fallback: try toString pattern matching + String asText = String.valueOf(result); + if (asText.toLowerCase().contains("purchase")) { + resultString = "PURCHASED|" + asText; + } else if (asText.toLowerCase().contains("cancel")) { + resultString = "CANCELLED|" + asText; + } else if (asText.toLowerCase().contains("restore")) { + resultString = "RESTORED|" + asText; + } else if (asText.toLowerCase().contains("not") && asText.toLowerCase().contains("present")) { + resultString = "NOT_PRESENTED|" + asText; + } else { + resultString = "ERROR|Unknown result: " + asText; + } + } + sendResultToUnity(resultString); + } catch (Exception e) { + sendResultToUnity("ERROR|Failed to handle paywall result: " + e.getMessage()); + } + } + + /** + * Send result callback to Unity + */ + private void sendResultToUnity(String result) { + try { + UnityPlayer.UnitySendMessage(UNITY_CALLBACK_OBJECT, "OnPaywallResult", result); + } catch (Exception e) { + // If callback fails, at least log it + android.util.Log.e("RevenueCatUI", "Failed to send result to Unity: " + e.getMessage()); + } + } + + private void sendCustomerCenterResultToUnity(String result) { + try { + UnityPlayer.UnitySendMessage(UNITY_CALLBACK_OBJECT, "OnCustomerCenterResult", result); + } catch (Exception e) { + android.util.Log.e("RevenueCatUI", "Failed to send CC result to Unity: " + e.getMessage()); + } + } + + /** + * Static method to launch this bridge activity from Unity + * @deprecated This method is kept for backward compatibility but the new bridge pattern + * should be used instead through RevenueCatUIPlugin.presentPaywall() + */ + @Deprecated + public static void launch(Activity fromActivity, String offeringId, String requiredEntitlement, int requestCode) { + Intent intent = new Intent(fromActivity, PaywallBridgeActivity.class); + intent.putExtra(EXTRA_OFFERING_ID, offeringId); + intent.putExtra(EXTRA_REQUIRED_ENTITLEMENT, requiredEntitlement); + intent.putExtra(EXTRA_ONLY_IF_NEEDED, false); + fromActivity.startActivityForResult(intent, requestCode); + } +} \ No newline at end of file diff --git a/RevenueCatUI/Plugins/Android/src/main/java/com/revenuecat/unity/PaywallBridgeActivity.java.meta b/RevenueCatUI/Plugins/Android/src/main/java/com/revenuecat/unity/PaywallBridgeActivity.java.meta new file mode 100644 index 00000000..a77e9cd3 --- /dev/null +++ b/RevenueCatUI/Plugins/Android/src/main/java/com/revenuecat/unity/PaywallBridgeActivity.java.meta @@ -0,0 +1,32 @@ +fileFormatVersion: 2 +guid: 3de0cb42d4cef4f0684501fe75872e3a +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 1 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + Android: Android + second: + enabled: 1 + settings: {} + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + userData: + assetBundleName: + assetBundleVariant: diff --git a/RevenueCatUI/Plugins/Android/src/main/java/com/revenuecat/unity/RevenueCatUIPlugin.java b/RevenueCatUI/Plugins/Android/src/main/java/com/revenuecat/unity/RevenueCatUIPlugin.java new file mode 100644 index 00000000..30b9a71f --- /dev/null +++ b/RevenueCatUI/Plugins/Android/src/main/java/com/revenuecat/unity/RevenueCatUIPlugin.java @@ -0,0 +1,135 @@ +package com.revenuecat.unity; + +import android.app.Activity; +import android.content.Intent; +import com.unity3d.player.UnityPlayer; +import com.revenuecat.purchases.ui.revenuecatui.activity.PaywallResult; + +public class RevenueCatUIPlugin { + + private static final String UNITY_CALLBACK_OBJECT = "RevenueCatUI"; + + /** + * Shows RevenueCat paywall using the new bridge pattern + * Accepts Activity (Unity's UnityPlayerActivity) and casts to FragmentActivity when possible + * @param requiredEntitlementIdentifier The required entitlement identifier (optional, can be empty) + * @param offeringIdentifier The offering identifier to display (optional, can be empty) + */ + public static void presentPaywall(String requiredEntitlementIdentifier, String offeringIdentifier) { + Activity activity = UnityPlayer.currentActivity; + if (activity == null) { + sendResultToUnity("ERROR|Unity activity is null"); + return; + } + + // Always use bridge activity to guarantee Activity Result registration occurs before STARTED + startBridgeActivity(activity, requiredEntitlementIdentifier, offeringIdentifier, false, false); + } + + /** + * Shows RevenueCat paywall if needed using the new bridge pattern + * @param requiredEntitlementIdentifier The required entitlement identifier + * @param offeringIdentifier The offering identifier to display (optional, can be empty) + */ + public static void presentPaywallIfNeeded(String requiredEntitlementIdentifier, String offeringIdentifier) { + Activity activity = UnityPlayer.currentActivity; + if (activity == null) { + sendResultToUnity("ERROR|Unity activity is null"); + return; + } + + // Always use bridge activity + startBridgeActivity(activity, requiredEntitlementIdentifier, offeringIdentifier, true, false); + } + + /** + * Shows customer center using the new bridge pattern + */ + public static void presentCustomerCenter() { + Activity activity = UnityPlayer.currentActivity; + if (activity == null) { + sendResultToUnity("ERROR|Unity activity is null"); + return; + } + + startBridgeActivity(activity, null, null, false, true); + } + + /** + * Check if RevenueCat UI is supported + * @return true if supported + */ + public static boolean isSupported() { + return true; // RevenueCat UI is available with hybrid-common-ui + } + + /** + * Internal method that implements the bridge pattern + * At compile-time we treat the parameter as Activity, but at run-time the object + * Unity passes in is still a UnityPlayerActivity instance. + */ + private static void startBridgeActivity(Activity activity, String requiredEntitlementIdentifier, String offeringIdentifier, boolean onlyIfNeeded, boolean isCustomerCenter) { + try { + Intent intent = new Intent(activity, PaywallBridgeActivity.class); + if (offeringIdentifier != null) intent.putExtra(PaywallBridgeActivity.EXTRA_OFFERING_ID, offeringIdentifier); + if (requiredEntitlementIdentifier != null) intent.putExtra(PaywallBridgeActivity.EXTRA_REQUIRED_ENTITLEMENT, requiredEntitlementIdentifier); + intent.putExtra(PaywallBridgeActivity.EXTRA_ONLY_IF_NEEDED, onlyIfNeeded); + intent.putExtra(PaywallBridgeActivity.EXTRA_IS_CUSTOMER_CENTER, isCustomerCenter); + activity.startActivity(intent); + } catch (Exception e) { + sendResultToUnity("ERROR|Failed to present paywall: " + e.getMessage()); + } + } + + /** + * Internal method for showing customer center + */ + // Customer center uses the same bridge method (isCustomerCenter=true) + + /** + * Handle the result from PaywallActivityLauncher + */ + private static void handlePaywallResult(PaywallResult result) { + try { + String resultString; + String simple = result != null ? result.getClass().getSimpleName() : ""; + if ("Purchased".equalsIgnoreCase(simple)) { + resultString = "PURCHASED|Purchase completed successfully"; + } else if ("Cancelled".equalsIgnoreCase(simple) || "Canceled".equalsIgnoreCase(simple)) { + resultString = "CANCELLED|Paywall was cancelled by user"; + } else if ("Restored".equalsIgnoreCase(simple)) { + resultString = "RESTORED|Purchases restored successfully"; + } else if ("NotPresented".equalsIgnoreCase(simple) || "NotShown".equalsIgnoreCase(simple)) { + resultString = "NOT_PRESENTED|Paywall was not needed"; + } else { + String asText = String.valueOf(result); + if (asText.toLowerCase().contains("purchase")) { + resultString = "PURCHASED|" + asText; + } else if (asText.toLowerCase().contains("cancel")) { + resultString = "CANCELLED|" + asText; + } else if (asText.toLowerCase().contains("restore")) { + resultString = "RESTORED|" + asText; + } else if (asText.toLowerCase().contains("not") && asText.toLowerCase().contains("present")) { + resultString = "NOT_PRESENTED|" + asText; + } else { + resultString = "ERROR|Unknown result: " + asText; + } + } + sendResultToUnity(resultString); + } catch (Exception e) { + sendResultToUnity("ERROR|Failed to handle paywall result: " + e.getMessage()); + } + } + + /** + * Send result callback to Unity + */ + private static void sendResultToUnity(String result) { + try { + UnityPlayer.UnitySendMessage(UNITY_CALLBACK_OBJECT, "OnPaywallResult", result); + } catch (Exception e) { + // If callback fails, at least log it + android.util.Log.e("RevenueCatUI", "Failed to send result to Unity: " + e.getMessage()); + } + } +} \ No newline at end of file diff --git a/RevenueCatUI/Plugins/Android/src/main/java/com/revenuecat/unity/RevenueCatUIPlugin.java.meta b/RevenueCatUI/Plugins/Android/src/main/java/com/revenuecat/unity/RevenueCatUIPlugin.java.meta new file mode 100644 index 00000000..987008aa --- /dev/null +++ b/RevenueCatUI/Plugins/Android/src/main/java/com/revenuecat/unity/RevenueCatUIPlugin.java.meta @@ -0,0 +1,32 @@ +fileFormatVersion: 2 +guid: 85c9d3acf58334392bacc9b3a9e53d2a +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 1 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + Android: Android + second: + enabled: 1 + settings: {} + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + userData: + assetBundleName: + assetBundleVariant: diff --git a/RevenueCatUI/Plugins/Editor.meta b/RevenueCatUI/Plugins/Editor.meta new file mode 100644 index 00000000..0c111ea8 --- /dev/null +++ b/RevenueCatUI/Plugins/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d2b81261d59c84f56a239b8403da7987 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/RevenueCatUI/Plugins/Editor/RevenueCat.UI.Editor.asmdef b/RevenueCatUI/Plugins/Editor/RevenueCat.UI.Editor.asmdef new file mode 100644 index 00000000..1e0902f3 --- /dev/null +++ b/RevenueCatUI/Plugins/Editor/RevenueCat.UI.Editor.asmdef @@ -0,0 +1,18 @@ +{ + "name": "RevenueCat.UI.Editor", + "rootNamespace": "RevenueCat.UI.Editor", + "references": [ + "RevenueCat.UI" + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": false, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/RevenueCatUI/Plugins/Editor/RevenueCat.UI.Editor.asmdef.meta b/RevenueCatUI/Plugins/Editor/RevenueCat.UI.Editor.asmdef.meta new file mode 100644 index 00000000..305f1ec4 --- /dev/null +++ b/RevenueCatUI/Plugins/Editor/RevenueCat.UI.Editor.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: c4fed84d98342472aa13425ca9ece010 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/RevenueCatUI/Plugins/Editor/RevenueCatUIDependencies.xml b/RevenueCatUI/Plugins/Editor/RevenueCatUIDependencies.xml new file mode 100644 index 00000000..90a2826a --- /dev/null +++ b/RevenueCatUI/Plugins/Editor/RevenueCatUIDependencies.xml @@ -0,0 +1,36 @@ + + + + + + + https://s01.oss.sonatype.org/content/repositories/snapshots/ + + + + + + + + https://maven.google.com + + + + + https://maven.google.com + + + + + https://maven.google.com + + + + + + + + + + + diff --git a/RevenueCatUI/Plugins/Editor/RevenueCatUIDependencies.xml.meta b/RevenueCatUI/Plugins/Editor/RevenueCatUIDependencies.xml.meta new file mode 100644 index 00000000..d1c9b288 --- /dev/null +++ b/RevenueCatUI/Plugins/Editor/RevenueCatUIDependencies.xml.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: d9d7440f253ef433eabdabb8b8f04046 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/RevenueCatUI/Plugins/iOS.meta b/RevenueCatUI/Plugins/iOS.meta new file mode 100644 index 00000000..bfee2745 --- /dev/null +++ b/RevenueCatUI/Plugins/iOS.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7409590fc13bc435ca37a81a3964c912 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/RevenueCatUI/Plugins/iOS/RevenueCatUIBridge.m b/RevenueCatUI/Plugins/iOS/RevenueCatUIBridge.m new file mode 100644 index 00000000..961329c4 --- /dev/null +++ b/RevenueCatUI/Plugins/iOS/RevenueCatUIBridge.m @@ -0,0 +1,166 @@ +// +// RevenueCatUIBridge.m +// RevenueCat Unity UI Plugin +// +// Created for Unity SDK +// + +#import +#import +@import PurchasesHybridCommonUI; + +// Global callback storage +static void (*paywallResultCallback)(const char* result) = NULL; +static void (*customerCenterCallback)(void) = NULL; + +// Global proxy instances +static PaywallProxy *paywallProxy = nil; +static CustomerCenterProxy *customerCenterProxy = nil; + +#pragma mark - External Utility Methods (from core RevenueCat plugin) + +// These utility functions are already defined in PurchasesUnityHelper.m +// We declare them as external to use the existing implementations +extern NSString *convertCString(const char *string); +extern char *makeStringCopy(NSString *nstring); + +#pragma mark - Initialization + +void initializeRevenueCatUI() { + if (@available(iOS 15.0, *)) { + if (!paywallProxy) { + paywallProxy = [[PaywallProxy alloc] init]; + } + if (!customerCenterProxy) { + customerCenterProxy = [[CustomerCenterProxy alloc] init]; + } + NSLog(@"RevenueCat UI initialized successfully"); + } else { + NSLog(@"RevenueCat UI requires iOS 15.0 or later"); + } +} + +#pragma mark - Paywall Methods + +void presentPaywall(const char* offeringIdentifier, bool displayCloseButton, void (*callback)(const char*)) { + if (@available(iOS 15.0, *)) { + if (!paywallProxy) { + initializeRevenueCatUI(); + } + + // Store the callback + paywallResultCallback = callback; + + // Create options dictionary + NSMutableDictionary *options = [[NSMutableDictionary alloc] init]; + + if (offeringIdentifier && strlen(offeringIdentifier) > 0) { + options[@"offeringIdentifier"] = convertCString(offeringIdentifier); + } + + options[@"displayCloseButton"] = @(displayCloseButton); + options[@"shouldBlockTouchEvents"] = @(YES); // Needed for Unity integration + + // Present paywall with result handler + [paywallProxy presentPaywallWithOptions:options paywallResultHandler:^(NSString *result) { + if (paywallResultCallback) { + paywallResultCallback([result UTF8String]); + paywallResultCallback = NULL; + } + }]; + + NSLog(@"Presenting paywall with offering: %s, displayCloseButton: %d", + offeringIdentifier ? offeringIdentifier : "default", displayCloseButton); + } else { + NSLog(@"Presenting paywall requires iOS 15.0 or later"); + if (callback) { + callback("{\"error\": \"iOS 15.0 required\"}"); + } + } +} + +void presentPaywallIfNeeded(const char* requiredEntitlementIdentifier, const char* offeringIdentifier, bool displayCloseButton, void (*callback)(const char*)) { + if (@available(iOS 15.0, *)) { + if (!paywallProxy) { + initializeRevenueCatUI(); + } + + // Store the callback + paywallResultCallback = callback; + + // Create options dictionary + NSMutableDictionary *options = [[NSMutableDictionary alloc] init]; + + if (requiredEntitlementIdentifier && strlen(requiredEntitlementIdentifier) > 0) { + options[@"requiredEntitlementIdentifier"] = convertCString(requiredEntitlementIdentifier); + } else { + NSLog(@"Error: requiredEntitlementIdentifier is required for presentPaywallIfNeeded"); + if (callback) { + callback("{\"error\": \"requiredEntitlementIdentifier required\"}"); + } + return; + } + + if (offeringIdentifier && strlen(offeringIdentifier) > 0) { + options[@"offeringIdentifier"] = convertCString(offeringIdentifier); + } + + options[@"displayCloseButton"] = @(displayCloseButton); + options[@"shouldBlockTouchEvents"] = @(YES); // Needed for Unity integration + + // Present paywall if needed with result handler + [paywallProxy presentPaywallIfNeededWithOptions:options paywallResultHandler:^(NSString *result) { + if (paywallResultCallback) { + paywallResultCallback([result UTF8String]); + paywallResultCallback = NULL; + } + }]; + + NSLog(@"Presenting paywall if needed for entitlement: %s, offering: %s, displayCloseButton: %d", + requiredEntitlementIdentifier, + offeringIdentifier ? offeringIdentifier : "default", + displayCloseButton); + } else { + NSLog(@"Presenting paywall if needed requires iOS 15.0 or later"); + if (callback) { + callback("{\"error\": \"iOS 15.0 required\"}"); + } + } +} + +#pragma mark - Customer Center Methods + +void presentCustomerCenter(void (*callback)(void)) { + if (@available(iOS 15.0, *)) { + if (!customerCenterProxy) { + initializeRevenueCatUI(); + } + + // Store the callback + customerCenterCallback = callback; + + // Present customer center with result handler + [customerCenterProxy presentWithResultHandler:^{ + if (customerCenterCallback) { + customerCenterCallback(); + customerCenterCallback = NULL; + } + }]; + + NSLog(@"Presenting customer center"); + } else { + NSLog(@"Presenting customer center requires iOS 15.0 or later"); + if (callback) { + callback(); + } + } +} + +#pragma mark - Support Methods + +bool isRevenueCatUISupported() { + if (@available(iOS 15.0, *)) { + return [PaywallProxy class] != nil && [CustomerCenterProxy class] != nil; + } + return false; +} \ No newline at end of file diff --git a/RevenueCatUI/Plugins/iOS/RevenueCatUIBridge.m.meta b/RevenueCatUI/Plugins/iOS/RevenueCatUIBridge.m.meta new file mode 100644 index 00000000..cb90cd66 --- /dev/null +++ b/RevenueCatUI/Plugins/iOS/RevenueCatUIBridge.m.meta @@ -0,0 +1,37 @@ +fileFormatVersion: 2 +guid: 3ced81200d83a4db49a3e40ec42d8c4e +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 1 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + - first: + iPhone: iOS + second: + enabled: 1 + settings: {} + - first: + tvOS: tvOS + second: + enabled: 1 + settings: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/RevenueCatUI/Plugins/iOS/RevenueCatUIDummy.swift b/RevenueCatUI/Plugins/iOS/RevenueCatUIDummy.swift new file mode 100644 index 00000000..6ead7c38 --- /dev/null +++ b/RevenueCatUI/Plugins/iOS/RevenueCatUIDummy.swift @@ -0,0 +1,9 @@ +// +// RevenueCatUIDummy.swift +// RevenueCat Unity UI Plugin +// +// Required for Swift framework support in Unity +// + +// This file is intentionally empty. +// It exists to enable Swift support for PurchasesHybridCommonUI framework in Unity iOS builds. \ No newline at end of file diff --git a/RevenueCatUI/Plugins/iOS/RevenueCatUIDummy.swift.meta b/RevenueCatUI/Plugins/iOS/RevenueCatUIDummy.swift.meta new file mode 100644 index 00000000..fc388314 --- /dev/null +++ b/RevenueCatUI/Plugins/iOS/RevenueCatUIDummy.swift.meta @@ -0,0 +1,37 @@ +fileFormatVersion: 2 +guid: f8d93b2e4c7a4d6a9b1c8e5f0a3b6c9d +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + - first: + iPhone: iOS + second: + enabled: 1 + settings: {} + - first: + tvOS: tvOS + second: + enabled: 1 + settings: {} + userData: + assetBundleName: + assetBundleVariant: \ No newline at end of file diff --git a/RevenueCatUI/README.md b/RevenueCatUI/README.md new file mode 100644 index 00000000..ba99e0d6 --- /dev/null +++ b/RevenueCatUI/README.md @@ -0,0 +1,194 @@ +# RevenueCat UI for Unity + +Unity UI components (paywalls and customer center) for RevenueCat's subscription management. + +## Features + +- 🎨 **Native Paywalls**: Present beautiful, native paywalls +- 🏪 **Customer Center**: Manage subscriptions and customer support +- 🔄 **Unity Integration**: Clean, async/await APIs following Unity conventions +- 📱 **Mobile-First**: iOS and Android support +- 🎯 **Production Ready**: Built on proven purchases-hybrid-common-ui + +## Installation + +### Option 1: Git URL (Development Branch) + +Since this package is currently on the `ui-test_v1` branch, install via: + +``` +https://github.com/RevenueCat/purchases-unity.git?path=RevenueCatUI#ui-test_v1 +``` + +### Option 2: Git URL (After Merge to Main) + +Once merged to main, you can use: + +``` +https://github.com/RevenueCat/purchases-unity.git?path=RevenueCatUI +``` + +### Requirements + +- **Unity 2022.3 LTS or later** +- **RevenueCat Unity SDK** - This package depends on the main RevenueCat package +- **iOS 15.0+** - Required for paywall presentation +- **Android API 24+** - Required for UI components + +### Dependencies + +This package automatically includes: +- **iOS**: `PurchasesHybridCommonUI` via CocoaPods +- **Android**: `purchases-hybrid-common-ui` via Gradle + +Dependencies are managed by Unity's External Dependency Manager. + +## Quick Start + +```csharp +using RevenueCat.UI; + +public class MySubscriptionManager : MonoBehaviour +{ + public async void ShowPaywall() + { + if (!RevenueCatUI.IsSupported()) + { + Debug.LogWarning("RevenueCat UI not supported on this platform"); + return; + } + + try + { + var result = await RevenueCatUI.PresentPaywall(); + + switch (result.Result) + { + case PaywallResultType.Purchased: + Debug.Log("User made a purchase!"); + break; + case PaywallResultType.Cancelled: + Debug.Log("User cancelled"); + break; + } + } + catch (System.Exception e) + { + Debug.LogError($"Paywall error: {e.Message}"); + } + } + + public async void ShowCustomerCenter() + { + if (RevenueCatUI.IsSupported()) + { + await RevenueCatUI.PresentCustomerCenter(); + } + } +} +``` + +## API Reference + +### RevenueCatUI (Main API) + +| Method | Description | +|--------|-------------| +| `PresentPaywall()` | Present paywall with default offering | +| `PresentPaywall(PaywallOptions)` | Present paywall with specific options | +| `PresentPaywallIfNeeded(string)` | Present paywall only if user lacks entitlement | +| `PresentPaywallIfNeeded(string, PaywallOptions)` | Conditional paywall with options | +| `PresentCustomerCenter()` | Present customer center | +| `IsSupported()` | Check if UI is supported on current platform | + +### PaywallOptions + +```csharp +var options = new PaywallOptions +{ + OfferingIdentifier = "premium", // Optional: specific offering + DisplayCloseButton = true // Show close button (iOS only) +}; +``` + +### PaywallResult + +```csharp +public enum PaywallResultType +{ + NotPresented, // User already has entitlement + Cancelled, // User cancelled + Error, // An error occurred + Purchased, // User made a purchase + Restored // User restored purchases +} +``` + +## Architecture + +### iOS Implementation +- **Native Bridge**: `RevenueCatUIBridge.mm` (Objective-C++) +- **Dependencies**: `PurchasesHybridCommonUI` via External Dependency Manager +- **Minimum Version**: iOS 15.0 (required for paywall presentation) + +### Android Implementation +- **Java Bridge**: `RevenueCatUIPlugin.java` +- **Dependencies**: `purchases-hybrid-common-ui` via Gradle +- **Minimum Version**: API 24 (Android 7.0) + +### Platform Abstraction +- Factory pattern with `IPaywallPresenter` and `ICustomerCenterPresenter` +- Graceful fallbacks for unsupported platforms +- Unity-style async/await throughout + +## Platform Support + +| Platform | Paywalls | Customer Center | Notes | +|----------|----------|----------------|-------| +| iOS | ✅ | ✅ | Requires iOS 15.0+ | +| Android | ✅ | ✅ | Requires API 24+ | +| Editor | ❌ | ❌ | Logs warnings only | +| Other | ❌ | ❌ | Graceful fallbacks | + +## Examples + +See `RevenueCatUIExample.cs` for complete examples including: +- Basic paywall presentation +- Conditional paywalls based on entitlements +- Customer center presentation +- Error handling and result processing +- Integration with RevenueCat SDK + +## Troubleshooting + +### iOS Issues +- Ensure Xcode project has iOS 15.0+ deployment target +- Verify CocoaPods dependencies are resolved +- Check that `PurchasesHybridCommonUI` is included + +### Android Issues +- Verify Unity activity extends `FragmentActivity` +- Check that `purchases-hybrid-common-ui` is in dependencies +- Ensure minimum API level 24 + +### General +- Call `RevenueCatUI.IsSupported()` before presenting UI +- Initialize RevenueCat SDK before using UI components +- Check Unity console for debug logs with `[RevenueCatUI]` prefix + +## Stub Mode (No Native Dependencies) + +To integrate the API without pulling native UI dependencies yet, enable stub mode: + +- Add scripting define symbol `REVENUECAT_UI_STUBS` in Unity Player Settings. +- Behavior in stub mode: + - `PresentPaywall()` returns `Cancelled` immediately. + - `PresentPaywallIfNeeded(...)` returns `NotNeeded` immediately. + - `PresentCustomerCenter()` completes immediately. + - `IsSupported()` returns `true` so you can wire flows. + +Remove the define once you are ready to resolve Android/iOS dependencies and ship the real UI. + +## License + +This package follows the same license as the main RevenueCat Unity SDK. diff --git a/RevenueCatUI/README.md.meta b/RevenueCatUI/README.md.meta new file mode 100644 index 00000000..b033aee6 --- /dev/null +++ b/RevenueCatUI/README.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 4181f8939ea324c7f8014d9ec9d6a417 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/RevenueCatUI/Resources.meta b/RevenueCatUI/Resources.meta new file mode 100644 index 00000000..94cbd6c3 --- /dev/null +++ b/RevenueCatUI/Resources.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 9dd24195234294a83bca312d81fbf71e +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/RevenueCatUI/Runtime.meta b/RevenueCatUI/Runtime.meta new file mode 100644 index 00000000..3ba050bb --- /dev/null +++ b/RevenueCatUI/Runtime.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 1d689e53bae3041f297355e1618b78df +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/RevenueCatUI/Runtime/Examples.meta b/RevenueCatUI/Runtime/Examples.meta new file mode 100644 index 00000000..3c1efe57 --- /dev/null +++ b/RevenueCatUI/Runtime/Examples.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: af8cddb254f094f3a984c7cdd42000cd +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/RevenueCatUI/Runtime/Examples/RevenueCatUICompleteExample.cs b/RevenueCatUI/Runtime/Examples/RevenueCatUICompleteExample.cs new file mode 100644 index 00000000..7b6e3800 --- /dev/null +++ b/RevenueCatUI/Runtime/Examples/RevenueCatUICompleteExample.cs @@ -0,0 +1,213 @@ +using System; +using System.Threading.Tasks; +using UnityEngine; +using RevenueCat.UI; + +namespace RevenueCat.UI.Examples +{ + /// + /// Complete example demonstrating RevenueCat UI functionality. + /// This script shows how to use paywalls and customer center after the iOS implementation is complete. + /// + public class RevenueCatUICompleteExample : MonoBehaviour + { + [Header("Paywall Configuration")] + public string offeringIdentifier = ""; // Leave empty for default offering + public bool displayCloseButton = true; + + [Header("Conditional Paywall")] + public string requiredEntitlementIdentifier = "premium"; // Example entitlement ID + + [Header("UI")] + public UnityEngine.UI.Button paywallButton; + public UnityEngine.UI.Button conditionalPaywallButton; + public UnityEngine.UI.Button customerCenterButton; + public UnityEngine.UI.Text statusText; + + private void Start() + { + // Setup button listeners + if (paywallButton != null) + paywallButton.onClick.AddListener(OnPresentPaywallClicked); + + if (conditionalPaywallButton != null) + conditionalPaywallButton.onClick.AddListener(OnPresentConditionalPaywallClicked); + + if (customerCenterButton != null) + customerCenterButton.onClick.AddListener(OnPresentCustomerCenterClicked); + + // Check platform support + bool isSupported = RevenueCatUI.IsSupported(); + UpdateStatus($"RevenueCat UI Supported: {isSupported}"); + + // Enable/disable buttons based on support + if (paywallButton != null) paywallButton.interactable = isSupported; + if (conditionalPaywallButton != null) conditionalPaywallButton.interactable = isSupported; + if (customerCenterButton != null) customerCenterButton.interactable = isSupported; + + if (!isSupported) + { + Debug.LogWarning("[RevenueCatUI] Platform not supported. UI components will be disabled."); + } + } + + /// + /// Present a paywall with the configured options. + /// + public async void OnPresentPaywallClicked() + { + UpdateStatus("Presenting paywall..."); + + var options = new PaywallOptions + { + OfferingIdentifier = string.IsNullOrEmpty(offeringIdentifier) ? null : offeringIdentifier, + DisplayCloseButton = displayCloseButton + }; + + try + { + var result = await RevenueCatUI.PresentPaywall(options); + HandlePaywallResult(result); + } + catch (Exception e) + { + UpdateStatus($"Error presenting paywall: {e.Message}"); + Debug.LogError($"[RevenueCatUI] Paywall presentation error: {e}"); + } + } + + /// + /// Present a paywall only if the user doesn't have the required entitlement. + /// + public async void OnPresentConditionalPaywallClicked() + { + if (string.IsNullOrEmpty(requiredEntitlementIdentifier)) + { + UpdateStatus("Error: Please set a required entitlement identifier"); + return; + } + + UpdateStatus($"Checking entitlement '{requiredEntitlementIdentifier}' and presenting paywall if needed..."); + + var options = new PaywallOptions + { + OfferingIdentifier = string.IsNullOrEmpty(offeringIdentifier) ? null : offeringIdentifier, + DisplayCloseButton = displayCloseButton + }; + + try + { + var result = await RevenueCatUI.PresentPaywallIfNeeded(requiredEntitlementIdentifier, options); + HandlePaywallResult(result); + } + catch (Exception e) + { + UpdateStatus($"Error presenting conditional paywall: {e.Message}"); + Debug.LogError($"[RevenueCatUI] Conditional paywall presentation error: {e}"); + } + } + + /// + /// Present the customer center for subscription management. + /// + public async void OnPresentCustomerCenterClicked() + { + UpdateStatus("Presenting customer center..."); + + try + { + await RevenueCatUI.PresentCustomerCenter(); + UpdateStatus("Customer center dismissed"); + } + catch (Exception e) + { + UpdateStatus($"Error presenting customer center: {e.Message}"); + Debug.LogError($"[RevenueCatUI] Customer center presentation error: {e}"); + } + } + + /// + /// Handle the result from paywall presentation. + /// + private void HandlePaywallResult(PaywallResult result) + { + string message = result.Result switch + { + PaywallResultType.NotPresented => "Paywall not presented (user may already have entitlement)", + PaywallResultType.Cancelled => "User cancelled the paywall", + PaywallResultType.Error => "An error occurred during paywall presentation", + PaywallResultType.Purchased => "User completed a purchase! 🎉", + PaywallResultType.Restored => "User restored their purchases! ✨", + _ => $"Unknown result: {result.Result}" + }; + + UpdateStatus($"Paywall result: {message}"); + Debug.Log($"[RevenueCatUI] Paywall result: {result}"); + + // Handle successful transactions + if (result.Result == PaywallResultType.Purchased || result.Result == PaywallResultType.Restored) + { + OnSuccessfulTransaction(result.Result); + } + } + + /// + /// Called when a transaction is successful (purchase or restore). + /// + private void OnSuccessfulTransaction(PaywallResultType transactionType) + { + Debug.Log($"[RevenueCatUI] Successful transaction: {transactionType}"); + + // Here you would typically: + // 1. Update your UI to reflect the new entitlements + // 2. Unlock premium features + // 3. Show a success message + // 4. Refresh customer info if needed + + // Example: + // await RefreshCustomerInfo(); + // EnablePremiumFeatures(); + // ShowSuccessMessage(); + } + + /// + /// Update the status text (if available). + /// + private void UpdateStatus(string message) + { + if (statusText != null) + { + statusText.text = message; + } + + Debug.Log($"[RevenueCatUI] Status: {message}"); + } + + /// + /// Test method to validate the complete implementation. + /// Call this from a button or in your test suite. + /// + [ContextMenu("Test RevenueCat UI Implementation")] + public void TestImplementation() + { + Debug.Log("=== RevenueCat UI Implementation Test ==="); + + // Test platform support + bool isSupported = RevenueCatUI.IsSupported(); + Debug.Log($"Platform supported: {isSupported}"); + + if (!isSupported) + { + Debug.LogWarning("Platform not supported. Cannot test further."); + return; + } + + Debug.Log("✅ RevenueCat UI implementation appears complete!"); + Debug.Log("You can now:"); + Debug.Log("- Present paywalls with RevenueCatUI.PresentPaywall()"); + Debug.Log("- Present conditional paywalls with RevenueCatUI.PresentPaywallIfNeeded()"); + Debug.Log("- Present customer center with RevenueCatUI.PresentCustomerCenter()"); + Debug.Log("- Handle results with proper async/await patterns"); + } + } +} \ No newline at end of file diff --git a/RevenueCatUI/Runtime/Examples/RevenueCatUICompleteExample.cs.meta b/RevenueCatUI/Runtime/Examples/RevenueCatUICompleteExample.cs.meta new file mode 100644 index 00000000..5508b528 --- /dev/null +++ b/RevenueCatUI/Runtime/Examples/RevenueCatUICompleteExample.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: abaeb905f65af458687780388d0d2435 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/RevenueCatUI/Runtime/Examples/RevenueCatUIExample.cs b/RevenueCatUI/Runtime/Examples/RevenueCatUIExample.cs new file mode 100644 index 00000000..b87f9d54 --- /dev/null +++ b/RevenueCatUI/Runtime/Examples/RevenueCatUIExample.cs @@ -0,0 +1,183 @@ +using UnityEngine; +using RevenueCat; +using System.Threading.Tasks; + +namespace RevenueCat.UI.Examples +{ + /// + /// Example script demonstrating how to use RevenueCat UI components. + /// Attach this to a GameObject and call the public methods from UI buttons. + /// + public class RevenueCatUIExample : MonoBehaviour + { + [Header("Configuration")] + [SerializeField] private string offeringIdentifier = "premium"; + [SerializeField] private string requiredEntitlementIdentifier = "pro_access"; + + /// + /// Example: Present a paywall with default settings + /// + public async void ShowPaywall() + { + Debug.Log("[RevenueCatUIExample] Presenting paywall..."); + + if (!RevenueCatUI.IsSupported()) + { + Debug.LogWarning("[RevenueCatUIExample] RevenueCat UI is not supported on this platform"); + return; + } + + try + { + var result = await RevenueCatUI.PresentPaywall(); + HandlePaywallResult(result); + } + catch (System.Exception e) + { + Debug.LogError($"[RevenueCatUIExample] Error presenting paywall: {e.Message}"); + } + } + + /// + /// Example: Present a paywall with specific offering and close button + /// + public async void ShowPaywallWithOptions() + { + Debug.Log($"[RevenueCatUIExample] Presenting paywall with offering: {offeringIdentifier}"); + + if (!RevenueCatUI.IsSupported()) + { + Debug.LogWarning("[RevenueCatUIExample] RevenueCat UI is not supported on this platform"); + return; + } + + try + { + var options = new PaywallOptions(offeringIdentifier, displayCloseButton: true); + var result = await RevenueCatUI.PresentPaywall(options); + HandlePaywallResult(result); + } + catch (System.Exception e) + { + Debug.LogError($"[RevenueCatUIExample] Error presenting paywall with options: {e.Message}"); + } + } + + /// + /// Example: Present paywall only if user doesn't have specific entitlement + /// + public async void ShowPaywallIfNeeded() + { + Debug.Log($"[RevenueCatUIExample] Checking for entitlement '{requiredEntitlementIdentifier}'..."); + + if (!RevenueCatUI.IsSupported()) + { + Debug.LogWarning("[RevenueCatUIExample] RevenueCat UI is not supported on this platform"); + return; + } + + try + { + var options = new PaywallOptions(offeringIdentifier, displayCloseButton: true); + var result = await RevenueCatUI.PresentPaywallIfNeeded(requiredEntitlementIdentifier, options); + + if (result.Result == PaywallResultType.NotPresented) + { + Debug.Log($"[RevenueCatUIExample] User already has '{requiredEntitlementIdentifier}' entitlement!"); + } + else + { + HandlePaywallResult(result); + } + } + catch (System.Exception e) + { + Debug.LogError($"[RevenueCatUIExample] Error presenting conditional paywall: {e.Message}"); + } + } + + /// + /// Example: Present customer center + /// + public async void ShowCustomerCenter() + { + Debug.Log("[RevenueCatUIExample] Presenting customer center..."); + + if (!RevenueCatUI.IsSupported()) + { + Debug.LogWarning("[RevenueCatUIExample] RevenueCat UI is not supported on this platform"); + return; + } + + try + { + await RevenueCatUI.PresentCustomerCenter(); + Debug.Log("[RevenueCatUIExample] Customer center was dismissed"); + } + catch (System.Exception e) + { + Debug.LogError($"[RevenueCatUIExample] Error presenting customer center: {e.Message}"); + } + } + + /// + /// Handle the result of a paywall presentation + /// + private void HandlePaywallResult(PaywallResult result) + { + switch (result.Result) + { + case PaywallResultType.Purchased: + Debug.Log("[RevenueCatUIExample] ✅ User made a purchase!"); + OnPurchaseCompleted(); + break; + + case PaywallResultType.Restored: + Debug.Log("[RevenueCatUIExample] ✅ User restored purchases!"); + OnPurchaseCompleted(); + break; + + case PaywallResultType.Cancelled: + Debug.Log("[RevenueCatUIExample] ❌ User cancelled the paywall"); + break; + + case PaywallResultType.Error: + Debug.LogError("[RevenueCatUIExample] ❌ Error during paywall presentation"); + break; + + default: + Debug.Log($"[RevenueCatUIExample] Paywall result: {result.Result}"); + break; + } + } + + /// + /// Called when a purchase or restore is completed + /// + private void OnPurchaseCompleted() + { + Debug.Log("[RevenueCatUIExample] Purchase completed - unlocking premium features!"); + + // Here you would typically: + // 1. Refresh customer info + // 2. Update UI to reflect new entitlements + // 3. Unlock premium features + // 4. Show success message to user + } + + /// + /// Check platform support on start + /// + void Start() + { + if (RevenueCatUI.IsSupported()) + { + Debug.Log("[RevenueCatUIExample] ✅ RevenueCat UI is supported on this platform"); + } + else + { + Debug.LogWarning("[RevenueCatUIExample] ⚠️ RevenueCat UI is not supported on this platform"); + } + } + } +} \ No newline at end of file diff --git a/RevenueCatUI/Runtime/Examples/RevenueCatUIExample.cs.meta b/RevenueCatUI/Runtime/Examples/RevenueCatUIExample.cs.meta new file mode 100644 index 00000000..d90ce054 --- /dev/null +++ b/RevenueCatUI/Runtime/Examples/RevenueCatUIExample.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 57172af6589fa4fca8eed186cf7878b9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/RevenueCatUI/Runtime/Internal.meta b/RevenueCatUI/Runtime/Internal.meta new file mode 100644 index 00000000..97a7bd87 --- /dev/null +++ b/RevenueCatUI/Runtime/Internal.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c3ff900372e794b3fb2921147b45df17 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/RevenueCatUI/Runtime/Internal/CustomerCenterPresenter.cs b/RevenueCatUI/Runtime/Internal/CustomerCenterPresenter.cs new file mode 100644 index 00000000..ac904869 --- /dev/null +++ b/RevenueCatUI/Runtime/Internal/CustomerCenterPresenter.cs @@ -0,0 +1,58 @@ +using System.Threading.Tasks; +using UnityEngine; + +namespace RevenueCat.UI +{ + /// + /// Platform-agnostic factory for customer center presenters. + /// + internal static class CustomerCenterPresenter + { + private static ICustomerCenterPresenter _instance; + + /// + /// Gets the platform-specific customer center presenter instance. + /// + internal static ICustomerCenterPresenter Instance + { + get + { + if (_instance == null) + { + _instance = CreatePlatformPresenter(); + } + return _instance; + } + } + + private static ICustomerCenterPresenter CreatePlatformPresenter() + { +#if REVENUECAT_UI_STUBS + return new Platforms.Stub.StubCustomerCenterPresenter(); +#elif UNITY_IOS && !UNITY_EDITOR + return new Platforms.IOSCustomerCenterPresenter(); +#elif UNITY_ANDROID && !UNITY_EDITOR + return new Platforms.AndroidCustomerCenterPresenter(); +#else + return new UnsupportedCustomerCenterPresenter(); +#endif + } + } + + /// + /// Fallback presenter for unsupported platforms. + /// + internal class UnsupportedCustomerCenterPresenter : ICustomerCenterPresenter + { + public Task PresentCustomerCenterAsync() + { + Debug.LogWarning("[RevenueCatUI] Customer center presentation is not supported on this platform."); + return Task.CompletedTask; + } + + public bool IsSupported() + { + return false; + } + } +} diff --git a/RevenueCatUI/Runtime/Internal/CustomerCenterPresenter.cs.meta b/RevenueCatUI/Runtime/Internal/CustomerCenterPresenter.cs.meta new file mode 100644 index 00000000..a3f9d9bc --- /dev/null +++ b/RevenueCatUI/Runtime/Internal/CustomerCenterPresenter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c96ee6318b1394296b8079caa65e3378 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/RevenueCatUI/Runtime/Internal/ICustomerCenterPresenter.cs b/RevenueCatUI/Runtime/Internal/ICustomerCenterPresenter.cs new file mode 100644 index 00000000..2e073be9 --- /dev/null +++ b/RevenueCatUI/Runtime/Internal/ICustomerCenterPresenter.cs @@ -0,0 +1,23 @@ +using System.Threading.Tasks; + +namespace RevenueCat.UI +{ + /// + /// Internal interface for presenting customer center. + /// Implemented by platform-specific presenters. + /// + internal interface ICustomerCenterPresenter + { + /// + /// Presents the customer center. + /// + /// Task that completes when the customer center is dismissed + Task PresentCustomerCenterAsync(); + + /// + /// Checks if customer center presentation is supported on this platform. + /// + /// True if supported, false otherwise + bool IsSupported(); + } +} \ No newline at end of file diff --git a/RevenueCatUI/Runtime/Internal/ICustomerCenterPresenter.cs.meta b/RevenueCatUI/Runtime/Internal/ICustomerCenterPresenter.cs.meta new file mode 100644 index 00000000..f794dba2 --- /dev/null +++ b/RevenueCatUI/Runtime/Internal/ICustomerCenterPresenter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f0b6f8331246c4aeb9be069c6c780b10 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/RevenueCatUI/Runtime/Internal/IPaywallPresenter.cs b/RevenueCatUI/Runtime/Internal/IPaywallPresenter.cs new file mode 100644 index 00000000..c2a4abd9 --- /dev/null +++ b/RevenueCatUI/Runtime/Internal/IPaywallPresenter.cs @@ -0,0 +1,32 @@ +using System.Threading.Tasks; + +namespace RevenueCat.UI +{ + /// + /// Internal interface for presenting paywalls. + /// Implemented by platform-specific presenters. + /// + internal interface IPaywallPresenter + { + /// + /// Presents a paywall with the given options. + /// + /// Paywall presentation options + /// Result of the paywall presentation + Task PresentPaywallAsync(PaywallOptions options); + + /// + /// Presents a paywall only if the user does not have the specified entitlement. + /// + /// Required entitlement identifier + /// Paywall presentation options + /// Result of the paywall presentation + Task PresentPaywallIfNeededAsync(string requiredEntitlementIdentifier, PaywallOptions options); + + /// + /// Checks if paywall presentation is supported on this platform. + /// + /// True if supported, false otherwise + bool IsSupported(); + } +} diff --git a/RevenueCatUI/Runtime/Internal/IPaywallPresenter.cs.meta b/RevenueCatUI/Runtime/Internal/IPaywallPresenter.cs.meta new file mode 100644 index 00000000..32a7be3f --- /dev/null +++ b/RevenueCatUI/Runtime/Internal/IPaywallPresenter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f3883b90080784a4d9979679f3a40d56 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/RevenueCatUI/Runtime/Internal/JsonUtility.cs b/RevenueCatUI/Runtime/Internal/JsonUtility.cs new file mode 100644 index 00000000..f113962c --- /dev/null +++ b/RevenueCatUI/Runtime/Internal/JsonUtility.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; +using System.Text; + +namespace RevenueCat.UI +{ + /// + /// Simple JSON utility for serializing options. + /// + internal static class JsonUtility + { + public static string Serialize(Dictionary dict) + { + if (dict == null || dict.Count == 0) + return "{}"; + + var sb = new StringBuilder(); + sb.Append("{"); + + bool first = true; + foreach (var kvp in dict) + { + if (!first) + sb.Append(","); + + sb.Append($"\"{kvp.Key}\":"); + + if (kvp.Value is string) + sb.Append($"\"{kvp.Value}\""); + else if (kvp.Value is bool) + sb.Append(kvp.Value.ToString().ToLower()); + else + sb.Append(kvp.Value?.ToString() ?? "null"); + + first = false; + } + + sb.Append("}"); + return sb.ToString(); + } + } +} \ No newline at end of file diff --git a/RevenueCatUI/Runtime/Internal/JsonUtility.cs.meta b/RevenueCatUI/Runtime/Internal/JsonUtility.cs.meta new file mode 100644 index 00000000..987d0e62 --- /dev/null +++ b/RevenueCatUI/Runtime/Internal/JsonUtility.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 04f95b42e795f4c3d97e8c6fe104e36d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/RevenueCatUI/Runtime/Internal/PaywallPresenter.cs b/RevenueCatUI/Runtime/Internal/PaywallPresenter.cs new file mode 100644 index 00000000..76fe23ce --- /dev/null +++ b/RevenueCatUI/Runtime/Internal/PaywallPresenter.cs @@ -0,0 +1,64 @@ +using System.Threading.Tasks; +using UnityEngine; + +namespace RevenueCat.UI +{ + /// + /// Platform-agnostic factory for paywall presenters. + /// + internal static class PaywallPresenter + { + private static IPaywallPresenter _instance; + + /// + /// Gets the platform-specific paywall presenter instance. + /// + internal static IPaywallPresenter Instance + { + get + { + if (_instance == null) + { + _instance = CreatePlatformPresenter(); + } + return _instance; + } + } + + private static IPaywallPresenter CreatePlatformPresenter() + { +#if REVENUECAT_UI_STUBS + return new Platforms.Stub.StubPaywallPresenter(); +#elif UNITY_IOS && !UNITY_EDITOR + return new Platforms.IOSPaywallPresenter(); +#elif UNITY_ANDROID && !UNITY_EDITOR + return new Platforms.AndroidPaywallPresenter(); +#else + return new UnsupportedPaywallPresenter(); +#endif + } + } + + /// + /// Fallback presenter for unsupported platforms. + /// + internal class UnsupportedPaywallPresenter : IPaywallPresenter + { + public Task PresentPaywallAsync(PaywallOptions options) + { + Debug.LogWarning("[RevenueCatUI] Paywall presentation is not supported on this platform."); + return Task.FromResult(PaywallResult.Error); + } + + public Task PresentPaywallIfNeededAsync(string requiredEntitlementIdentifier, PaywallOptions options) + { + Debug.LogWarning("[RevenueCatUI] Paywall presentation is not supported on this platform."); + return Task.FromResult(PaywallResult.Error); + } + + public bool IsSupported() + { + return false; + } + } +} diff --git a/RevenueCatUI/Runtime/Internal/PaywallPresenter.cs.meta b/RevenueCatUI/Runtime/Internal/PaywallPresenter.cs.meta new file mode 100644 index 00000000..79ec76cb --- /dev/null +++ b/RevenueCatUI/Runtime/Internal/PaywallPresenter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 654e05765efe643018b98cbce33f0d75 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/RevenueCatUI/Runtime/Internal/RevenueCatUICallbackHandler.cs b/RevenueCatUI/Runtime/Internal/RevenueCatUICallbackHandler.cs new file mode 100644 index 00000000..c2b800fe --- /dev/null +++ b/RevenueCatUI/Runtime/Internal/RevenueCatUICallbackHandler.cs @@ -0,0 +1,55 @@ +using UnityEngine; + +namespace RevenueCat.UI.Internal +{ + /// + /// Handles callbacks from native RevenueCat UI platforms + /// + internal class RevenueCatUICallbackHandler : MonoBehaviour + { + private static RevenueCatUICallbackHandler _instance; + +#if UNITY_ANDROID && !UNITY_EDITOR + private static Platforms.AndroidPaywallPresenter _androidPresenter; + private static Platforms.AndroidCustomerCenterPresenter _androidCustomerCenterPresenter; +#endif + + public static void Initialize() + { + if (_instance == null) + { + var go = new GameObject("RevenueCatUI"); + _instance = go.AddComponent(); + DontDestroyOnLoad(go); + } + } + +#if UNITY_ANDROID && !UNITY_EDITOR + public static void SetAndroidPresenter(Platforms.AndroidPaywallPresenter presenter) + { + _androidPresenter = presenter; + } + + public static void SetAndroidCustomerCenterPresenter(Platforms.AndroidCustomerCenterPresenter presenter) + { + _androidCustomerCenterPresenter = presenter; + } +#endif + + // Called from Android via UnitySendMessage + public void OnPaywallResult(string resultData) + { +#if UNITY_ANDROID && !UNITY_EDITOR + _androidPresenter?.OnPaywallResult(resultData); +#endif + } + + // Called from Android via UnitySendMessage + public void OnCustomerCenterResult(string resultData) + { +#if UNITY_ANDROID && !UNITY_EDITOR + _androidCustomerCenterPresenter?.OnCustomerCenterResult(resultData); +#endif + } + } +} \ No newline at end of file diff --git a/RevenueCatUI/Runtime/Internal/RevenueCatUICallbackHandler.cs.meta b/RevenueCatUI/Runtime/Internal/RevenueCatUICallbackHandler.cs.meta new file mode 100644 index 00000000..9b9432ee --- /dev/null +++ b/RevenueCatUI/Runtime/Internal/RevenueCatUICallbackHandler.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e0d48b0098bd945449d998a3e7cf40d5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/RevenueCatUI/Runtime/PaywallOptions.cs b/RevenueCatUI/Runtime/PaywallOptions.cs new file mode 100644 index 00000000..e68347fe --- /dev/null +++ b/RevenueCatUI/Runtime/PaywallOptions.cs @@ -0,0 +1,50 @@ +using System; + +namespace RevenueCat.UI +{ + /// + /// Options for configuring paywall presentation. + /// + [Serializable] + public class PaywallOptions + { + /// + /// The identifier of the offering to present. + /// If not provided, the current offering will be used. + /// + public string OfferingIdentifier { get; set; } + + /// + /// Whether to display a close button on the paywall. + /// Only applicable for original template paywalls, ignored for V2 Paywalls. + /// + public bool DisplayCloseButton { get; set; } = false; + + /// + /// Creates a new PaywallOptions instance. + /// + public PaywallOptions() + { + } + + /// + /// Creates a new PaywallOptions instance with the specified offering identifier. + /// + /// The offering identifier to present + public PaywallOptions(string offeringIdentifier) + { + OfferingIdentifier = offeringIdentifier; + } + + /// + /// Creates a new PaywallOptions instance with the specified offering identifier and close button option. + /// + /// The offering identifier to present + /// Whether to display a close button + public PaywallOptions(string offeringIdentifier, bool displayCloseButton) + { + OfferingIdentifier = offeringIdentifier; + DisplayCloseButton = displayCloseButton; + } + } +} \ No newline at end of file diff --git a/RevenueCatUI/Runtime/PaywallOptions.cs.meta b/RevenueCatUI/Runtime/PaywallOptions.cs.meta new file mode 100644 index 00000000..db35d992 --- /dev/null +++ b/RevenueCatUI/Runtime/PaywallOptions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8e862a53fcb314b2985bb7a20e4e6058 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/RevenueCatUI/Runtime/PaywallResult.cs b/RevenueCatUI/Runtime/PaywallResult.cs new file mode 100644 index 00000000..ff5cebd5 --- /dev/null +++ b/RevenueCatUI/Runtime/PaywallResult.cs @@ -0,0 +1,110 @@ +using System; + +namespace RevenueCat.UI +{ + /// + /// Represents the result of a paywall presentation. + /// + [Serializable] + public class PaywallResult + { + /// + /// The type of result from the paywall presentation. + /// + public PaywallResultType Result { get; } + + /// + /// Creates a new PaywallResult. + /// + /// The result type + public PaywallResult(PaywallResultType result) + { + Result = result; + } + + // Convenient static properties for common results + public static PaywallResult NotNeeded => new PaywallResult(PaywallResultType.NotPresented); + public static PaywallResult Cancelled => new PaywallResult(PaywallResultType.Cancelled); + public static PaywallResult Error => new PaywallResult(PaywallResultType.Error); + public static PaywallResult Purchased => new PaywallResult(PaywallResultType.Purchased); + public static PaywallResult Restored => new PaywallResult(PaywallResultType.Restored); + + public override string ToString() + { + return $"PaywallResult({Result})"; + } + } + + /// + /// Enum representing the possible results of a paywall presentation. + /// + public enum PaywallResultType + { + /// + /// The paywall was not presented (e.g., user already has the required entitlement). + /// + NotPresented, + + /// + /// The user cancelled the paywall presentation. + /// + Cancelled, + + /// + /// An error occurred during paywall presentation. + /// + Error, + + /// + /// The user completed a purchase. + /// + Purchased, + + /// + /// The user restored their purchases. + /// + Restored + } + + /// + /// Extension methods for PaywallResultType. + /// + public static class PaywallResultTypeExtensions + { + /// + /// Converts a PaywallResultType to its string representation used by the native SDKs. + /// + /// The result type to convert + /// String representation matching the native SDK format + public static string ToNativeString(this PaywallResultType resultType) + { + return resultType switch + { + PaywallResultType.NotPresented => "NOT_PRESENTED", + PaywallResultType.Cancelled => "CANCELLED", + PaywallResultType.Error => "ERROR", + PaywallResultType.Purchased => "PURCHASED", + PaywallResultType.Restored => "RESTORED", + _ => "ERROR" + }; + } + + /// + /// Parses a native string result to PaywallResultType. + /// + /// The native result string + /// The corresponding PaywallResultType + public static PaywallResultType FromNativeString(string nativeResult) + { + return nativeResult switch + { + "NOT_PRESENTED" => PaywallResultType.NotPresented, + "CANCELLED" => PaywallResultType.Cancelled, + "ERROR" => PaywallResultType.Error, + "PURCHASED" => PaywallResultType.Purchased, + "RESTORED" => PaywallResultType.Restored, + _ => PaywallResultType.Error + }; + } + } +} \ No newline at end of file diff --git a/RevenueCatUI/Runtime/PaywallResult.cs.meta b/RevenueCatUI/Runtime/PaywallResult.cs.meta new file mode 100644 index 00000000..4aecb96b --- /dev/null +++ b/RevenueCatUI/Runtime/PaywallResult.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 28c680983dad64ceb89ff35791959bed +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/RevenueCatUI/Runtime/Platforms.meta b/RevenueCatUI/Runtime/Platforms.meta new file mode 100644 index 00000000..22f9238e --- /dev/null +++ b/RevenueCatUI/Runtime/Platforms.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 528349e0bce8f40cfbd13d9a49a2a398 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/RevenueCatUI/Runtime/Platforms/AndroidCustomerCenterPresenter.cs b/RevenueCatUI/Runtime/Platforms/AndroidCustomerCenterPresenter.cs new file mode 100644 index 00000000..6634d965 --- /dev/null +++ b/RevenueCatUI/Runtime/Platforms/AndroidCustomerCenterPresenter.cs @@ -0,0 +1,83 @@ +#if UNITY_ANDROID && !UNITY_EDITOR +using System; +using System.Threading.Tasks; +using UnityEngine; +using RevenueCat.UI.Internal; + +namespace RevenueCat.UI.Platforms +{ + /// + /// Android implementation of the customer center presenter. + /// Uses RevenueCat Hybrid Common UI for Unity integration. + /// + internal class AndroidCustomerCenterPresenter : ICustomerCenterPresenter + { + private readonly AndroidJavaClass _plugin; + private TaskCompletionSource _currentTask; + + public AndroidCustomerCenterPresenter() + { + _plugin = new AndroidJavaClass("com.revenuecat.unity.RevenueCatUIPlugin"); + RevenueCatUICallbackHandler.Initialize(); + RevenueCatUICallbackHandler.SetAndroidCustomerCenterPresenter(this); + } + + public bool IsSupported() + { + try + { + return _plugin.CallStatic("isSupported"); + } + catch (Exception e) + { + Debug.LogError($"RevenueCatUI: Error checking if customer center supported: {e.Message}"); + return false; + } + } + + public Task PresentCustomerCenterAsync() + { + if (_currentTask != null && !_currentTask.Task.IsCompleted) + { + _currentTask.TrySetCanceled(); + } + + _currentTask = new TaskCompletionSource(); + + try + { + _plugin.CallStatic("presentCustomerCenter"); + } + catch (Exception e) + { + Debug.LogError($"RevenueCatUI: Error presenting customer center: {e.Message}"); + _currentTask.TrySetResult(false); + } + + return _currentTask.Task; + } + + // Called from Java via UnitySendMessage + public void OnCustomerCenterResult(string resultData) + { + if (_currentTask == null || _currentTask.Task.IsCompleted) + return; + + try + { + string[] parts = resultData.Split('|'); + string resultString = parts.Length > 0 ? parts[0] : "ERROR"; + string message = parts.Length > 1 ? parts[1] : ""; + + bool success = !resultString.Equals("ERROR", StringComparison.OrdinalIgnoreCase); + _currentTask.TrySetResult(success); + } + catch (Exception e) + { + Debug.LogError($"RevenueCatUI: Error processing customer center result: {e.Message}"); + _currentTask.TrySetResult(false); + } + } + } +} +#endif \ No newline at end of file diff --git a/RevenueCatUI/Runtime/Platforms/AndroidCustomerCenterPresenter.cs.meta b/RevenueCatUI/Runtime/Platforms/AndroidCustomerCenterPresenter.cs.meta new file mode 100644 index 00000000..560c1ce6 --- /dev/null +++ b/RevenueCatUI/Runtime/Platforms/AndroidCustomerCenterPresenter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 43ded08764978403d8385a3a70904b78 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/RevenueCatUI/Runtime/Platforms/AndroidPaywallPresenter.cs b/RevenueCatUI/Runtime/Platforms/AndroidPaywallPresenter.cs new file mode 100644 index 00000000..136f1282 --- /dev/null +++ b/RevenueCatUI/Runtime/Platforms/AndroidPaywallPresenter.cs @@ -0,0 +1,139 @@ +#if UNITY_ANDROID && !UNITY_EDITOR +using System; +using System.Threading.Tasks; +using UnityEngine; +using RevenueCat.UI.Internal; + +namespace RevenueCat.UI.Platforms +{ + /// + /// Android implementation of the paywall presenter. + /// Uses native PaywallActivity directly, avoiding FragmentActivity requirement. + /// + internal class AndroidPaywallPresenter : IPaywallPresenter + { + private readonly AndroidJavaClass _plugin; + private TaskCompletionSource _currentPaywallTask; + + public AndroidPaywallPresenter() + { + _plugin = new AndroidJavaClass("com.revenuecat.unity.RevenueCatUIPlugin"); + RevenueCatUICallbackHandler.Initialize(); + RevenueCatUICallbackHandler.SetAndroidPresenter(this); + } + + public bool IsSupported() + { + try + { + return _plugin.CallStatic("isSupported"); + } + catch (Exception e) + { + Debug.LogError($"RevenueCatUI: Error checking if supported: {e.Message}"); + return false; + } + } + + public Task PresentPaywallAsync(PaywallOptions options) + { + if (_currentPaywallTask != null && !_currentPaywallTask.Task.IsCompleted) + { + _currentPaywallTask.TrySetCanceled(); + } + + _currentPaywallTask = new TaskCompletionSource(); + + try + { + string offeringId = options?.OfferingIdentifier ?? ""; + + Debug.Log($"[RevenueCatUI] Android: Presenting paywall with offering: '{offeringId}'"); + + // Call Java bridge with both parameters (requiredEntitlementIdentifier not needed for regular paywall) + _plugin.CallStatic("presentPaywall", "", offeringId); + } + catch (Exception e) + { + Debug.LogError($"RevenueCatUI: Error presenting paywall: {e.Message}"); + _currentPaywallTask.TrySetResult(PaywallResult.Error); + } + + return _currentPaywallTask.Task; + } + + public Task PresentPaywallIfNeededAsync(string requiredEntitlementIdentifier, PaywallOptions options) + { + if (_currentPaywallTask != null && !_currentPaywallTask.Task.IsCompleted) + { + _currentPaywallTask.TrySetCanceled(); + } + + _currentPaywallTask = new TaskCompletionSource(); + + try + { + string entitlementId = requiredEntitlementIdentifier ?? ""; + string offeringId = options?.OfferingIdentifier ?? ""; + + Debug.Log($"[RevenueCatUI] Android: Presenting conditional paywall for entitlement: '{entitlementId}', offering: '{offeringId}'"); + + // Call Java bridge with both parameters + _plugin.CallStatic("presentPaywallIfNeeded", entitlementId, offeringId); + } + catch (Exception e) + { + Debug.LogError($"RevenueCatUI: Error presenting paywall if needed: {e.Message}"); + _currentPaywallTask.TrySetResult(PaywallResult.Error); + } + + return _currentPaywallTask.Task; + } + + // Called from Java via UnitySendMessage + public void OnPaywallResult(string resultData) + { + if (_currentPaywallTask == null || _currentPaywallTask.Task.IsCompleted) + return; + + try + { + string[] parts = resultData.Split('|'); + string resultString = parts.Length > 0 ? parts[0] : "ERROR"; + string message = parts.Length > 1 ? parts[1] : ""; + + Debug.Log($"[RevenueCatUI] Android: Received paywall result: {resultString} - {message}"); + + PaywallResult result; + switch (resultString.ToUpper()) + { + case "PURCHASED": + result = PaywallResult.Purchased; + break; + case "CANCELLED": + case "CANCELED": + result = PaywallResult.Cancelled; + break; + case "RESTORED": + result = PaywallResult.Restored; + break; + case "NOT_PRESENTED": + result = PaywallResult.NotNeeded; + break; + default: + result = PaywallResult.Error; + break; + } + + _currentPaywallTask.TrySetResult(result); + } + catch (Exception e) + { + Debug.LogError($"RevenueCatUI: Error processing paywall result: {e.Message}"); + _currentPaywallTask.TrySetResult(PaywallResult.Error); + } + } + } +} +#endif + diff --git a/RevenueCatUI/Runtime/Platforms/AndroidPaywallPresenter.cs.meta b/RevenueCatUI/Runtime/Platforms/AndroidPaywallPresenter.cs.meta new file mode 100644 index 00000000..c44beec2 --- /dev/null +++ b/RevenueCatUI/Runtime/Platforms/AndroidPaywallPresenter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 13e212390fbc044cc8ba9084b3e6e2f0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/RevenueCatUI/Runtime/Platforms/Stub.meta b/RevenueCatUI/Runtime/Platforms/Stub.meta new file mode 100644 index 00000000..7a2fe6d0 --- /dev/null +++ b/RevenueCatUI/Runtime/Platforms/Stub.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 464a162a04a74496db6938e5bf8b8d56 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/RevenueCatUI/Runtime/Platforms/Stub/StubCustomerCenterPresenter.cs b/RevenueCatUI/Runtime/Platforms/Stub/StubCustomerCenterPresenter.cs new file mode 100644 index 00000000..bf8ab14f --- /dev/null +++ b/RevenueCatUI/Runtime/Platforms/Stub/StubCustomerCenterPresenter.cs @@ -0,0 +1,24 @@ +using System.Threading.Tasks; +using UnityEngine; + +namespace RevenueCat.UI.Platforms.Stub +{ + /// + /// Stub implementation that simulates Customer Center presentation. + /// Enabled when scripting define symbol REVENUECAT_UI_STUBS is present. + /// + internal class StubCustomerCenterPresenter : ICustomerCenterPresenter + { + public bool IsSupported() + { + return true; + } + + public async Task PresentCustomerCenterAsync() + { + Debug.Log("[RevenueCatUI][Stub] PresentCustomerCenter called. Completing immediately."); + await Task.Yield(); + } + } +} + diff --git a/RevenueCatUI/Runtime/Platforms/Stub/StubCustomerCenterPresenter.cs.meta b/RevenueCatUI/Runtime/Platforms/Stub/StubCustomerCenterPresenter.cs.meta new file mode 100644 index 00000000..f5432f4e --- /dev/null +++ b/RevenueCatUI/Runtime/Platforms/Stub/StubCustomerCenterPresenter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b92f3061f8bdb4a1f8e7e70348a7454b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/RevenueCatUI/Runtime/Platforms/Stub/StubPaywallPresenter.cs b/RevenueCatUI/Runtime/Platforms/Stub/StubPaywallPresenter.cs new file mode 100644 index 00000000..0fba5fa8 --- /dev/null +++ b/RevenueCatUI/Runtime/Platforms/Stub/StubPaywallPresenter.cs @@ -0,0 +1,33 @@ +using System; +using System.Threading.Tasks; +using UnityEngine; + +namespace RevenueCat.UI.Platforms.Stub +{ + /// + /// Stub implementation that simulates paywall behavior without native SDKs. + /// Enabled when scripting define symbol REVENUECAT_UI_STUBS is present. + /// + internal class StubPaywallPresenter : IPaywallPresenter + { + public bool IsSupported() + { + return true; + } + + public async Task PresentPaywallAsync(PaywallOptions options) + { + Debug.Log("[RevenueCatUI][Stub] PresentPaywall called. Simulating user cancel."); + await Task.Yield(); + return PaywallResult.Cancelled; + } + + public async Task PresentPaywallIfNeededAsync(string requiredEntitlementIdentifier, PaywallOptions options) + { + Debug.Log($"[RevenueCatUI][Stub] PresentPaywallIfNeeded called for entitlement '{requiredEntitlementIdentifier}'. Returning NotNeeded."); + await Task.Yield(); + return PaywallResult.NotNeeded; + } + } +} + diff --git a/RevenueCatUI/Runtime/Platforms/Stub/StubPaywallPresenter.cs.meta b/RevenueCatUI/Runtime/Platforms/Stub/StubPaywallPresenter.cs.meta new file mode 100644 index 00000000..3c7cca20 --- /dev/null +++ b/RevenueCatUI/Runtime/Platforms/Stub/StubPaywallPresenter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a214f790c33274faba8456f09bef6e29 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/RevenueCatUI/Runtime/Platforms/iOS.meta b/RevenueCatUI/Runtime/Platforms/iOS.meta new file mode 100644 index 00000000..420323eb --- /dev/null +++ b/RevenueCatUI/Runtime/Platforms/iOS.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 723e91e3e063c4fe38abdd31e4ad5deb +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/RevenueCatUI/Runtime/Platforms/iOS/IOSCustomerCenterPresenter.cs b/RevenueCatUI/Runtime/Platforms/iOS/IOSCustomerCenterPresenter.cs new file mode 100644 index 00000000..c9d98438 --- /dev/null +++ b/RevenueCatUI/Runtime/Platforms/iOS/IOSCustomerCenterPresenter.cs @@ -0,0 +1,92 @@ +#if UNITY_IOS && !UNITY_EDITOR +using System; +using System.Runtime.InteropServices; +using System.Threading.Tasks; +using UnityEngine; +using RevenueCat.UI; + +namespace RevenueCat.UI.Platforms +{ + internal class IOSCustomerCenterPresenter : ICustomerCenterPresenter + { + #region DLL Imports + + [DllImport("__Internal")] + private static extern void initializeRevenueCatUI(); + + [DllImport("__Internal")] + private static extern void presentCustomerCenter(CustomerCenterResultCallback callback); + + [DllImport("__Internal")] + private static extern bool isRevenueCatUISupported(); + + private delegate void CustomerCenterResultCallback(); + + #endregion + + private TaskCompletionSource currentCustomerCenterTcs; + private static TaskCompletionSource staticCurrentCustomerCenterTcs; + + public IOSCustomerCenterPresenter() + { + // Initialize the native iOS bridge + initializeRevenueCatUI(); + } + + public bool IsSupported() + { + return isRevenueCatUISupported(); + } + + public async Task PresentCustomerCenterAsync() + { + if (currentCustomerCenterTcs != null && !currentCustomerCenterTcs.Task.IsCompleted) + { + Debug.LogWarning("[RevenueCatUI] Customer center is already being presented. Cancelling previous request."); + currentCustomerCenterTcs.TrySetCanceled(); + } + + currentCustomerCenterTcs = new TaskCompletionSource(); + staticCurrentCustomerCenterTcs = currentCustomerCenterTcs; // Store for static callback + + try + { + Debug.Log("[RevenueCatUI] Presenting customer center"); + + presentCustomerCenter(OnCustomerCenterResult); + + await currentCustomerCenterTcs.Task; + } + catch (Exception ex) + { + Debug.LogError($"[RevenueCatUI] Error presenting customer center: {ex.Message}"); + currentCustomerCenterTcs?.TrySetException(ex); + } + } + + [AOT.MonoPInvokeCallback(typeof(CustomerCenterResultCallback))] + private static void OnCustomerCenterResult() + { + Debug.Log("[RevenueCatUI] Customer center dismissed"); + + var currentTcs = staticCurrentCustomerCenterTcs; + if (currentTcs != null) + { + try + { + currentTcs.TrySetResult(true); + } + catch (Exception ex) + { + Debug.LogError($"[RevenueCatUI] Error handling customer center result: {ex.Message}"); + currentTcs.TrySetResult(false); + } + finally + { + staticCurrentCustomerCenterTcs = null; // Clear after handling + } + } + } + } +} +#endif \ No newline at end of file diff --git a/RevenueCatUI/Runtime/Platforms/iOS/IOSCustomerCenterPresenter.cs.meta b/RevenueCatUI/Runtime/Platforms/iOS/IOSCustomerCenterPresenter.cs.meta new file mode 100644 index 00000000..0f34d931 --- /dev/null +++ b/RevenueCatUI/Runtime/Platforms/iOS/IOSCustomerCenterPresenter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3c25347f16a994257ae49e1048cf650d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/RevenueCatUI/Runtime/Platforms/iOS/IOSPaywallPresenter.cs b/RevenueCatUI/Runtime/Platforms/iOS/IOSPaywallPresenter.cs new file mode 100644 index 00000000..460c8786 --- /dev/null +++ b/RevenueCatUI/Runtime/Platforms/iOS/IOSPaywallPresenter.cs @@ -0,0 +1,183 @@ +#if UNITY_IOS && !UNITY_EDITOR +using System; +using System.Runtime.InteropServices; +using System.Threading.Tasks; +using UnityEngine; +using RevenueCat.UI; + +namespace RevenueCat.UI.Platforms +{ + /// + /// iOS implementation of the paywall presenter. + /// Uses purchases-hybrid-common PaywallProxy. + /// + internal class IOSPaywallPresenter : IPaywallPresenter + { + #region DLL Imports + + [DllImport("__Internal")] + private static extern void initializeRevenueCatUI(); + + [DllImport("__Internal")] + private static extern void presentPaywall(string offeringIdentifier, bool displayCloseButton, PaywallResultCallback callback); + + [DllImport("__Internal")] + private static extern void presentPaywallIfNeeded(string requiredEntitlementIdentifier, string offeringIdentifier, bool displayCloseButton, PaywallResultCallback callback); + + [DllImport("__Internal")] + private static extern bool isRevenueCatUISupported(); + + private delegate void PaywallResultCallback(string result); + + #endregion + + private TaskCompletionSource currentPaywallTcs; + private static TaskCompletionSource staticCurrentPaywallTcs; + + public IOSPaywallPresenter() + { + // Initialize the native iOS bridge + initializeRevenueCatUI(); + } + + public bool IsSupported() + { + return isRevenueCatUISupported(); + } + + public async Task PresentPaywallAsync(PaywallOptions options) + { + if (currentPaywallTcs != null && !currentPaywallTcs.Task.IsCompleted) + { + Debug.LogWarning("[RevenueCatUI] Another paywall is already being presented. Cancelling previous request."); + currentPaywallTcs.TrySetCanceled(); + } + + currentPaywallTcs = new TaskCompletionSource(); + staticCurrentPaywallTcs = currentPaywallTcs; // Store for static callback + + try + { + Debug.Log($"[RevenueCatUI] Presenting paywall with offering: {options?.OfferingIdentifier ?? "default"}, displayCloseButton: {options?.DisplayCloseButton ?? false}"); + + presentPaywall( + options?.OfferingIdentifier, + options?.DisplayCloseButton ?? false, + OnPaywallResult + ); + + return await currentPaywallTcs.Task; + } + catch (Exception ex) + { + Debug.LogError($"[RevenueCatUI] Error presenting paywall: {ex.Message}"); + currentPaywallTcs?.TrySetException(ex); + return PaywallResult.Error; + } + } + + public async Task PresentPaywallIfNeededAsync(string requiredEntitlementIdentifier, PaywallOptions options) + { + if (string.IsNullOrEmpty(requiredEntitlementIdentifier)) + { + Debug.LogError("[RevenueCatUI] requiredEntitlementIdentifier cannot be null or empty for PresentPaywallIfNeededAsync"); + return PaywallResult.Error; + } + + if (currentPaywallTcs != null && !currentPaywallTcs.Task.IsCompleted) + { + Debug.LogWarning("[RevenueCatUI] Another paywall is already being presented. Cancelling previous request."); + currentPaywallTcs.TrySetCanceled(); + } + + currentPaywallTcs = new TaskCompletionSource(); + staticCurrentPaywallTcs = currentPaywallTcs; // Store for static callback + + try + { + Debug.Log($"[RevenueCatUI] Presenting paywall if needed for entitlement: {requiredEntitlementIdentifier}, offering: {options?.OfferingIdentifier ?? "default"}, displayCloseButton: {options?.DisplayCloseButton ?? false}"); + + presentPaywallIfNeeded( + requiredEntitlementIdentifier, + options?.OfferingIdentifier, + options?.DisplayCloseButton ?? false, + OnPaywallResult + ); + + return await currentPaywallTcs.Task; + } + catch (Exception ex) + { + Debug.LogError($"[RevenueCatUI] Error presenting conditional paywall: {ex.Message}"); + currentPaywallTcs?.TrySetException(ex); + return PaywallResult.Error; + } + } + + [AOT.MonoPInvokeCallback(typeof(PaywallResultCallback))] + private static void OnPaywallResult(string result) + { + Debug.Log($"[RevenueCatUI] Paywall result received: {result}"); + + var currentTcs = staticCurrentPaywallTcs; + if (currentTcs != null) + { + HandlePaywallResult(currentTcs, result); + staticCurrentPaywallTcs = null; // Clear after handling + } + } + + private static void HandlePaywallResult(TaskCompletionSource tcs, string result) + { + try + { + if (string.IsNullOrEmpty(result)) + { + tcs.TrySetResult(PaywallResult.Error); + return; + } + + // Handle error responses + if (result.Contains("error") || result.Contains("Error")) + { + Debug.LogError($"[RevenueCatUI] Paywall error: {result}"); + tcs.TrySetResult(PaywallResult.Error); + return; + } + + // Parse result based on expected formats from PaywallProxy + // The actual result format depends on RevenueCat UI implementation + // Common results: purchased, cancelled, restored, not_needed + var lowerResult = result.ToLower(); + + if (lowerResult.Contains("purchased") || lowerResult.Contains("success")) + { + tcs.TrySetResult(PaywallResult.Purchased); + } + else if (lowerResult.Contains("cancelled") || lowerResult.Contains("cancel")) + { + tcs.TrySetResult(PaywallResult.Cancelled); + } + else if (lowerResult.Contains("restored") || lowerResult.Contains("restore")) + { + tcs.TrySetResult(PaywallResult.Restored); + } + else if (lowerResult.Contains("not_needed") || lowerResult.Contains("not needed")) + { + tcs.TrySetResult(PaywallResult.NotNeeded); + } + else + { + Debug.LogWarning($"[RevenueCatUI] Unknown paywall result: {result}, defaulting to Cancelled"); + tcs.TrySetResult(PaywallResult.Cancelled); + } + } + catch (Exception ex) + { + Debug.LogError($"[RevenueCatUI] Error handling paywall result: {ex.Message}"); + tcs.TrySetResult(PaywallResult.Error); + } + } + } +} +#endif diff --git a/RevenueCatUI/Runtime/Platforms/iOS/IOSPaywallPresenter.cs.meta b/RevenueCatUI/Runtime/Platforms/iOS/IOSPaywallPresenter.cs.meta new file mode 100644 index 00000000..14dca4e4 --- /dev/null +++ b/RevenueCatUI/Runtime/Platforms/iOS/IOSPaywallPresenter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3f43077ff755543a5b17a0b90808573d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/RevenueCatUI/Runtime/RevenueCat.UI.asmdef b/RevenueCatUI/Runtime/RevenueCat.UI.asmdef new file mode 100644 index 00000000..016d0ebe --- /dev/null +++ b/RevenueCatUI/Runtime/RevenueCat.UI.asmdef @@ -0,0 +1,20 @@ +{ + "name": "RevenueCat.UI", + "rootNamespace": "RevenueCat.UI", + "references": [ + "revenuecat.purchases-unity" + ], + "includePlatforms": [ + "Editor", + "Android", + "iOS" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/RevenueCatUI/Runtime/RevenueCat.UI.asmdef.meta b/RevenueCatUI/Runtime/RevenueCat.UI.asmdef.meta new file mode 100644 index 00000000..f04e0f87 --- /dev/null +++ b/RevenueCatUI/Runtime/RevenueCat.UI.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 538af592d6f3141c99c5b816a379fb80 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/RevenueCatUI/Runtime/RevenueCatUI.cs b/RevenueCatUI/Runtime/RevenueCatUI.cs new file mode 100644 index 00000000..9383f33b --- /dev/null +++ b/RevenueCatUI/Runtime/RevenueCatUI.cs @@ -0,0 +1,102 @@ +using System; +using System.Threading.Tasks; +using UnityEngine; + +namespace RevenueCat.UI +{ + /// + /// Main interface for RevenueCat UI components. + /// Provides methods to present paywalls and customer center. + /// + public static class RevenueCatUI + { + /// + /// Presents a paywall configured in the RevenueCat dashboard. + /// + /// Options for presenting the paywall + /// A PaywallResult indicating what happened during the paywall presentation + public static async Task PresentPaywall(PaywallOptions options = null) + { + try + { + Debug.Log("[RevenueCatUI] Presenting paywall..."); + + // Use the platform-specific implementation + var presenter = PaywallPresenter.Instance; + return await presenter.PresentPaywallAsync(options ?? new PaywallOptions()); + } + catch (Exception e) + { + Debug.LogError($"[RevenueCatUI] Error presenting paywall: {e.Message}"); + return PaywallResult.Error; + } + } + + /// + /// Presents a paywall only if the user does not have the specified entitlement. + /// + /// Entitlement identifier to check before presenting + /// Options for presenting the paywall + /// A PaywallResult indicating what happened during the paywall presentation + public static async Task PresentPaywallIfNeeded( + string requiredEntitlementIdentifier, + PaywallOptions options = null) + { + if (string.IsNullOrEmpty(requiredEntitlementIdentifier)) + { + Debug.LogError("[RevenueCatUI] Required entitlement identifier cannot be null or empty"); + return PaywallResult.Error; + } + + try + { + Debug.Log($"[RevenueCatUI] Presenting paywall if needed for entitlement: {requiredEntitlementIdentifier}"); + + var presenter = PaywallPresenter.Instance; + return await presenter.PresentPaywallIfNeededAsync(requiredEntitlementIdentifier, options ?? new PaywallOptions()); + } + catch (Exception e) + { + Debug.LogError($"[RevenueCatUI] Error presenting paywall if needed: {e.Message}"); + return PaywallResult.Error; + } + } + + /// + /// Presents the customer center where users can manage their subscriptions. + /// + /// A task that completes when the customer center is dismissed + public static async Task PresentCustomerCenter() + { + try + { + Debug.Log("[RevenueCatUI] Presenting customer center..."); + + var presenter = CustomerCenterPresenter.Instance; + await presenter.PresentCustomerCenterAsync(); + } + catch (Exception e) + { + Debug.LogError($"[RevenueCatUI] Error presenting customer center: {e.Message}"); + } + } + + /// + /// Checks if the RevenueCat UI components are available on the current platform. + /// + /// True if UI components are supported on this platform + public static bool IsSupported() + { + try + { + var paywallPresenter = PaywallPresenter.Instance; + var customerCenterPresenter = CustomerCenterPresenter.Instance; + return paywallPresenter.IsSupported() && customerCenterPresenter.IsSupported(); + } + catch + { + return false; + } + } + } +} \ No newline at end of file diff --git a/RevenueCatUI/Runtime/RevenueCatUI.cs.meta b/RevenueCatUI/Runtime/RevenueCatUI.cs.meta new file mode 100644 index 00000000..068e5675 --- /dev/null +++ b/RevenueCatUI/Runtime/RevenueCatUI.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2204d27dd3f02469dbaf46f478d096e8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/RevenueCatUI/UI.meta b/RevenueCatUI/UI.meta new file mode 100644 index 00000000..e58f0e74 --- /dev/null +++ b/RevenueCatUI/UI.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 995238eb5abdd41018e10ea0c9a5913e +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/RevenueCatUI/package.json b/RevenueCatUI/package.json new file mode 100644 index 00000000..627dd2e6 --- /dev/null +++ b/RevenueCatUI/package.json @@ -0,0 +1,32 @@ +{ + "name": "com.revenuecat.purchases-ui-unity", + "version": "1.0.0", + "displayName": "RevenueCat UI SDK for Unity", + "description": "Unity UI components for RevenueCat paywalls and customer center. Supports iOS and Android.", + "documentationUrl": "https://www.revenuecat.com/docs/unity-ui", + "changelogUrl": "https://github.com/RevenueCat/purchases-unity/blob/main/RevenueCatUI/CHANGELOG.md", + "licensesUrl": "https://github.com/RevenueCat/purchases-unity/blob/main/LICENSE", + "author": { + "name": "RevenueCat", + "email": "support@revenuecat.com", + "url": "https://www.revenuecat.com" + }, + "keywords": [ + "unity", + "ui", + "paywall", + "customer center", + "in-app purchase", + "subscriptions", + "iap", + "iOS", + "Apple", + "Android" + ], + "repoUrl": "https://github.com/RevenueCat/purchases-unity", + "dependencies": { + "com.revenuecat.purchases-unity": "file:../RevenueCat" + }, + "unity": "2021.3", + "unityRelease": "0f1" +} \ No newline at end of file diff --git a/RevenueCatUI/package.json.meta b/RevenueCatUI/package.json.meta new file mode 100644 index 00000000..65113fd3 --- /dev/null +++ b/RevenueCatUI/package.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 5a60e6102a1fc43c3af50f7b36516c3c +PackageManifestImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Subtester/Assets/Scripts/RevenueCatUITestButtons.cs b/Subtester/Assets/Scripts/RevenueCatUITestButtons.cs new file mode 100644 index 00000000..f05e7404 --- /dev/null +++ b/Subtester/Assets/Scripts/RevenueCatUITestButtons.cs @@ -0,0 +1,59 @@ +using System.Threading.Tasks; +using UnityEngine; +using UnityEngine.UI; +using RevenueCat.UI; + +public class RevenueCatUITestButtons : MonoBehaviour +{ + [SerializeField] private Text infoLabel; + [SerializeField] private string requiredEntitlementIdentifier = "premium"; + [SerializeField] private string offeringIdentifier = ""; + + public async void ShowPaywall() + { + if (!RevenueCatUI.IsSupported()) + { + LogAndSet("RevenueCatUI not supported on this platform"); + return; + } + + var options = new PaywallOptions { OfferingIdentifier = string.IsNullOrEmpty(offeringIdentifier) ? null : offeringIdentifier }; + LogAndSet("Presenting paywall..."); + var result = await RevenueCatUI.PresentPaywall(options); + LogAndSet($"Paywall result: {result}"); + } + + public async void ShowPaywallIfNeeded() + { + if (!RevenueCatUI.IsSupported()) + { + LogAndSet("RevenueCatUI not supported on this platform"); + return; + } + + var options = new PaywallOptions { OfferingIdentifier = string.IsNullOrEmpty(offeringIdentifier) ? null : offeringIdentifier }; + LogAndSet($"Presenting paywall if needed for '{requiredEntitlementIdentifier}'..."); + var result = await RevenueCatUI.PresentPaywallIfNeeded(requiredEntitlementIdentifier, options); + LogAndSet($"Conditional paywall result: {result}"); + } + + public async void ShowCustomerCenter() + { + if (!RevenueCatUI.IsSupported()) + { + LogAndSet("RevenueCatUI not supported on this platform"); + return; + } + + LogAndSet("Presenting customer center..."); + await RevenueCatUI.PresentCustomerCenter(); + LogAndSet("Customer center dismissed"); + } + + private void LogAndSet(string msg) + { + Debug.Log($"[RevenueCatUI][Test] {msg}"); + if (infoLabel != null) infoLabel.text = msg; + } +} + diff --git a/Subtester/Assets/Scripts/RevenueCatUITestButtons.cs.meta b/Subtester/Assets/Scripts/RevenueCatUITestButtons.cs.meta new file mode 100644 index 00000000..79be3c8e --- /dev/null +++ b/Subtester/Assets/Scripts/RevenueCatUITestButtons.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a99f53da2849b4780b145d5d51df8935 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Subtester/Packages/manifest.json b/Subtester/Packages/manifest.json index ca00c4ea..876c7fd8 100644 --- a/Subtester/Packages/manifest.json +++ b/Subtester/Packages/manifest.json @@ -1,6 +1,7 @@ { "dependencies": { "com.revenuecat.purchases-unity": "file:../../RevenueCat", + "com.revenuecat.purchases-ui-unity": "file:../../RevenueCatUI", "com.unity.2d.sprite": "1.0.0", "com.unity.2d.tilemap": "1.0.0", "com.unity.ads": "4.4.2", diff --git a/Subtester/Packages/packages-lock.json b/Subtester/Packages/packages-lock.json index b3dd9755..a022dda5 100644 --- a/Subtester/Packages/packages-lock.json +++ b/Subtester/Packages/packages-lock.json @@ -1,5 +1,13 @@ { "dependencies": { + "com.revenuecat.purchases-ui-unity": { + "version": "file:../../RevenueCatUI", + "depth": 0, + "source": "local", + "dependencies": { + "com.revenuecat.purchases-unity": "file:../RevenueCat" + } + }, "com.revenuecat.purchases-unity": { "version": "file:../../RevenueCat", "depth": 0, @@ -101,8 +109,8 @@ "source": "registry", "dependencies": { "com.unity.ugui": "1.0.0", - "com.unity.services.core": "1.12.4", - "com.unity.modules.jsonserialize": "1.0.0" + "com.unity.modules.jsonserialize": "1.0.0", + "com.unity.services.core": "1.12.4" }, "url": "https://packages.unity.com" }, @@ -111,9 +119,9 @@ "depth": 2, "source": "registry", "dependencies": { - "com.unity.modules.androidjni": "1.0.0", + "com.unity.modules.unitywebrequest": "1.0.0", "com.unity.nuget.newtonsoft-json": "3.2.1", - "com.unity.modules.unitywebrequest": "1.0.0" + "com.unity.modules.androidjni": "1.0.0" }, "url": "https://packages.unity.com" }, @@ -132,9 +140,9 @@ "depth": 0, "source": "registry", "dependencies": { - "com.unity.modules.audio": "1.0.0", "com.unity.modules.director": "1.0.0", "com.unity.modules.animation": "1.0.0", + "com.unity.modules.audio": "1.0.0", "com.unity.modules.particlesystem": "1.0.0" }, "url": "https://packages.unity.com" diff --git a/Subtester/ProjectSettings/GvhProjectSettings.xml b/Subtester/ProjectSettings/GvhProjectSettings.xml index 82970e59..d13f3125 100644 --- a/Subtester/ProjectSettings/GvhProjectSettings.xml +++ b/Subtester/ProjectSettings/GvhProjectSettings.xml @@ -1,28 +1,4 @@ - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/Subtester/UserSettings/Layouts/default-2021.dwlt b/Subtester/UserSettings/Layouts/default-2021.dwlt new file mode 100644 index 00000000..edde8e87 --- /dev/null +++ b/Subtester/UserSettings/Layouts/default-2021.dwlt @@ -0,0 +1,999 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &1 +MonoBehaviour: + m_ObjectHideFlags: 52 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 12004, guid: 0000000000000000e000000000000000, type: 0} + m_Name: + m_EditorClassIdentifier: + m_PixelRect: + serializedVersion: 2 + x: 0 + y: 66 + width: 1298 + height: 916 + m_ShowMode: 4 + m_Title: Console + m_RootView: {fileID: 6} + m_MinSize: {x: 875, y: 300} + m_MaxSize: {x: 10000, y: 10000} + m_Maximized: 1 +--- !u!114 &2 +MonoBehaviour: + m_ObjectHideFlags: 52 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 1 + m_Script: {fileID: 12010, guid: 0000000000000000e000000000000000, type: 0} + m_Name: + m_EditorClassIdentifier: + m_Children: + - {fileID: 9} + - {fileID: 3} + m_Position: + serializedVersion: 2 + x: 0 + y: 30 + width: 1298 + height: 866 + m_MinSize: {x: 300, y: 200} + m_MaxSize: {x: 24288, y: 16192} + vertical: 0 + controlID: 169 +--- !u!114 &3 +MonoBehaviour: + m_ObjectHideFlags: 52 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 1 + m_Script: {fileID: 12006, guid: 0000000000000000e000000000000000, type: 0} + m_Name: + m_EditorClassIdentifier: + m_Children: [] + m_Position: + serializedVersion: 2 + x: 991.5 + y: 0 + width: 306.5 + height: 866 + m_MinSize: {x: 275, y: 50} + m_MaxSize: {x: 4000, y: 4000} + m_ActualView: {fileID: 14} + m_Panes: + - {fileID: 14} + m_Selected: 0 + m_LastSelected: 0 +--- !u!114 &4 +MonoBehaviour: + m_ObjectHideFlags: 52 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 1 + m_Script: {fileID: 12006, guid: 0000000000000000e000000000000000, type: 0} + m_Name: + m_EditorClassIdentifier: + m_Children: [] + m_Position: + serializedVersion: 2 + x: 0 + y: 0 + width: 243 + height: 524 + m_MinSize: {x: 201, y: 221} + m_MaxSize: {x: 4001, y: 4021} + m_ActualView: {fileID: 15} + m_Panes: + - {fileID: 15} + m_Selected: 0 + m_LastSelected: 0 +--- !u!114 &5 +MonoBehaviour: + m_ObjectHideFlags: 52 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 1 + m_Script: {fileID: 12006, guid: 0000000000000000e000000000000000, type: 0} + m_Name: ConsoleWindow + m_EditorClassIdentifier: + m_Children: [] + m_Position: + serializedVersion: 2 + x: 0 + y: 524 + width: 991.5 + height: 342 + m_MinSize: {x: 101, y: 121} + m_MaxSize: {x: 4001, y: 4021} + m_ActualView: {fileID: 18} + m_Panes: + - {fileID: 13} + - {fileID: 18} + m_Selected: 1 + m_LastSelected: 0 +--- !u!114 &6 +MonoBehaviour: + m_ObjectHideFlags: 52 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 1 + m_Script: {fileID: 12008, guid: 0000000000000000e000000000000000, type: 0} + m_Name: + m_EditorClassIdentifier: + m_Children: + - {fileID: 7} + - {fileID: 2} + - {fileID: 8} + m_Position: + serializedVersion: 2 + x: 0 + y: 0 + width: 1298 + height: 916 + m_MinSize: {x: 875, y: 300} + m_MaxSize: {x: 10000, y: 10000} + m_UseTopView: 1 + m_TopViewHeight: 30 + m_UseBottomView: 1 + m_BottomViewHeight: 20 +--- !u!114 &7 +MonoBehaviour: + m_ObjectHideFlags: 52 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 1 + m_Script: {fileID: 12011, guid: 0000000000000000e000000000000000, type: 0} + m_Name: + m_EditorClassIdentifier: + m_Children: [] + m_Position: + serializedVersion: 2 + x: 0 + y: 0 + width: 1298 + height: 30 + m_MinSize: {x: 0, y: 0} + m_MaxSize: {x: 0, y: 0} + m_LastLoadedLayoutName: +--- !u!114 &8 +MonoBehaviour: + m_ObjectHideFlags: 52 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 1 + m_Script: {fileID: 12042, guid: 0000000000000000e000000000000000, type: 0} + m_Name: + m_EditorClassIdentifier: + m_Children: [] + m_Position: + serializedVersion: 2 + x: 0 + y: 896 + width: 1298 + height: 20 + m_MinSize: {x: 0, y: 0} + m_MaxSize: {x: 0, y: 0} +--- !u!114 &9 +MonoBehaviour: + m_ObjectHideFlags: 52 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 1 + m_Script: {fileID: 12010, guid: 0000000000000000e000000000000000, type: 0} + m_Name: + m_EditorClassIdentifier: + m_Children: + - {fileID: 10} + - {fileID: 5} + m_Position: + serializedVersion: 2 + x: 0 + y: 0 + width: 991.5 + height: 866 + m_MinSize: {x: 200, y: 200} + m_MaxSize: {x: 16192, y: 16192} + vertical: 1 + controlID: 170 +--- !u!114 &10 +MonoBehaviour: + m_ObjectHideFlags: 52 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 1 + m_Script: {fileID: 12010, guid: 0000000000000000e000000000000000, type: 0} + m_Name: + m_EditorClassIdentifier: + m_Children: + - {fileID: 4} + - {fileID: 11} + m_Position: + serializedVersion: 2 + x: 0 + y: 0 + width: 991.5 + height: 524 + m_MinSize: {x: 200, y: 100} + m_MaxSize: {x: 16192, y: 8096} + vertical: 0 + controlID: 171 +--- !u!114 &11 +MonoBehaviour: + m_ObjectHideFlags: 52 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 1 + m_Script: {fileID: 12006, guid: 0000000000000000e000000000000000, type: 0} + m_Name: SceneView + m_EditorClassIdentifier: + m_Children: [] + m_Position: + serializedVersion: 2 + x: 243 + y: 0 + width: 748.5 + height: 524 + m_MinSize: {x: 200, y: 200} + m_MaxSize: {x: 4000, y: 4000} + m_ActualView: {fileID: 16} + m_Panes: + - {fileID: 16} + - {fileID: 17} + - {fileID: 12} + m_Selected: 0 + m_LastSelected: 1 +--- !u!114 &12 +MonoBehaviour: + m_ObjectHideFlags: 52 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 1 + m_Script: {fileID: 12111, guid: 0000000000000000e000000000000000, type: 0} + m_Name: + m_EditorClassIdentifier: + m_MinSize: {x: 400, y: 100} + m_MaxSize: {x: 2048, y: 2048} + m_TitleContent: + m_Text: Asset Store + m_Image: {fileID: -7444545952099596278, guid: 0000000000000000d000000000000000, + type: 0} + m_Tooltip: + m_Pos: + serializedVersion: 2 + x: 468 + y: 181 + width: 973 + height: 501 + m_ViewDataDictionary: {fileID: 0} + m_OverlayCanvas: + m_LastAppliedPresetName: Default + m_SaveData: [] +--- !u!114 &13 +MonoBehaviour: + m_ObjectHideFlags: 52 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 1 + m_Script: {fileID: 12014, guid: 0000000000000000e000000000000000, type: 0} + m_Name: + m_EditorClassIdentifier: + m_MinSize: {x: 230, y: 250} + m_MaxSize: {x: 10000, y: 10000} + m_TitleContent: + m_Text: Project + m_Image: {fileID: -5179483145760003458, guid: 0000000000000000d000000000000000, + type: 0} + m_Tooltip: + m_Pos: + serializedVersion: 2 + x: 0 + y: 620 + width: 1154 + height: 321 + m_ViewDataDictionary: {fileID: 0} + m_OverlayCanvas: + m_LastAppliedPresetName: Default + m_SaveData: [] + m_SearchFilter: + m_NameFilter: + m_ClassNames: [] + m_AssetLabels: [] + m_AssetBundleNames: [] + m_VersionControlStates: [] + m_SoftLockControlStates: [] + m_ReferencingInstanceIDs: + m_SceneHandles: + m_ShowAllHits: 0 + m_SkipHidden: 0 + m_SearchArea: 1 + m_Folders: + - Assets/Scenes + m_Globs: [] + m_OriginalText: + m_ViewMode: 1 + m_StartGridSize: 64 + m_LastFolders: + - Assets/Scenes + m_LastFoldersGridSize: -1 + m_LastProjectPath: /Users/facundomenzella/Developer/rc/purchases-unity/Subtester + m_LockTracker: + m_IsLocked: 0 + m_FolderTreeState: + scrollPos: {x: 0, y: 143} + m_SelectedIDs: de4e0000 + m_LastClickedID: 20190 + m_ExpandedIDs: 000000002c4f000000ca9a3bffffff7f + m_RenameOverlay: + m_UserAcceptedRename: 0 + m_Name: + m_OriginalName: + m_EditFieldRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 0 + height: 0 + m_UserData: 0 + m_IsWaitingForDelay: 0 + m_IsRenaming: 0 + m_OriginalEventType: 11 + m_IsRenamingFilename: 1 + m_ClientGUIView: {fileID: 0} + m_SearchString: + m_CreateAssetUtility: + m_EndAction: {fileID: 0} + m_InstanceID: 0 + m_Path: + m_Icon: {fileID: 0} + m_ResourceFile: + m_AssetTreeState: + scrollPos: {x: 0, y: 0} + m_SelectedIDs: + m_LastClickedID: 0 + m_ExpandedIDs: 000000002c4f000000ca9a3bffffff7f + m_RenameOverlay: + m_UserAcceptedRename: 0 + m_Name: + m_OriginalName: + m_EditFieldRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 0 + height: 0 + m_UserData: 0 + m_IsWaitingForDelay: 0 + m_IsRenaming: 0 + m_OriginalEventType: 11 + m_IsRenamingFilename: 1 + m_ClientGUIView: {fileID: 0} + m_SearchString: + m_CreateAssetUtility: + m_EndAction: {fileID: 0} + m_InstanceID: 0 + m_Path: + m_Icon: {fileID: 0} + m_ResourceFile: + m_ListAreaState: + m_SelectedInstanceIDs: + m_LastClickedInstanceID: 0 + m_HadKeyboardFocusLastEvent: 1 + m_ExpandedInstanceIDs: c6230000 + m_RenameOverlay: + m_UserAcceptedRename: 0 + m_Name: + m_OriginalName: + m_EditFieldRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 0 + height: 0 + m_UserData: 0 + m_IsWaitingForDelay: 0 + m_IsRenaming: 0 + m_OriginalEventType: 11 + m_IsRenamingFilename: 1 + m_ClientGUIView: {fileID: 0} + m_CreateAssetUtility: + m_EndAction: {fileID: 0} + m_InstanceID: 0 + m_Path: + m_Icon: {fileID: 0} + m_ResourceFile: + m_NewAssetIndexInList: -1 + m_ScrollPosition: {x: 0, y: 0} + m_GridSize: 64 + m_SkipHiddenPackages: 0 + m_DirectoriesAreaWidth: 214 +--- !u!114 &14 +MonoBehaviour: + m_ObjectHideFlags: 52 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 1 + m_Script: {fileID: 12019, guid: 0000000000000000e000000000000000, type: 0} + m_Name: + m_EditorClassIdentifier: + m_MinSize: {x: 275, y: 50} + m_MaxSize: {x: 4000, y: 4000} + m_TitleContent: + m_Text: Inspector + m_Image: {fileID: -440750813802333266, guid: 0000000000000000d000000000000000, + type: 0} + m_Tooltip: + m_Pos: + serializedVersion: 2 + x: 991.5 + y: 96 + width: 305.5 + height: 845 + m_ViewDataDictionary: {fileID: 0} + m_OverlayCanvas: + m_LastAppliedPresetName: Default + m_SaveData: [] + m_ObjectsLockedBeforeSerialization: [] + m_InstanceIDsLockedBeforeSerialization: + m_PreviewResizer: + m_CachedPref: 160 + m_ControlHash: -371814159 + m_PrefName: Preview_InspectorPreview + m_LastInspectedObjectInstanceID: -1 + m_LastVerticalScrollValue: 0 + m_GlobalObjectId: + m_InspectorMode: 0 + m_LockTracker: + m_IsLocked: 0 + m_PreviewWindow: {fileID: 0} +--- !u!114 &15 +MonoBehaviour: + m_ObjectHideFlags: 52 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 1 + m_Script: {fileID: 12061, guid: 0000000000000000e000000000000000, type: 0} + m_Name: + m_EditorClassIdentifier: + m_MinSize: {x: 200, y: 200} + m_MaxSize: {x: 4000, y: 4000} + m_TitleContent: + m_Text: Hierarchy + m_Image: {fileID: -3734745235275155857, guid: 0000000000000000d000000000000000, + type: 0} + m_Tooltip: + m_Pos: + serializedVersion: 2 + x: 0 + y: 96 + width: 242 + height: 503 + m_ViewDataDictionary: {fileID: 0} + m_OverlayCanvas: + m_LastAppliedPresetName: Default + m_SaveData: [] + m_SceneHierarchy: + m_TreeViewState: + scrollPos: {x: 0, y: 0} + m_SelectedIDs: + m_LastClickedID: 0 + m_ExpandedIDs: caefffff4afaffff + m_RenameOverlay: + m_UserAcceptedRename: 0 + m_Name: + m_OriginalName: + m_EditFieldRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 0 + height: 0 + m_UserData: 0 + m_IsWaitingForDelay: 0 + m_IsRenaming: 0 + m_OriginalEventType: 11 + m_IsRenamingFilename: 0 + m_ClientGUIView: {fileID: 0} + m_SearchString: + m_ExpandedScenes: [] + m_CurrenRootInstanceID: 0 + m_LockTracker: + m_IsLocked: 0 + m_CurrentSortingName: TransformSorting + m_WindowGUID: ca75dc20719ba4dea810e8ad275cd97c +--- !u!114 &16 +MonoBehaviour: + m_ObjectHideFlags: 52 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 1 + m_Script: {fileID: 12013, guid: 0000000000000000e000000000000000, type: 0} + m_Name: + m_EditorClassIdentifier: + m_MinSize: {x: 200, y: 200} + m_MaxSize: {x: 4000, y: 4000} + m_TitleContent: + m_Text: Scene + m_Image: {fileID: 8634526014445323508, guid: 0000000000000000d000000000000000, + type: 0} + m_Tooltip: + m_Pos: + serializedVersion: 2 + x: 243 + y: 96 + width: 746.5 + height: 503 + m_ViewDataDictionary: {fileID: 0} + m_OverlayCanvas: + m_LastAppliedPresetName: Default + m_SaveData: + - dockPosition: 0 + containerId: overlay-toolbar__top + floating: 0 + collapsed: 0 + displayed: 1 + snapOffset: {x: -101, y: -26} + snapOffsetDelta: {x: 0, y: 0} + snapCorner: 3 + id: Tool Settings + index: 0 + layout: 1 + - dockPosition: 0 + containerId: overlay-toolbar__top + floating: 0 + collapsed: 0 + displayed: 1 + snapOffset: {x: -141, y: 149} + snapOffsetDelta: {x: 0, y: 0} + snapCorner: 1 + id: unity-grid-and-snap-toolbar + index: 1 + layout: 1 + - dockPosition: 1 + containerId: overlay-toolbar__top + floating: 0 + collapsed: 0 + displayed: 1 + snapOffset: {x: 0, y: 0} + snapOffsetDelta: {x: 0, y: 0} + snapCorner: 0 + id: unity-scene-view-toolbar + index: 0 + layout: 1 + - dockPosition: 1 + containerId: overlay-toolbar__top + floating: 0 + collapsed: 0 + displayed: 0 + snapOffset: {x: 0, y: 0} + snapOffsetDelta: {x: 0, y: 0} + snapCorner: 1 + id: unity-search-toolbar + index: 1 + layout: 1 + - dockPosition: 1 + containerId: overlay-toolbar__top + floating: 0 + collapsed: 0 + displayed: 0 + snapOffset: {x: 0, y: 0} + snapOffsetDelta: {x: 0, y: 0} + snapCorner: 0 + id: Scene View/Open Tile Palette + index: 2 + layout: 4 + - dockPosition: 1 + containerId: overlay-toolbar__top + floating: 0 + collapsed: 0 + displayed: 0 + snapOffset: {x: 0, y: 0} + snapOffsetDelta: {x: 0, y: 0} + snapCorner: 0 + id: Scene View/Tilemap Focus + index: 3 + layout: 4 + - dockPosition: 0 + containerId: overlay-container--left + floating: 0 + collapsed: 0 + displayed: 1 + snapOffset: {x: 0, y: 0} + snapOffsetDelta: {x: 0, y: 0} + snapCorner: 0 + id: unity-transform-toolbar + index: 0 + layout: 2 + - dockPosition: 0 + containerId: overlay-container--right + floating: 0 + collapsed: 0 + displayed: 1 + snapOffset: {x: 67.5, y: 86} + snapOffsetDelta: {x: 0, y: 0} + snapCorner: 0 + id: Orientation + index: 0 + layout: 4 + - dockPosition: 1 + containerId: overlay-container--right + floating: 0 + collapsed: 0 + displayed: 0 + snapOffset: {x: 0, y: 0} + snapOffsetDelta: {x: 0, y: 0} + snapCorner: 0 + id: Scene View/Light Settings + index: 0 + layout: 4 + - dockPosition: 1 + containerId: overlay-container--right + floating: 0 + collapsed: 0 + displayed: 0 + snapOffset: {x: 0, y: 0} + snapOffsetDelta: {x: 0, y: 0} + snapCorner: 0 + id: Scene View/Camera + index: 1 + layout: 4 + - dockPosition: 1 + containerId: overlay-container--right + floating: 0 + collapsed: 0 + displayed: 0 + snapOffset: {x: 0, y: 0} + snapOffsetDelta: {x: 0, y: 0} + snapCorner: 0 + id: Scene View/Cloth Constraints + index: 2 + layout: 4 + - dockPosition: 1 + containerId: overlay-container--right + floating: 0 + collapsed: 0 + displayed: 0 + snapOffset: {x: 0, y: 0} + snapOffsetDelta: {x: 0, y: 0} + snapCorner: 0 + id: Scene View/Cloth Collisions + index: 3 + layout: 4 + - dockPosition: 1 + containerId: overlay-container--right + floating: 0 + collapsed: 0 + displayed: 0 + snapOffset: {x: 0, y: 0} + snapOffsetDelta: {x: 0, y: 0} + snapCorner: 0 + id: Scene View/Navmesh Display + index: 4 + layout: 4 + - dockPosition: 1 + containerId: overlay-container--right + floating: 0 + collapsed: 0 + displayed: 0 + snapOffset: {x: 0, y: 0} + snapOffsetDelta: {x: 0, y: 0} + snapCorner: 0 + id: Scene View/Agent Display + index: 5 + layout: 4 + - dockPosition: 1 + containerId: overlay-container--right + floating: 0 + collapsed: 0 + displayed: 0 + snapOffset: {x: 0, y: 0} + snapOffsetDelta: {x: 0, y: 0} + snapCorner: 0 + id: Scene View/Obstacle Display + index: 6 + layout: 4 + - dockPosition: 1 + containerId: overlay-container--right + floating: 0 + collapsed: 0 + displayed: 0 + snapOffset: {x: 0, y: 0} + snapOffsetDelta: {x: 0, y: 0} + snapCorner: 0 + id: Scene View/Occlusion Culling + index: 7 + layout: 4 + - dockPosition: 1 + containerId: overlay-container--right + floating: 0 + collapsed: 0 + displayed: 0 + snapOffset: {x: 0, y: 0} + snapOffsetDelta: {x: 0, y: 0} + snapCorner: 0 + id: Scene View/Physics Debugger + index: 8 + layout: 4 + - dockPosition: 1 + containerId: overlay-container--right + floating: 0 + collapsed: 0 + displayed: 0 + snapOffset: {x: 0, y: 0} + snapOffsetDelta: {x: 0, y: 0} + snapCorner: 0 + id: Scene View/Scene Visibility + index: 9 + layout: 4 + - dockPosition: 1 + containerId: overlay-container--right + floating: 0 + collapsed: 0 + displayed: 0 + snapOffset: {x: 0, y: 0} + snapOffsetDelta: {x: 0, y: 0} + snapCorner: 0 + id: Scene View/Particles + index: 10 + layout: 4 + m_WindowGUID: 4256d097feee24264925944e861fcaa2 + m_Gizmos: 1 + m_OverrideSceneCullingMask: 6917529027641081856 + m_SceneIsLit: 1 + m_SceneLighting: 1 + m_2DMode: 0 + m_isRotationLocked: 0 + m_PlayAudio: 0 + m_AudioPlay: 0 + m_Position: + m_Target: {x: 460.2448, y: 190.76195, z: -83.29181} + speed: 2 + m_Value: {x: 460.2448, y: 190.76195, z: -83.29181} + m_RenderMode: 0 + m_CameraMode: + drawMode: 0 + name: Shaded + section: Shading Mode + m_ValidateTrueMetals: 0 + m_DoValidateTrueMetals: 0 + m_ExposureSliderValue: 0 + m_SceneViewState: + m_AlwaysRefresh: 0 + showFog: 1 + showSkybox: 1 + showFlares: 1 + showImageEffects: 1 + showParticleSystems: 1 + showVisualEffectGraphs: 1 + m_FxEnabled: 1 + m_Grid: + xGrid: + m_Fade: + m_Target: 0 + speed: 2 + m_Value: 0 + m_Color: {r: 0.5, g: 0.5, b: 0.5, a: 0.4} + m_Pivot: {x: 0, y: 0, z: 0} + m_Size: {x: 0, y: 0} + yGrid: + m_Fade: + m_Target: 1 + speed: 2 + m_Value: 1 + m_Color: {r: 0.5, g: 0.5, b: 0.5, a: 0.4} + m_Pivot: {x: 0, y: 0, z: 0} + m_Size: {x: 1, y: 1} + zGrid: + m_Fade: + m_Target: 0 + speed: 2 + m_Value: 0 + m_Color: {r: 0.5, g: 0.5, b: 0.5, a: 0.4} + m_Pivot: {x: 0, y: 0, z: 0} + m_Size: {x: 0, y: 0} + m_ShowGrid: 1 + m_GridAxis: 1 + m_gridOpacity: 0.5 + m_Rotation: + m_Target: {x: 0, y: 0, z: 0, w: 1} + speed: 2 + m_Value: {x: -0, y: 0, z: -0, w: -1} + m_Size: + m_Target: 1455.9442 + speed: 2 + m_Value: 1455.9442 + m_Ortho: + m_Target: 0 + speed: 2 + m_Value: 0 + m_CameraSettings: + m_Speed: 1 + m_SpeedNormalized: 0.5 + m_SpeedMin: 0.01 + m_SpeedMax: 2 + m_EasingEnabled: 1 + m_EasingDuration: 0.4 + m_AccelerationEnabled: 1 + m_FieldOfViewHorizontalOrVertical: 60 + m_NearClip: 0.03 + m_FarClip: 10000 + m_DynamicClip: 1 + m_OcclusionCulling: 0 + m_LastSceneViewRotation: {x: 0, y: 0, z: 0, w: 0} + m_LastSceneViewOrtho: 0 + m_ReplacementShader: {fileID: 0} + m_ReplacementString: + m_SceneVisActive: 1 + m_LastLockedObject: {fileID: 0} + m_ViewIsLockedToObject: 0 +--- !u!114 &17 +MonoBehaviour: + m_ObjectHideFlags: 52 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 1 + m_Script: {fileID: 12015, guid: 0000000000000000e000000000000000, type: 0} + m_Name: + m_EditorClassIdentifier: + m_MinSize: {x: 200, y: 200} + m_MaxSize: {x: 4000, y: 4000} + m_TitleContent: + m_Text: Game + m_Image: {fileID: 4621777727084837110, guid: 0000000000000000d000000000000000, + type: 0} + m_Tooltip: + m_Pos: + serializedVersion: 2 + x: 283 + y: 96 + width: 870 + height: 503 + m_ViewDataDictionary: {fileID: 0} + m_OverlayCanvas: + m_LastAppliedPresetName: Default + m_SaveData: [] + m_SerializedViewNames: [] + m_SerializedViewValues: [] + m_PlayModeViewName: GameView + m_ShowGizmos: 0 + m_TargetDisplay: 0 + m_ClearColor: {r: 0, g: 0, b: 0, a: 0} + m_TargetSize: {x: 1740, y: 964} + m_TextureFilterMode: 0 + m_TextureHideFlags: 61 + m_RenderIMGUI: 1 + m_EnterPlayModeBehavior: 0 + m_UseMipMap: 0 + m_VSyncEnabled: 0 + m_Gizmos: 0 + m_Stats: 0 + m_SelectedSizes: 00000000000000000000000000000000000000000000000000000000000000000000000000000000 + m_ZoomArea: + m_HRangeLocked: 0 + m_VRangeLocked: 0 + hZoomLockedByDefault: 0 + vZoomLockedByDefault: 0 + m_HBaseRangeMin: -435 + m_HBaseRangeMax: 435 + m_VBaseRangeMin: -241 + m_VBaseRangeMax: 241 + m_HAllowExceedBaseRangeMin: 1 + m_HAllowExceedBaseRangeMax: 1 + m_VAllowExceedBaseRangeMin: 1 + m_VAllowExceedBaseRangeMax: 1 + m_ScaleWithWindow: 0 + m_HSlider: 0 + m_VSlider: 0 + m_IgnoreScrollWheelUntilClicked: 0 + m_EnableMouseInput: 0 + m_EnableSliderZoomHorizontal: 0 + m_EnableSliderZoomVertical: 0 + m_UniformScale: 1 + m_UpDirection: 1 + m_DrawArea: + serializedVersion: 2 + x: 0 + y: 21 + width: 870 + height: 482 + m_Scale: {x: 1, y: 1} + m_Translation: {x: 435, y: 241} + m_MarginLeft: 0 + m_MarginRight: 0 + m_MarginTop: 0 + m_MarginBottom: 0 + m_LastShownAreaInsideMargins: + serializedVersion: 2 + x: -435 + y: -241 + width: 870 + height: 482 + m_MinimalGUI: 1 + m_defaultScale: 1 + m_LastWindowPixelSize: {x: 1740, y: 1006} + m_ClearInEditMode: 1 + m_NoCameraWarning: 1 + m_LowResolutionForAspectRatios: 01000000000000000000 + m_XRRenderMode: 0 + m_RenderTexture: {fileID: 0} +--- !u!114 &18 +MonoBehaviour: + m_ObjectHideFlags: 52 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 1 + m_Script: {fileID: 12003, guid: 0000000000000000e000000000000000, type: 0} + m_Name: + m_EditorClassIdentifier: + m_MinSize: {x: 100, y: 100} + m_MaxSize: {x: 4000, y: 4000} + m_TitleContent: + m_Text: Console + m_Image: {fileID: -4950941429401207979, guid: 0000000000000000d000000000000000, + type: 0} + m_Tooltip: + m_Pos: + serializedVersion: 2 + x: 0 + y: 620 + width: 990.5 + height: 321 + m_ViewDataDictionary: {fileID: 0} + m_OverlayCanvas: + m_LastAppliedPresetName: Default + m_SaveData: [] From f9683a5dc1056c3ead5b1e5c6932642367ab60e3 Mon Sep 17 00:00:00 2001 From: Facundo Menzella Date: Wed, 10 Sep 2025 13:43:29 +0200 Subject: [PATCH 02/65] revert code --- RevenueCatUI/Plugins.meta | 8 - RevenueCatUI/Plugins/Android.meta | 8 - .../Plugins/Android/AndroidManifest.xml | 10 - .../Plugins/Android/AndroidManifest.xml.meta | 7 - RevenueCatUI/Plugins/Android/build.gradle | 25 --- .../Plugins/Android/build.gradle.meta | 7 - .../Plugins/Android/proguard-rules.pro | 12 -- .../Plugins/Android/proguard-rules.pro.meta | 7 - .../unity/PaywallBridgeActivity.java | 183 ------------------ .../unity/PaywallBridgeActivity.java.meta | 32 --- .../revenuecat/unity/RevenueCatUIPlugin.java | 135 ------------- .../unity/RevenueCatUIPlugin.java.meta | 32 --- RevenueCatUI/Plugins/Editor.meta | 8 - .../Editor/RevenueCat.UI.Editor.asmdef | 18 -- .../Editor/RevenueCat.UI.Editor.asmdef.meta | 7 - .../Editor/RevenueCatUIDependencies.xml | 36 ---- .../Editor/RevenueCatUIDependencies.xml.meta | 7 - RevenueCatUI/Plugins/iOS.meta | 8 - RevenueCatUI/Plugins/iOS/RevenueCatUIBridge.m | 166 ---------------- .../Plugins/iOS/RevenueCatUIBridge.m.meta | 37 ---- .../Plugins/iOS/RevenueCatUIDummy.swift | 9 - .../Plugins/iOS/RevenueCatUIDummy.swift.meta | 37 ---- RevenueCatUI/README.md | 36 ++-- .../Internal/CustomerCenterPresenter.cs | 8 +- .../Runtime/Internal/PaywallPresenter.cs | 8 +- .../Internal/RevenueCatUICallbackHandler.cs | 8 +- .../AndroidCustomerCenterPresenter.cs | 83 -------- .../AndroidCustomerCenterPresenter.cs.meta | 11 -- .../Platforms/AndroidPaywallPresenter.cs | 139 ------------- .../Platforms/AndroidPaywallPresenter.cs.meta | 11 -- .../iOS/IOSCustomerCenterPresenter.cs | 92 --------- .../iOS/IOSCustomerCenterPresenter.cs.meta | 11 -- .../Platforms/iOS/IOSPaywallPresenter.cs | 183 ------------------ .../Platforms/iOS/IOSPaywallPresenter.cs.meta | 11 -- 34 files changed, 22 insertions(+), 1378 deletions(-) delete mode 100644 RevenueCatUI/Plugins.meta delete mode 100644 RevenueCatUI/Plugins/Android.meta delete mode 100644 RevenueCatUI/Plugins/Android/AndroidManifest.xml delete mode 100644 RevenueCatUI/Plugins/Android/AndroidManifest.xml.meta delete mode 100644 RevenueCatUI/Plugins/Android/build.gradle delete mode 100644 RevenueCatUI/Plugins/Android/build.gradle.meta delete mode 100644 RevenueCatUI/Plugins/Android/proguard-rules.pro delete mode 100644 RevenueCatUI/Plugins/Android/proguard-rules.pro.meta delete mode 100644 RevenueCatUI/Plugins/Android/src/main/java/com/revenuecat/unity/PaywallBridgeActivity.java delete mode 100644 RevenueCatUI/Plugins/Android/src/main/java/com/revenuecat/unity/PaywallBridgeActivity.java.meta delete mode 100644 RevenueCatUI/Plugins/Android/src/main/java/com/revenuecat/unity/RevenueCatUIPlugin.java delete mode 100644 RevenueCatUI/Plugins/Android/src/main/java/com/revenuecat/unity/RevenueCatUIPlugin.java.meta delete mode 100644 RevenueCatUI/Plugins/Editor.meta delete mode 100644 RevenueCatUI/Plugins/Editor/RevenueCat.UI.Editor.asmdef delete mode 100644 RevenueCatUI/Plugins/Editor/RevenueCat.UI.Editor.asmdef.meta delete mode 100644 RevenueCatUI/Plugins/Editor/RevenueCatUIDependencies.xml delete mode 100644 RevenueCatUI/Plugins/Editor/RevenueCatUIDependencies.xml.meta delete mode 100644 RevenueCatUI/Plugins/iOS.meta delete mode 100644 RevenueCatUI/Plugins/iOS/RevenueCatUIBridge.m delete mode 100644 RevenueCatUI/Plugins/iOS/RevenueCatUIBridge.m.meta delete mode 100644 RevenueCatUI/Plugins/iOS/RevenueCatUIDummy.swift delete mode 100644 RevenueCatUI/Plugins/iOS/RevenueCatUIDummy.swift.meta delete mode 100644 RevenueCatUI/Runtime/Platforms/AndroidCustomerCenterPresenter.cs delete mode 100644 RevenueCatUI/Runtime/Platforms/AndroidCustomerCenterPresenter.cs.meta delete mode 100644 RevenueCatUI/Runtime/Platforms/AndroidPaywallPresenter.cs delete mode 100644 RevenueCatUI/Runtime/Platforms/AndroidPaywallPresenter.cs.meta delete mode 100644 RevenueCatUI/Runtime/Platforms/iOS/IOSCustomerCenterPresenter.cs delete mode 100644 RevenueCatUI/Runtime/Platforms/iOS/IOSCustomerCenterPresenter.cs.meta delete mode 100644 RevenueCatUI/Runtime/Platforms/iOS/IOSPaywallPresenter.cs delete mode 100644 RevenueCatUI/Runtime/Platforms/iOS/IOSPaywallPresenter.cs.meta diff --git a/RevenueCatUI/Plugins.meta b/RevenueCatUI/Plugins.meta deleted file mode 100644 index 87a8a2ee..00000000 --- a/RevenueCatUI/Plugins.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: ebbdc005f0bc94b24be9b3d15f2d58db -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/RevenueCatUI/Plugins/Android.meta b/RevenueCatUI/Plugins/Android.meta deleted file mode 100644 index 80c4953e..00000000 --- a/RevenueCatUI/Plugins/Android.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 6b10b69268c67402da4a4d5750cd5ffa -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/RevenueCatUI/Plugins/Android/AndroidManifest.xml b/RevenueCatUI/Plugins/Android/AndroidManifest.xml deleted file mode 100644 index 4a790bb9..00000000 --- a/RevenueCatUI/Plugins/Android/AndroidManifest.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/RevenueCatUI/Plugins/Android/AndroidManifest.xml.meta b/RevenueCatUI/Plugins/Android/AndroidManifest.xml.meta deleted file mode 100644 index cb3a1478..00000000 --- a/RevenueCatUI/Plugins/Android/AndroidManifest.xml.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 6b5f674826a2d4d87b06c76c6af9ebdb -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/RevenueCatUI/Plugins/Android/build.gradle b/RevenueCatUI/Plugins/Android/build.gradle deleted file mode 100644 index ec280c8b..00000000 --- a/RevenueCatUI/Plugins/Android/build.gradle +++ /dev/null @@ -1,25 +0,0 @@ -apply plugin: 'com.android.library' - -android { - compileSdkVersion 33 - defaultConfig { - minSdkVersion 21 - targetSdkVersion 33 - } - - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } -} - -dependencies { - // Use hybrid-common-ui aligned with core PurchasesHybridCommon version - implementation 'com.revenuecat.purchases:purchases-hybrid-common-ui:[17.0.0]' - - // AppCompat for the bridge activity - implementation 'androidx.appcompat:appcompat:1.6.1' - - // Fragment support for the bridge pattern - implementation 'androidx.fragment:fragment:1.6.2' -} diff --git a/RevenueCatUI/Plugins/Android/build.gradle.meta b/RevenueCatUI/Plugins/Android/build.gradle.meta deleted file mode 100644 index cf26f23c..00000000 --- a/RevenueCatUI/Plugins/Android/build.gradle.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 285ca726e6cfc4f029c1d50052d6d9d3 -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/RevenueCatUI/Plugins/Android/proguard-rules.pro b/RevenueCatUI/Plugins/Android/proguard-rules.pro deleted file mode 100644 index 9d3d59d4..00000000 --- a/RevenueCatUI/Plugins/Android/proguard-rules.pro +++ /dev/null @@ -1,12 +0,0 @@ -# Keep RevenueCat UI Unity plugin classes --keep class com.revenuecat.purchases.ui.unity.** { *; } - -# Keep RevenueCat classes that we depend on --keep class com.revenuecat.purchases.** { *; } --keep class com.revenuecat.purchases.hybridcommon.** { *; } - -# Keep Unity player classes --keep class com.unity3d.player.** { *; } - -# Keep AndroidX Fragment classes --keep class androidx.fragment.** { *; } \ No newline at end of file diff --git a/RevenueCatUI/Plugins/Android/proguard-rules.pro.meta b/RevenueCatUI/Plugins/Android/proguard-rules.pro.meta deleted file mode 100644 index 4de5efc1..00000000 --- a/RevenueCatUI/Plugins/Android/proguard-rules.pro.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 17bdc4ea2e6df465f8dff3ab423da4ae -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/RevenueCatUI/Plugins/Android/src/main/java/com/revenuecat/unity/PaywallBridgeActivity.java b/RevenueCatUI/Plugins/Android/src/main/java/com/revenuecat/unity/PaywallBridgeActivity.java deleted file mode 100644 index 08b9e22e..00000000 --- a/RevenueCatUI/Plugins/Android/src/main/java/com/revenuecat/unity/PaywallBridgeActivity.java +++ /dev/null @@ -1,183 +0,0 @@ -package com.revenuecat.unity; - -import android.app.Activity; -import android.content.Intent; -import android.os.Bundle; -import androidx.appcompat.app.AppCompatActivity; -import com.revenuecat.purchases.ui.revenuecatui.activity.PaywallActivityLauncher; -import com.revenuecat.purchases.ui.revenuecatui.activity.PaywallResultHandler; -import com.revenuecat.purchases.ui.revenuecatui.activity.PaywallResult; -import com.unity3d.player.UnityPlayer; - -/** - * Optional host-wrapper activity that extends AppCompatActivity - * This is used as a fallback when the Unity activity is not a FragmentActivity - * Because it extends AppCompatActivity, it already implements the ActivityResultCaller interface - * that PaywallActivityLauncher needs. - */ -public class PaywallBridgeActivity extends AppCompatActivity { - - public static final String EXTRA_OFFERING_ID = "offering_id"; - public static final String EXTRA_REQUIRED_ENTITLEMENT = "required_entitlement"; - public static final String EXTRA_ONLY_IF_NEEDED = "only_if_needed"; - public static final String EXTRA_IS_CUSTOMER_CENTER = "is_customer_center"; - - private static final String UNITY_CALLBACK_OBJECT = "RevenueCatUI"; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - // Get parameters from intent - String offeringId = getIntent().getStringExtra(EXTRA_OFFERING_ID); - String requiredEntitlement = getIntent().getStringExtra(EXTRA_REQUIRED_ENTITLEMENT); - boolean onlyIfNeeded = getIntent().getBooleanExtra(EXTRA_ONLY_IF_NEEDED, false); - boolean isCustomerCenter = getIntent().getBooleanExtra(EXTRA_IS_CUSTOMER_CENTER, false); - - try { - if (isCustomerCenter) { - // For now, immediately report not implemented to customer center handler - sendCustomerCenterResultToUnity("ERROR|Customer center not yet implemented"); - finish(); - } else { - // Launch RevenueCat paywall - PaywallActivityLauncher launcher = new PaywallActivityLauncher(this, new PaywallResultHandler() { - @Override - public void onActivityResult(PaywallResult result) { - handlePaywallResult(result); - finish(); // Close this bridge activity - } - }); - - // Configure the launcher based on parameters (use reflection for API compatibility) - try { - if (offeringId != null && !offeringId.isEmpty()) { - boolean invoked = false; - try { - PaywallActivityLauncher.class - .getMethod("setOfferingIdentifier", String.class) - .invoke(launcher, offeringId); - invoked = true; - } catch (NoSuchMethodException ignored) {} - if (!invoked) { - try { - PaywallActivityLauncher.class - .getMethod("setOfferingId", String.class) - .invoke(launcher, offeringId); - invoked = true; - } catch (NoSuchMethodException ignored) {} - } - if (!invoked) { - try { - PaywallActivityLauncher.class - .getMethod("setOffering", String.class) - .invoke(launcher, offeringId); - } catch (NoSuchMethodException ignored) {} - } - } - if (onlyIfNeeded && requiredEntitlement != null && !requiredEntitlement.isEmpty()) { - boolean invokedReq = false; - try { - PaywallActivityLauncher.class - .getMethod("setRequiredEntitlementIdentifier", String.class) - .invoke(launcher, requiredEntitlement); - invokedReq = true; - } catch (NoSuchMethodException ignored) {} - if (!invokedReq) { - try { - PaywallActivityLauncher.class - .getMethod("setRequiredEntitlementId", String.class) - .invoke(launcher, requiredEntitlement); - invokedReq = true; - } catch (NoSuchMethodException ignored) {} - } - if (!invokedReq) { - try { - PaywallActivityLauncher.class - .getMethod("setRequiredEntitlement", String.class) - .invoke(launcher, requiredEntitlement); - } catch (NoSuchMethodException ignored) {} - } - } - } catch (Exception reflectionError) { - android.util.Log.w("RevenueCatUI", "Optional configuration via reflection failed: " + reflectionError.getMessage()); - } - - launcher.launch(); // Launch full-screen paywall - } - } catch (Exception e) { - sendResultToUnity("ERROR|Failed to launch paywall: " + e.getMessage()); - finish(); - } - } - - /** - * Handle the result from PaywallActivityLauncher - */ - private void handlePaywallResult(PaywallResult result) { - try { - String resultString; - String simple = result != null ? result.getClass().getSimpleName() : ""; - if ("Purchased".equalsIgnoreCase(simple)) { - resultString = "PURCHASED|Purchase completed successfully"; - } else if ("Cancelled".equalsIgnoreCase(simple) || "Canceled".equalsIgnoreCase(simple)) { - resultString = "CANCELLED|Paywall was cancelled by user"; - } else if ("Restored".equalsIgnoreCase(simple)) { - resultString = "RESTORED|Purchases restored successfully"; - } else if ("NotPresented".equalsIgnoreCase(simple) || "NotShown".equalsIgnoreCase(simple)) { - resultString = "NOT_PRESENTED|Paywall was not needed"; - } else { - // Fallback: try toString pattern matching - String asText = String.valueOf(result); - if (asText.toLowerCase().contains("purchase")) { - resultString = "PURCHASED|" + asText; - } else if (asText.toLowerCase().contains("cancel")) { - resultString = "CANCELLED|" + asText; - } else if (asText.toLowerCase().contains("restore")) { - resultString = "RESTORED|" + asText; - } else if (asText.toLowerCase().contains("not") && asText.toLowerCase().contains("present")) { - resultString = "NOT_PRESENTED|" + asText; - } else { - resultString = "ERROR|Unknown result: " + asText; - } - } - sendResultToUnity(resultString); - } catch (Exception e) { - sendResultToUnity("ERROR|Failed to handle paywall result: " + e.getMessage()); - } - } - - /** - * Send result callback to Unity - */ - private void sendResultToUnity(String result) { - try { - UnityPlayer.UnitySendMessage(UNITY_CALLBACK_OBJECT, "OnPaywallResult", result); - } catch (Exception e) { - // If callback fails, at least log it - android.util.Log.e("RevenueCatUI", "Failed to send result to Unity: " + e.getMessage()); - } - } - - private void sendCustomerCenterResultToUnity(String result) { - try { - UnityPlayer.UnitySendMessage(UNITY_CALLBACK_OBJECT, "OnCustomerCenterResult", result); - } catch (Exception e) { - android.util.Log.e("RevenueCatUI", "Failed to send CC result to Unity: " + e.getMessage()); - } - } - - /** - * Static method to launch this bridge activity from Unity - * @deprecated This method is kept for backward compatibility but the new bridge pattern - * should be used instead through RevenueCatUIPlugin.presentPaywall() - */ - @Deprecated - public static void launch(Activity fromActivity, String offeringId, String requiredEntitlement, int requestCode) { - Intent intent = new Intent(fromActivity, PaywallBridgeActivity.class); - intent.putExtra(EXTRA_OFFERING_ID, offeringId); - intent.putExtra(EXTRA_REQUIRED_ENTITLEMENT, requiredEntitlement); - intent.putExtra(EXTRA_ONLY_IF_NEEDED, false); - fromActivity.startActivityForResult(intent, requestCode); - } -} \ No newline at end of file diff --git a/RevenueCatUI/Plugins/Android/src/main/java/com/revenuecat/unity/PaywallBridgeActivity.java.meta b/RevenueCatUI/Plugins/Android/src/main/java/com/revenuecat/unity/PaywallBridgeActivity.java.meta deleted file mode 100644 index a77e9cd3..00000000 --- a/RevenueCatUI/Plugins/Android/src/main/java/com/revenuecat/unity/PaywallBridgeActivity.java.meta +++ /dev/null @@ -1,32 +0,0 @@ -fileFormatVersion: 2 -guid: 3de0cb42d4cef4f0684501fe75872e3a -PluginImporter: - externalObjects: {} - serializedVersion: 2 - iconMap: {} - executionOrder: {} - defineConstraints: [] - isPreloaded: 0 - isOverridable: 1 - isExplicitlyReferenced: 0 - validateReferences: 1 - platformData: - - first: - Android: Android - second: - enabled: 1 - settings: {} - - first: - Any: - second: - enabled: 0 - settings: {} - - first: - Editor: Editor - second: - enabled: 0 - settings: - DefaultValueInitialized: true - userData: - assetBundleName: - assetBundleVariant: diff --git a/RevenueCatUI/Plugins/Android/src/main/java/com/revenuecat/unity/RevenueCatUIPlugin.java b/RevenueCatUI/Plugins/Android/src/main/java/com/revenuecat/unity/RevenueCatUIPlugin.java deleted file mode 100644 index 30b9a71f..00000000 --- a/RevenueCatUI/Plugins/Android/src/main/java/com/revenuecat/unity/RevenueCatUIPlugin.java +++ /dev/null @@ -1,135 +0,0 @@ -package com.revenuecat.unity; - -import android.app.Activity; -import android.content.Intent; -import com.unity3d.player.UnityPlayer; -import com.revenuecat.purchases.ui.revenuecatui.activity.PaywallResult; - -public class RevenueCatUIPlugin { - - private static final String UNITY_CALLBACK_OBJECT = "RevenueCatUI"; - - /** - * Shows RevenueCat paywall using the new bridge pattern - * Accepts Activity (Unity's UnityPlayerActivity) and casts to FragmentActivity when possible - * @param requiredEntitlementIdentifier The required entitlement identifier (optional, can be empty) - * @param offeringIdentifier The offering identifier to display (optional, can be empty) - */ - public static void presentPaywall(String requiredEntitlementIdentifier, String offeringIdentifier) { - Activity activity = UnityPlayer.currentActivity; - if (activity == null) { - sendResultToUnity("ERROR|Unity activity is null"); - return; - } - - // Always use bridge activity to guarantee Activity Result registration occurs before STARTED - startBridgeActivity(activity, requiredEntitlementIdentifier, offeringIdentifier, false, false); - } - - /** - * Shows RevenueCat paywall if needed using the new bridge pattern - * @param requiredEntitlementIdentifier The required entitlement identifier - * @param offeringIdentifier The offering identifier to display (optional, can be empty) - */ - public static void presentPaywallIfNeeded(String requiredEntitlementIdentifier, String offeringIdentifier) { - Activity activity = UnityPlayer.currentActivity; - if (activity == null) { - sendResultToUnity("ERROR|Unity activity is null"); - return; - } - - // Always use bridge activity - startBridgeActivity(activity, requiredEntitlementIdentifier, offeringIdentifier, true, false); - } - - /** - * Shows customer center using the new bridge pattern - */ - public static void presentCustomerCenter() { - Activity activity = UnityPlayer.currentActivity; - if (activity == null) { - sendResultToUnity("ERROR|Unity activity is null"); - return; - } - - startBridgeActivity(activity, null, null, false, true); - } - - /** - * Check if RevenueCat UI is supported - * @return true if supported - */ - public static boolean isSupported() { - return true; // RevenueCat UI is available with hybrid-common-ui - } - - /** - * Internal method that implements the bridge pattern - * At compile-time we treat the parameter as Activity, but at run-time the object - * Unity passes in is still a UnityPlayerActivity instance. - */ - private static void startBridgeActivity(Activity activity, String requiredEntitlementIdentifier, String offeringIdentifier, boolean onlyIfNeeded, boolean isCustomerCenter) { - try { - Intent intent = new Intent(activity, PaywallBridgeActivity.class); - if (offeringIdentifier != null) intent.putExtra(PaywallBridgeActivity.EXTRA_OFFERING_ID, offeringIdentifier); - if (requiredEntitlementIdentifier != null) intent.putExtra(PaywallBridgeActivity.EXTRA_REQUIRED_ENTITLEMENT, requiredEntitlementIdentifier); - intent.putExtra(PaywallBridgeActivity.EXTRA_ONLY_IF_NEEDED, onlyIfNeeded); - intent.putExtra(PaywallBridgeActivity.EXTRA_IS_CUSTOMER_CENTER, isCustomerCenter); - activity.startActivity(intent); - } catch (Exception e) { - sendResultToUnity("ERROR|Failed to present paywall: " + e.getMessage()); - } - } - - /** - * Internal method for showing customer center - */ - // Customer center uses the same bridge method (isCustomerCenter=true) - - /** - * Handle the result from PaywallActivityLauncher - */ - private static void handlePaywallResult(PaywallResult result) { - try { - String resultString; - String simple = result != null ? result.getClass().getSimpleName() : ""; - if ("Purchased".equalsIgnoreCase(simple)) { - resultString = "PURCHASED|Purchase completed successfully"; - } else if ("Cancelled".equalsIgnoreCase(simple) || "Canceled".equalsIgnoreCase(simple)) { - resultString = "CANCELLED|Paywall was cancelled by user"; - } else if ("Restored".equalsIgnoreCase(simple)) { - resultString = "RESTORED|Purchases restored successfully"; - } else if ("NotPresented".equalsIgnoreCase(simple) || "NotShown".equalsIgnoreCase(simple)) { - resultString = "NOT_PRESENTED|Paywall was not needed"; - } else { - String asText = String.valueOf(result); - if (asText.toLowerCase().contains("purchase")) { - resultString = "PURCHASED|" + asText; - } else if (asText.toLowerCase().contains("cancel")) { - resultString = "CANCELLED|" + asText; - } else if (asText.toLowerCase().contains("restore")) { - resultString = "RESTORED|" + asText; - } else if (asText.toLowerCase().contains("not") && asText.toLowerCase().contains("present")) { - resultString = "NOT_PRESENTED|" + asText; - } else { - resultString = "ERROR|Unknown result: " + asText; - } - } - sendResultToUnity(resultString); - } catch (Exception e) { - sendResultToUnity("ERROR|Failed to handle paywall result: " + e.getMessage()); - } - } - - /** - * Send result callback to Unity - */ - private static void sendResultToUnity(String result) { - try { - UnityPlayer.UnitySendMessage(UNITY_CALLBACK_OBJECT, "OnPaywallResult", result); - } catch (Exception e) { - // If callback fails, at least log it - android.util.Log.e("RevenueCatUI", "Failed to send result to Unity: " + e.getMessage()); - } - } -} \ No newline at end of file diff --git a/RevenueCatUI/Plugins/Android/src/main/java/com/revenuecat/unity/RevenueCatUIPlugin.java.meta b/RevenueCatUI/Plugins/Android/src/main/java/com/revenuecat/unity/RevenueCatUIPlugin.java.meta deleted file mode 100644 index 987008aa..00000000 --- a/RevenueCatUI/Plugins/Android/src/main/java/com/revenuecat/unity/RevenueCatUIPlugin.java.meta +++ /dev/null @@ -1,32 +0,0 @@ -fileFormatVersion: 2 -guid: 85c9d3acf58334392bacc9b3a9e53d2a -PluginImporter: - externalObjects: {} - serializedVersion: 2 - iconMap: {} - executionOrder: {} - defineConstraints: [] - isPreloaded: 0 - isOverridable: 1 - isExplicitlyReferenced: 0 - validateReferences: 1 - platformData: - - first: - Android: Android - second: - enabled: 1 - settings: {} - - first: - Any: - second: - enabled: 0 - settings: {} - - first: - Editor: Editor - second: - enabled: 0 - settings: - DefaultValueInitialized: true - userData: - assetBundleName: - assetBundleVariant: diff --git a/RevenueCatUI/Plugins/Editor.meta b/RevenueCatUI/Plugins/Editor.meta deleted file mode 100644 index 0c111ea8..00000000 --- a/RevenueCatUI/Plugins/Editor.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: d2b81261d59c84f56a239b8403da7987 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/RevenueCatUI/Plugins/Editor/RevenueCat.UI.Editor.asmdef b/RevenueCatUI/Plugins/Editor/RevenueCat.UI.Editor.asmdef deleted file mode 100644 index 1e0902f3..00000000 --- a/RevenueCatUI/Plugins/Editor/RevenueCat.UI.Editor.asmdef +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "RevenueCat.UI.Editor", - "rootNamespace": "RevenueCat.UI.Editor", - "references": [ - "RevenueCat.UI" - ], - "includePlatforms": [ - "Editor" - ], - "excludePlatforms": [], - "allowUnsafeCode": false, - "overrideReferences": false, - "precompiledReferences": [], - "autoReferenced": false, - "defineConstraints": [], - "versionDefines": [], - "noEngineReferences": false -} \ No newline at end of file diff --git a/RevenueCatUI/Plugins/Editor/RevenueCat.UI.Editor.asmdef.meta b/RevenueCatUI/Plugins/Editor/RevenueCat.UI.Editor.asmdef.meta deleted file mode 100644 index 305f1ec4..00000000 --- a/RevenueCatUI/Plugins/Editor/RevenueCat.UI.Editor.asmdef.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: c4fed84d98342472aa13425ca9ece010 -AssemblyDefinitionImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/RevenueCatUI/Plugins/Editor/RevenueCatUIDependencies.xml b/RevenueCatUI/Plugins/Editor/RevenueCatUIDependencies.xml deleted file mode 100644 index 90a2826a..00000000 --- a/RevenueCatUI/Plugins/Editor/RevenueCatUIDependencies.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - https://s01.oss.sonatype.org/content/repositories/snapshots/ - - - - - - - - https://maven.google.com - - - - - https://maven.google.com - - - - - https://maven.google.com - - - - - - - - - - - diff --git a/RevenueCatUI/Plugins/Editor/RevenueCatUIDependencies.xml.meta b/RevenueCatUI/Plugins/Editor/RevenueCatUIDependencies.xml.meta deleted file mode 100644 index d1c9b288..00000000 --- a/RevenueCatUI/Plugins/Editor/RevenueCatUIDependencies.xml.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: d9d7440f253ef433eabdabb8b8f04046 -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/RevenueCatUI/Plugins/iOS.meta b/RevenueCatUI/Plugins/iOS.meta deleted file mode 100644 index bfee2745..00000000 --- a/RevenueCatUI/Plugins/iOS.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 7409590fc13bc435ca37a81a3964c912 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/RevenueCatUI/Plugins/iOS/RevenueCatUIBridge.m b/RevenueCatUI/Plugins/iOS/RevenueCatUIBridge.m deleted file mode 100644 index 961329c4..00000000 --- a/RevenueCatUI/Plugins/iOS/RevenueCatUIBridge.m +++ /dev/null @@ -1,166 +0,0 @@ -// -// RevenueCatUIBridge.m -// RevenueCat Unity UI Plugin -// -// Created for Unity SDK -// - -#import -#import -@import PurchasesHybridCommonUI; - -// Global callback storage -static void (*paywallResultCallback)(const char* result) = NULL; -static void (*customerCenterCallback)(void) = NULL; - -// Global proxy instances -static PaywallProxy *paywallProxy = nil; -static CustomerCenterProxy *customerCenterProxy = nil; - -#pragma mark - External Utility Methods (from core RevenueCat plugin) - -// These utility functions are already defined in PurchasesUnityHelper.m -// We declare them as external to use the existing implementations -extern NSString *convertCString(const char *string); -extern char *makeStringCopy(NSString *nstring); - -#pragma mark - Initialization - -void initializeRevenueCatUI() { - if (@available(iOS 15.0, *)) { - if (!paywallProxy) { - paywallProxy = [[PaywallProxy alloc] init]; - } - if (!customerCenterProxy) { - customerCenterProxy = [[CustomerCenterProxy alloc] init]; - } - NSLog(@"RevenueCat UI initialized successfully"); - } else { - NSLog(@"RevenueCat UI requires iOS 15.0 or later"); - } -} - -#pragma mark - Paywall Methods - -void presentPaywall(const char* offeringIdentifier, bool displayCloseButton, void (*callback)(const char*)) { - if (@available(iOS 15.0, *)) { - if (!paywallProxy) { - initializeRevenueCatUI(); - } - - // Store the callback - paywallResultCallback = callback; - - // Create options dictionary - NSMutableDictionary *options = [[NSMutableDictionary alloc] init]; - - if (offeringIdentifier && strlen(offeringIdentifier) > 0) { - options[@"offeringIdentifier"] = convertCString(offeringIdentifier); - } - - options[@"displayCloseButton"] = @(displayCloseButton); - options[@"shouldBlockTouchEvents"] = @(YES); // Needed for Unity integration - - // Present paywall with result handler - [paywallProxy presentPaywallWithOptions:options paywallResultHandler:^(NSString *result) { - if (paywallResultCallback) { - paywallResultCallback([result UTF8String]); - paywallResultCallback = NULL; - } - }]; - - NSLog(@"Presenting paywall with offering: %s, displayCloseButton: %d", - offeringIdentifier ? offeringIdentifier : "default", displayCloseButton); - } else { - NSLog(@"Presenting paywall requires iOS 15.0 or later"); - if (callback) { - callback("{\"error\": \"iOS 15.0 required\"}"); - } - } -} - -void presentPaywallIfNeeded(const char* requiredEntitlementIdentifier, const char* offeringIdentifier, bool displayCloseButton, void (*callback)(const char*)) { - if (@available(iOS 15.0, *)) { - if (!paywallProxy) { - initializeRevenueCatUI(); - } - - // Store the callback - paywallResultCallback = callback; - - // Create options dictionary - NSMutableDictionary *options = [[NSMutableDictionary alloc] init]; - - if (requiredEntitlementIdentifier && strlen(requiredEntitlementIdentifier) > 0) { - options[@"requiredEntitlementIdentifier"] = convertCString(requiredEntitlementIdentifier); - } else { - NSLog(@"Error: requiredEntitlementIdentifier is required for presentPaywallIfNeeded"); - if (callback) { - callback("{\"error\": \"requiredEntitlementIdentifier required\"}"); - } - return; - } - - if (offeringIdentifier && strlen(offeringIdentifier) > 0) { - options[@"offeringIdentifier"] = convertCString(offeringIdentifier); - } - - options[@"displayCloseButton"] = @(displayCloseButton); - options[@"shouldBlockTouchEvents"] = @(YES); // Needed for Unity integration - - // Present paywall if needed with result handler - [paywallProxy presentPaywallIfNeededWithOptions:options paywallResultHandler:^(NSString *result) { - if (paywallResultCallback) { - paywallResultCallback([result UTF8String]); - paywallResultCallback = NULL; - } - }]; - - NSLog(@"Presenting paywall if needed for entitlement: %s, offering: %s, displayCloseButton: %d", - requiredEntitlementIdentifier, - offeringIdentifier ? offeringIdentifier : "default", - displayCloseButton); - } else { - NSLog(@"Presenting paywall if needed requires iOS 15.0 or later"); - if (callback) { - callback("{\"error\": \"iOS 15.0 required\"}"); - } - } -} - -#pragma mark - Customer Center Methods - -void presentCustomerCenter(void (*callback)(void)) { - if (@available(iOS 15.0, *)) { - if (!customerCenterProxy) { - initializeRevenueCatUI(); - } - - // Store the callback - customerCenterCallback = callback; - - // Present customer center with result handler - [customerCenterProxy presentWithResultHandler:^{ - if (customerCenterCallback) { - customerCenterCallback(); - customerCenterCallback = NULL; - } - }]; - - NSLog(@"Presenting customer center"); - } else { - NSLog(@"Presenting customer center requires iOS 15.0 or later"); - if (callback) { - callback(); - } - } -} - -#pragma mark - Support Methods - -bool isRevenueCatUISupported() { - if (@available(iOS 15.0, *)) { - return [PaywallProxy class] != nil && [CustomerCenterProxy class] != nil; - } - return false; -} \ No newline at end of file diff --git a/RevenueCatUI/Plugins/iOS/RevenueCatUIBridge.m.meta b/RevenueCatUI/Plugins/iOS/RevenueCatUIBridge.m.meta deleted file mode 100644 index cb90cd66..00000000 --- a/RevenueCatUI/Plugins/iOS/RevenueCatUIBridge.m.meta +++ /dev/null @@ -1,37 +0,0 @@ -fileFormatVersion: 2 -guid: 3ced81200d83a4db49a3e40ec42d8c4e -PluginImporter: - externalObjects: {} - serializedVersion: 2 - iconMap: {} - executionOrder: {} - defineConstraints: [] - isPreloaded: 0 - isOverridable: 1 - isExplicitlyReferenced: 0 - validateReferences: 1 - platformData: - - first: - Any: - second: - enabled: 0 - settings: {} - - first: - Editor: Editor - second: - enabled: 0 - settings: - DefaultValueInitialized: true - - first: - iPhone: iOS - second: - enabled: 1 - settings: {} - - first: - tvOS: tvOS - second: - enabled: 1 - settings: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/RevenueCatUI/Plugins/iOS/RevenueCatUIDummy.swift b/RevenueCatUI/Plugins/iOS/RevenueCatUIDummy.swift deleted file mode 100644 index 6ead7c38..00000000 --- a/RevenueCatUI/Plugins/iOS/RevenueCatUIDummy.swift +++ /dev/null @@ -1,9 +0,0 @@ -// -// RevenueCatUIDummy.swift -// RevenueCat Unity UI Plugin -// -// Required for Swift framework support in Unity -// - -// This file is intentionally empty. -// It exists to enable Swift support for PurchasesHybridCommonUI framework in Unity iOS builds. \ No newline at end of file diff --git a/RevenueCatUI/Plugins/iOS/RevenueCatUIDummy.swift.meta b/RevenueCatUI/Plugins/iOS/RevenueCatUIDummy.swift.meta deleted file mode 100644 index fc388314..00000000 --- a/RevenueCatUI/Plugins/iOS/RevenueCatUIDummy.swift.meta +++ /dev/null @@ -1,37 +0,0 @@ -fileFormatVersion: 2 -guid: f8d93b2e4c7a4d6a9b1c8e5f0a3b6c9d -PluginImporter: - externalObjects: {} - serializedVersion: 2 - iconMap: {} - executionOrder: {} - defineConstraints: [] - isPreloaded: 0 - isOverridable: 0 - isExplicitlyReferenced: 0 - validateReferences: 1 - platformData: - - first: - Any: - second: - enabled: 0 - settings: {} - - first: - Editor: Editor - second: - enabled: 0 - settings: - DefaultValueInitialized: true - - first: - iPhone: iOS - second: - enabled: 1 - settings: {} - - first: - tvOS: tvOS - second: - enabled: 1 - settings: {} - userData: - assetBundleName: - assetBundleVariant: \ No newline at end of file diff --git a/RevenueCatUI/README.md b/RevenueCatUI/README.md index ba99e0d6..7773fcbe 100644 --- a/RevenueCatUI/README.md +++ b/RevenueCatUI/README.md @@ -30,18 +30,14 @@ https://github.com/RevenueCat/purchases-unity.git?path=RevenueCatUI ### Requirements -- **Unity 2022.3 LTS or later** -- **RevenueCat Unity SDK** - This package depends on the main RevenueCat package -- **iOS 15.0+** - Required for paywall presentation -- **Android API 24+** - Required for UI components +- Unity 2022.3 LTS or later +- RevenueCat Unity SDK (this package depends on the main RevenueCat package) ### Dependencies -This package automatically includes: -- **iOS**: `PurchasesHybridCommonUI` via CocoaPods -- **Android**: `purchases-hybrid-common-ui` via Gradle - -Dependencies are managed by Unity's External Dependency Manager. +This package is currently stub-only. It does not include native Android/iOS UI +components or dependency definitions. You can wire your flows against the API in +Editor and on devices (no-ops), and add native UI code separately when ready. ## Quick Start @@ -126,15 +122,11 @@ public enum PaywallResultType ## Architecture -### iOS Implementation -- **Native Bridge**: `RevenueCatUIBridge.mm` (Objective-C++) -- **Dependencies**: `PurchasesHybridCommonUI` via External Dependency Manager -- **Minimum Version**: iOS 15.0 (required for paywall presentation) - -### Android Implementation -- **Java Bridge**: `RevenueCatUIPlugin.java` -- **Dependencies**: `purchases-hybrid-common-ui` via Gradle -- **Minimum Version**: API 24 (Android 7.0) +### Native Implementation (optional) +This package ships with stubs only. To enable native UI later: +- Add your Android/iOS plugin code under `Plugins/` and platform presenters under `Runtime/Platforms/`. +- Define `REVENUECAT_UI_NATIVE` in Scripting Define Symbols so the factory uses native presenters. +- Provide dependency specs (EDM4U) in `Plugins/Editor/` when you add native code. ### Platform Abstraction - Factory pattern with `IPaywallPresenter` and `ICustomerCenterPresenter` @@ -143,12 +135,8 @@ public enum PaywallResultType ## Platform Support -| Platform | Paywalls | Customer Center | Notes | -|----------|----------|----------------|-------| -| iOS | ✅ | ✅ | Requires iOS 15.0+ | -| Android | ✅ | ✅ | Requires API 24+ | -| Editor | ❌ | ❌ | Logs warnings only | -| Other | ❌ | ❌ | Graceful fallbacks | +- Editor / Any platform: Stubs return immediate results (no UI) +- Native support: Add your native code and set `REVENUECAT_UI_NATIVE` ## Examples diff --git a/RevenueCatUI/Runtime/Internal/CustomerCenterPresenter.cs b/RevenueCatUI/Runtime/Internal/CustomerCenterPresenter.cs index ac904869..0a1ab71b 100644 --- a/RevenueCatUI/Runtime/Internal/CustomerCenterPresenter.cs +++ b/RevenueCatUI/Runtime/Internal/CustomerCenterPresenter.cs @@ -27,14 +27,12 @@ internal static ICustomerCenterPresenter Instance private static ICustomerCenterPresenter CreatePlatformPresenter() { -#if REVENUECAT_UI_STUBS - return new Platforms.Stub.StubCustomerCenterPresenter(); -#elif UNITY_IOS && !UNITY_EDITOR +#if REVENUECAT_UI_NATIVE && UNITY_IOS && !UNITY_EDITOR return new Platforms.IOSCustomerCenterPresenter(); -#elif UNITY_ANDROID && !UNITY_EDITOR +#elif REVENUECAT_UI_NATIVE && UNITY_ANDROID && !UNITY_EDITOR return new Platforms.AndroidCustomerCenterPresenter(); #else - return new UnsupportedCustomerCenterPresenter(); + return new Platforms.Stub.StubCustomerCenterPresenter(); #endif } } diff --git a/RevenueCatUI/Runtime/Internal/PaywallPresenter.cs b/RevenueCatUI/Runtime/Internal/PaywallPresenter.cs index 76fe23ce..f5094f7b 100644 --- a/RevenueCatUI/Runtime/Internal/PaywallPresenter.cs +++ b/RevenueCatUI/Runtime/Internal/PaywallPresenter.cs @@ -27,14 +27,12 @@ internal static IPaywallPresenter Instance private static IPaywallPresenter CreatePlatformPresenter() { -#if REVENUECAT_UI_STUBS - return new Platforms.Stub.StubPaywallPresenter(); -#elif UNITY_IOS && !UNITY_EDITOR +#if REVENUECAT_UI_NATIVE && UNITY_IOS && !UNITY_EDITOR return new Platforms.IOSPaywallPresenter(); -#elif UNITY_ANDROID && !UNITY_EDITOR +#elif REVENUECAT_UI_NATIVE && UNITY_ANDROID && !UNITY_EDITOR return new Platforms.AndroidPaywallPresenter(); #else - return new UnsupportedPaywallPresenter(); + return new Platforms.Stub.StubPaywallPresenter(); #endif } } diff --git a/RevenueCatUI/Runtime/Internal/RevenueCatUICallbackHandler.cs b/RevenueCatUI/Runtime/Internal/RevenueCatUICallbackHandler.cs index c2b800fe..dac06dfb 100644 --- a/RevenueCatUI/Runtime/Internal/RevenueCatUICallbackHandler.cs +++ b/RevenueCatUI/Runtime/Internal/RevenueCatUICallbackHandler.cs @@ -9,7 +9,7 @@ internal class RevenueCatUICallbackHandler : MonoBehaviour { private static RevenueCatUICallbackHandler _instance; -#if UNITY_ANDROID && !UNITY_EDITOR +#if REVENUECAT_UI_NATIVE && UNITY_ANDROID && !UNITY_EDITOR private static Platforms.AndroidPaywallPresenter _androidPresenter; private static Platforms.AndroidCustomerCenterPresenter _androidCustomerCenterPresenter; #endif @@ -39,7 +39,7 @@ public static void SetAndroidCustomerCenterPresenter(Platforms.AndroidCustomerCe // Called from Android via UnitySendMessage public void OnPaywallResult(string resultData) { -#if UNITY_ANDROID && !UNITY_EDITOR +#if REVENUECAT_UI_NATIVE && UNITY_ANDROID && !UNITY_EDITOR _androidPresenter?.OnPaywallResult(resultData); #endif } @@ -47,9 +47,9 @@ public void OnPaywallResult(string resultData) // Called from Android via UnitySendMessage public void OnCustomerCenterResult(string resultData) { -#if UNITY_ANDROID && !UNITY_EDITOR +#if REVENUECAT_UI_NATIVE && UNITY_ANDROID && !UNITY_EDITOR _androidCustomerCenterPresenter?.OnCustomerCenterResult(resultData); #endif } } -} \ No newline at end of file +} diff --git a/RevenueCatUI/Runtime/Platforms/AndroidCustomerCenterPresenter.cs b/RevenueCatUI/Runtime/Platforms/AndroidCustomerCenterPresenter.cs deleted file mode 100644 index 6634d965..00000000 --- a/RevenueCatUI/Runtime/Platforms/AndroidCustomerCenterPresenter.cs +++ /dev/null @@ -1,83 +0,0 @@ -#if UNITY_ANDROID && !UNITY_EDITOR -using System; -using System.Threading.Tasks; -using UnityEngine; -using RevenueCat.UI.Internal; - -namespace RevenueCat.UI.Platforms -{ - /// - /// Android implementation of the customer center presenter. - /// Uses RevenueCat Hybrid Common UI for Unity integration. - /// - internal class AndroidCustomerCenterPresenter : ICustomerCenterPresenter - { - private readonly AndroidJavaClass _plugin; - private TaskCompletionSource _currentTask; - - public AndroidCustomerCenterPresenter() - { - _plugin = new AndroidJavaClass("com.revenuecat.unity.RevenueCatUIPlugin"); - RevenueCatUICallbackHandler.Initialize(); - RevenueCatUICallbackHandler.SetAndroidCustomerCenterPresenter(this); - } - - public bool IsSupported() - { - try - { - return _plugin.CallStatic("isSupported"); - } - catch (Exception e) - { - Debug.LogError($"RevenueCatUI: Error checking if customer center supported: {e.Message}"); - return false; - } - } - - public Task PresentCustomerCenterAsync() - { - if (_currentTask != null && !_currentTask.Task.IsCompleted) - { - _currentTask.TrySetCanceled(); - } - - _currentTask = new TaskCompletionSource(); - - try - { - _plugin.CallStatic("presentCustomerCenter"); - } - catch (Exception e) - { - Debug.LogError($"RevenueCatUI: Error presenting customer center: {e.Message}"); - _currentTask.TrySetResult(false); - } - - return _currentTask.Task; - } - - // Called from Java via UnitySendMessage - public void OnCustomerCenterResult(string resultData) - { - if (_currentTask == null || _currentTask.Task.IsCompleted) - return; - - try - { - string[] parts = resultData.Split('|'); - string resultString = parts.Length > 0 ? parts[0] : "ERROR"; - string message = parts.Length > 1 ? parts[1] : ""; - - bool success = !resultString.Equals("ERROR", StringComparison.OrdinalIgnoreCase); - _currentTask.TrySetResult(success); - } - catch (Exception e) - { - Debug.LogError($"RevenueCatUI: Error processing customer center result: {e.Message}"); - _currentTask.TrySetResult(false); - } - } - } -} -#endif \ No newline at end of file diff --git a/RevenueCatUI/Runtime/Platforms/AndroidCustomerCenterPresenter.cs.meta b/RevenueCatUI/Runtime/Platforms/AndroidCustomerCenterPresenter.cs.meta deleted file mode 100644 index 560c1ce6..00000000 --- a/RevenueCatUI/Runtime/Platforms/AndroidCustomerCenterPresenter.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 43ded08764978403d8385a3a70904b78 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/RevenueCatUI/Runtime/Platforms/AndroidPaywallPresenter.cs b/RevenueCatUI/Runtime/Platforms/AndroidPaywallPresenter.cs deleted file mode 100644 index 136f1282..00000000 --- a/RevenueCatUI/Runtime/Platforms/AndroidPaywallPresenter.cs +++ /dev/null @@ -1,139 +0,0 @@ -#if UNITY_ANDROID && !UNITY_EDITOR -using System; -using System.Threading.Tasks; -using UnityEngine; -using RevenueCat.UI.Internal; - -namespace RevenueCat.UI.Platforms -{ - /// - /// Android implementation of the paywall presenter. - /// Uses native PaywallActivity directly, avoiding FragmentActivity requirement. - /// - internal class AndroidPaywallPresenter : IPaywallPresenter - { - private readonly AndroidJavaClass _plugin; - private TaskCompletionSource _currentPaywallTask; - - public AndroidPaywallPresenter() - { - _plugin = new AndroidJavaClass("com.revenuecat.unity.RevenueCatUIPlugin"); - RevenueCatUICallbackHandler.Initialize(); - RevenueCatUICallbackHandler.SetAndroidPresenter(this); - } - - public bool IsSupported() - { - try - { - return _plugin.CallStatic("isSupported"); - } - catch (Exception e) - { - Debug.LogError($"RevenueCatUI: Error checking if supported: {e.Message}"); - return false; - } - } - - public Task PresentPaywallAsync(PaywallOptions options) - { - if (_currentPaywallTask != null && !_currentPaywallTask.Task.IsCompleted) - { - _currentPaywallTask.TrySetCanceled(); - } - - _currentPaywallTask = new TaskCompletionSource(); - - try - { - string offeringId = options?.OfferingIdentifier ?? ""; - - Debug.Log($"[RevenueCatUI] Android: Presenting paywall with offering: '{offeringId}'"); - - // Call Java bridge with both parameters (requiredEntitlementIdentifier not needed for regular paywall) - _plugin.CallStatic("presentPaywall", "", offeringId); - } - catch (Exception e) - { - Debug.LogError($"RevenueCatUI: Error presenting paywall: {e.Message}"); - _currentPaywallTask.TrySetResult(PaywallResult.Error); - } - - return _currentPaywallTask.Task; - } - - public Task PresentPaywallIfNeededAsync(string requiredEntitlementIdentifier, PaywallOptions options) - { - if (_currentPaywallTask != null && !_currentPaywallTask.Task.IsCompleted) - { - _currentPaywallTask.TrySetCanceled(); - } - - _currentPaywallTask = new TaskCompletionSource(); - - try - { - string entitlementId = requiredEntitlementIdentifier ?? ""; - string offeringId = options?.OfferingIdentifier ?? ""; - - Debug.Log($"[RevenueCatUI] Android: Presenting conditional paywall for entitlement: '{entitlementId}', offering: '{offeringId}'"); - - // Call Java bridge with both parameters - _plugin.CallStatic("presentPaywallIfNeeded", entitlementId, offeringId); - } - catch (Exception e) - { - Debug.LogError($"RevenueCatUI: Error presenting paywall if needed: {e.Message}"); - _currentPaywallTask.TrySetResult(PaywallResult.Error); - } - - return _currentPaywallTask.Task; - } - - // Called from Java via UnitySendMessage - public void OnPaywallResult(string resultData) - { - if (_currentPaywallTask == null || _currentPaywallTask.Task.IsCompleted) - return; - - try - { - string[] parts = resultData.Split('|'); - string resultString = parts.Length > 0 ? parts[0] : "ERROR"; - string message = parts.Length > 1 ? parts[1] : ""; - - Debug.Log($"[RevenueCatUI] Android: Received paywall result: {resultString} - {message}"); - - PaywallResult result; - switch (resultString.ToUpper()) - { - case "PURCHASED": - result = PaywallResult.Purchased; - break; - case "CANCELLED": - case "CANCELED": - result = PaywallResult.Cancelled; - break; - case "RESTORED": - result = PaywallResult.Restored; - break; - case "NOT_PRESENTED": - result = PaywallResult.NotNeeded; - break; - default: - result = PaywallResult.Error; - break; - } - - _currentPaywallTask.TrySetResult(result); - } - catch (Exception e) - { - Debug.LogError($"RevenueCatUI: Error processing paywall result: {e.Message}"); - _currentPaywallTask.TrySetResult(PaywallResult.Error); - } - } - } -} -#endif - diff --git a/RevenueCatUI/Runtime/Platforms/AndroidPaywallPresenter.cs.meta b/RevenueCatUI/Runtime/Platforms/AndroidPaywallPresenter.cs.meta deleted file mode 100644 index c44beec2..00000000 --- a/RevenueCatUI/Runtime/Platforms/AndroidPaywallPresenter.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 13e212390fbc044cc8ba9084b3e6e2f0 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/RevenueCatUI/Runtime/Platforms/iOS/IOSCustomerCenterPresenter.cs b/RevenueCatUI/Runtime/Platforms/iOS/IOSCustomerCenterPresenter.cs deleted file mode 100644 index c9d98438..00000000 --- a/RevenueCatUI/Runtime/Platforms/iOS/IOSCustomerCenterPresenter.cs +++ /dev/null @@ -1,92 +0,0 @@ -#if UNITY_IOS && !UNITY_EDITOR -using System; -using System.Runtime.InteropServices; -using System.Threading.Tasks; -using UnityEngine; -using RevenueCat.UI; - -namespace RevenueCat.UI.Platforms -{ - internal class IOSCustomerCenterPresenter : ICustomerCenterPresenter - { - #region DLL Imports - - [DllImport("__Internal")] - private static extern void initializeRevenueCatUI(); - - [DllImport("__Internal")] - private static extern void presentCustomerCenter(CustomerCenterResultCallback callback); - - [DllImport("__Internal")] - private static extern bool isRevenueCatUISupported(); - - private delegate void CustomerCenterResultCallback(); - - #endregion - - private TaskCompletionSource currentCustomerCenterTcs; - private static TaskCompletionSource staticCurrentCustomerCenterTcs; - - public IOSCustomerCenterPresenter() - { - // Initialize the native iOS bridge - initializeRevenueCatUI(); - } - - public bool IsSupported() - { - return isRevenueCatUISupported(); - } - - public async Task PresentCustomerCenterAsync() - { - if (currentCustomerCenterTcs != null && !currentCustomerCenterTcs.Task.IsCompleted) - { - Debug.LogWarning("[RevenueCatUI] Customer center is already being presented. Cancelling previous request."); - currentCustomerCenterTcs.TrySetCanceled(); - } - - currentCustomerCenterTcs = new TaskCompletionSource(); - staticCurrentCustomerCenterTcs = currentCustomerCenterTcs; // Store for static callback - - try - { - Debug.Log("[RevenueCatUI] Presenting customer center"); - - presentCustomerCenter(OnCustomerCenterResult); - - await currentCustomerCenterTcs.Task; - } - catch (Exception ex) - { - Debug.LogError($"[RevenueCatUI] Error presenting customer center: {ex.Message}"); - currentCustomerCenterTcs?.TrySetException(ex); - } - } - - [AOT.MonoPInvokeCallback(typeof(CustomerCenterResultCallback))] - private static void OnCustomerCenterResult() - { - Debug.Log("[RevenueCatUI] Customer center dismissed"); - - var currentTcs = staticCurrentCustomerCenterTcs; - if (currentTcs != null) - { - try - { - currentTcs.TrySetResult(true); - } - catch (Exception ex) - { - Debug.LogError($"[RevenueCatUI] Error handling customer center result: {ex.Message}"); - currentTcs.TrySetResult(false); - } - finally - { - staticCurrentCustomerCenterTcs = null; // Clear after handling - } - } - } - } -} -#endif \ No newline at end of file diff --git a/RevenueCatUI/Runtime/Platforms/iOS/IOSCustomerCenterPresenter.cs.meta b/RevenueCatUI/Runtime/Platforms/iOS/IOSCustomerCenterPresenter.cs.meta deleted file mode 100644 index 0f34d931..00000000 --- a/RevenueCatUI/Runtime/Platforms/iOS/IOSCustomerCenterPresenter.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 3c25347f16a994257ae49e1048cf650d -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/RevenueCatUI/Runtime/Platforms/iOS/IOSPaywallPresenter.cs b/RevenueCatUI/Runtime/Platforms/iOS/IOSPaywallPresenter.cs deleted file mode 100644 index 460c8786..00000000 --- a/RevenueCatUI/Runtime/Platforms/iOS/IOSPaywallPresenter.cs +++ /dev/null @@ -1,183 +0,0 @@ -#if UNITY_IOS && !UNITY_EDITOR -using System; -using System.Runtime.InteropServices; -using System.Threading.Tasks; -using UnityEngine; -using RevenueCat.UI; - -namespace RevenueCat.UI.Platforms -{ - /// - /// iOS implementation of the paywall presenter. - /// Uses purchases-hybrid-common PaywallProxy. - /// - internal class IOSPaywallPresenter : IPaywallPresenter - { - #region DLL Imports - - [DllImport("__Internal")] - private static extern void initializeRevenueCatUI(); - - [DllImport("__Internal")] - private static extern void presentPaywall(string offeringIdentifier, bool displayCloseButton, PaywallResultCallback callback); - - [DllImport("__Internal")] - private static extern void presentPaywallIfNeeded(string requiredEntitlementIdentifier, string offeringIdentifier, bool displayCloseButton, PaywallResultCallback callback); - - [DllImport("__Internal")] - private static extern bool isRevenueCatUISupported(); - - private delegate void PaywallResultCallback(string result); - - #endregion - - private TaskCompletionSource currentPaywallTcs; - private static TaskCompletionSource staticCurrentPaywallTcs; - - public IOSPaywallPresenter() - { - // Initialize the native iOS bridge - initializeRevenueCatUI(); - } - - public bool IsSupported() - { - return isRevenueCatUISupported(); - } - - public async Task PresentPaywallAsync(PaywallOptions options) - { - if (currentPaywallTcs != null && !currentPaywallTcs.Task.IsCompleted) - { - Debug.LogWarning("[RevenueCatUI] Another paywall is already being presented. Cancelling previous request."); - currentPaywallTcs.TrySetCanceled(); - } - - currentPaywallTcs = new TaskCompletionSource(); - staticCurrentPaywallTcs = currentPaywallTcs; // Store for static callback - - try - { - Debug.Log($"[RevenueCatUI] Presenting paywall with offering: {options?.OfferingIdentifier ?? "default"}, displayCloseButton: {options?.DisplayCloseButton ?? false}"); - - presentPaywall( - options?.OfferingIdentifier, - options?.DisplayCloseButton ?? false, - OnPaywallResult - ); - - return await currentPaywallTcs.Task; - } - catch (Exception ex) - { - Debug.LogError($"[RevenueCatUI] Error presenting paywall: {ex.Message}"); - currentPaywallTcs?.TrySetException(ex); - return PaywallResult.Error; - } - } - - public async Task PresentPaywallIfNeededAsync(string requiredEntitlementIdentifier, PaywallOptions options) - { - if (string.IsNullOrEmpty(requiredEntitlementIdentifier)) - { - Debug.LogError("[RevenueCatUI] requiredEntitlementIdentifier cannot be null or empty for PresentPaywallIfNeededAsync"); - return PaywallResult.Error; - } - - if (currentPaywallTcs != null && !currentPaywallTcs.Task.IsCompleted) - { - Debug.LogWarning("[RevenueCatUI] Another paywall is already being presented. Cancelling previous request."); - currentPaywallTcs.TrySetCanceled(); - } - - currentPaywallTcs = new TaskCompletionSource(); - staticCurrentPaywallTcs = currentPaywallTcs; // Store for static callback - - try - { - Debug.Log($"[RevenueCatUI] Presenting paywall if needed for entitlement: {requiredEntitlementIdentifier}, offering: {options?.OfferingIdentifier ?? "default"}, displayCloseButton: {options?.DisplayCloseButton ?? false}"); - - presentPaywallIfNeeded( - requiredEntitlementIdentifier, - options?.OfferingIdentifier, - options?.DisplayCloseButton ?? false, - OnPaywallResult - ); - - return await currentPaywallTcs.Task; - } - catch (Exception ex) - { - Debug.LogError($"[RevenueCatUI] Error presenting conditional paywall: {ex.Message}"); - currentPaywallTcs?.TrySetException(ex); - return PaywallResult.Error; - } - } - - [AOT.MonoPInvokeCallback(typeof(PaywallResultCallback))] - private static void OnPaywallResult(string result) - { - Debug.Log($"[RevenueCatUI] Paywall result received: {result}"); - - var currentTcs = staticCurrentPaywallTcs; - if (currentTcs != null) - { - HandlePaywallResult(currentTcs, result); - staticCurrentPaywallTcs = null; // Clear after handling - } - } - - private static void HandlePaywallResult(TaskCompletionSource tcs, string result) - { - try - { - if (string.IsNullOrEmpty(result)) - { - tcs.TrySetResult(PaywallResult.Error); - return; - } - - // Handle error responses - if (result.Contains("error") || result.Contains("Error")) - { - Debug.LogError($"[RevenueCatUI] Paywall error: {result}"); - tcs.TrySetResult(PaywallResult.Error); - return; - } - - // Parse result based on expected formats from PaywallProxy - // The actual result format depends on RevenueCat UI implementation - // Common results: purchased, cancelled, restored, not_needed - var lowerResult = result.ToLower(); - - if (lowerResult.Contains("purchased") || lowerResult.Contains("success")) - { - tcs.TrySetResult(PaywallResult.Purchased); - } - else if (lowerResult.Contains("cancelled") || lowerResult.Contains("cancel")) - { - tcs.TrySetResult(PaywallResult.Cancelled); - } - else if (lowerResult.Contains("restored") || lowerResult.Contains("restore")) - { - tcs.TrySetResult(PaywallResult.Restored); - } - else if (lowerResult.Contains("not_needed") || lowerResult.Contains("not needed")) - { - tcs.TrySetResult(PaywallResult.NotNeeded); - } - else - { - Debug.LogWarning($"[RevenueCatUI] Unknown paywall result: {result}, defaulting to Cancelled"); - tcs.TrySetResult(PaywallResult.Cancelled); - } - } - catch (Exception ex) - { - Debug.LogError($"[RevenueCatUI] Error handling paywall result: {ex.Message}"); - tcs.TrySetResult(PaywallResult.Error); - } - } - } -} -#endif diff --git a/RevenueCatUI/Runtime/Platforms/iOS/IOSPaywallPresenter.cs.meta b/RevenueCatUI/Runtime/Platforms/iOS/IOSPaywallPresenter.cs.meta deleted file mode 100644 index 14dca4e4..00000000 --- a/RevenueCatUI/Runtime/Platforms/iOS/IOSPaywallPresenter.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 3f43077ff755543a5b17a0b90808573d -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: From 0a407d2092f85d88ecafb6aedb6d107b53d6091f Mon Sep 17 00:00:00 2001 From: Facundo Menzella Date: Wed, 10 Sep 2025 13:58:25 +0200 Subject: [PATCH 03/65] Add minimum --- .../{Runtime/Examples.meta => Plugins.meta} | 2 +- RevenueCatUI/Plugins/Android.meta | 8 + RevenueCatUI/Plugins/Android/minimal.meta | 8 + .../Android/minimal/AndroidManifest.xml | 8 + .../Android/minimal/AndroidManifest.xml.meta | 7 + .../Plugins/Android/minimal/build.gradle | 20 ++ .../Plugins/Android/minimal/build.gradle.meta | 7 + .../Android/minimal/proguard-rules.pro | 3 + .../Android/minimal/proguard-rules.pro.meta | 7 + .../Plugins/Android/{ => minimal}/src.meta | 2 +- .../Android/{ => minimal}/src/main.meta | 2 +- .../Android/{ => minimal}/src/main/java.meta | 2 +- .../{ => minimal}/src/main/java/com.meta | 2 +- .../src/main/java/com/revenuecat.meta | 2 +- .../src/main/java/com/revenuecat/unity.meta | 2 +- .../com/revenuecat/unity/RevenueCatUI.java | 37 +++ .../revenuecat/unity/RevenueCatUI.java.meta | 2 + RevenueCatUI/Plugins/iOS.meta | 8 + RevenueCatUI/Plugins/iOS/RevenueCatUI.m | 29 +++ RevenueCatUI/Plugins/iOS/RevenueCatUI.m.meta | 2 + RevenueCatUI/README.md | 110 ++++----- .../Examples/RevenueCatUICompleteExample.cs | 213 ------------------ .../RevenueCatUICompleteExample.cs.meta | 11 - .../Runtime/Examples/RevenueCatUIExample.cs | 183 --------------- .../Examples/RevenueCatUIExample.cs.meta | 11 - .../Internal/CustomerCenterPresenter.cs | 6 +- .../Runtime/Internal/PaywallPresenter.cs | 5 +- .../Internal/RevenueCatUICallbackHandler.cs | 6 +- RevenueCatUI/Runtime/Platforms/Android.meta | 8 + .../Android/AndroidCustomerCenterPresenter.cs | 42 ++++ .../AndroidCustomerCenterPresenter.cs.meta | 2 + .../Android/AndroidPaywallPresenter.cs | 74 ++++++ .../Android/AndroidPaywallPresenter.cs.meta | 2 + .../Stub/StubCustomerCenterPresenter.cs | 24 -- .../Stub/StubCustomerCenterPresenter.cs.meta | 11 - .../iOS/IOSCustomerCenterPresenter.cs | 33 +++ .../iOS/IOSCustomerCenterPresenter.cs.meta | 2 + .../Platforms/iOS/IOSPaywallPresenter.cs | 45 ++++ .../Platforms/iOS/IOSPaywallPresenter.cs.meta | 2 + .../ProjectSettings/GvhProjectSettings.xml | 28 ++- 40 files changed, 438 insertions(+), 540 deletions(-) rename RevenueCatUI/{Runtime/Examples.meta => Plugins.meta} (77%) create mode 100644 RevenueCatUI/Plugins/Android.meta create mode 100644 RevenueCatUI/Plugins/Android/minimal.meta create mode 100644 RevenueCatUI/Plugins/Android/minimal/AndroidManifest.xml create mode 100644 RevenueCatUI/Plugins/Android/minimal/AndroidManifest.xml.meta create mode 100644 RevenueCatUI/Plugins/Android/minimal/build.gradle create mode 100644 RevenueCatUI/Plugins/Android/minimal/build.gradle.meta create mode 100644 RevenueCatUI/Plugins/Android/minimal/proguard-rules.pro create mode 100644 RevenueCatUI/Plugins/Android/minimal/proguard-rules.pro.meta rename RevenueCatUI/Plugins/Android/{ => minimal}/src.meta (77%) rename RevenueCatUI/Plugins/Android/{ => minimal}/src/main.meta (77%) rename RevenueCatUI/Plugins/Android/{ => minimal}/src/main/java.meta (77%) rename RevenueCatUI/Plugins/Android/{ => minimal}/src/main/java/com.meta (77%) rename RevenueCatUI/Plugins/Android/{ => minimal}/src/main/java/com/revenuecat.meta (77%) rename RevenueCatUI/Plugins/Android/{ => minimal}/src/main/java/com/revenuecat/unity.meta (77%) create mode 100644 RevenueCatUI/Plugins/Android/minimal/src/main/java/com/revenuecat/unity/RevenueCatUI.java create mode 100644 RevenueCatUI/Plugins/Android/minimal/src/main/java/com/revenuecat/unity/RevenueCatUI.java.meta create mode 100644 RevenueCatUI/Plugins/iOS.meta create mode 100644 RevenueCatUI/Plugins/iOS/RevenueCatUI.m create mode 100644 RevenueCatUI/Plugins/iOS/RevenueCatUI.m.meta delete mode 100644 RevenueCatUI/Runtime/Examples/RevenueCatUICompleteExample.cs delete mode 100644 RevenueCatUI/Runtime/Examples/RevenueCatUICompleteExample.cs.meta delete mode 100644 RevenueCatUI/Runtime/Examples/RevenueCatUIExample.cs delete mode 100644 RevenueCatUI/Runtime/Examples/RevenueCatUIExample.cs.meta create mode 100644 RevenueCatUI/Runtime/Platforms/Android.meta create mode 100644 RevenueCatUI/Runtime/Platforms/Android/AndroidCustomerCenterPresenter.cs create mode 100644 RevenueCatUI/Runtime/Platforms/Android/AndroidCustomerCenterPresenter.cs.meta create mode 100644 RevenueCatUI/Runtime/Platforms/Android/AndroidPaywallPresenter.cs create mode 100644 RevenueCatUI/Runtime/Platforms/Android/AndroidPaywallPresenter.cs.meta delete mode 100644 RevenueCatUI/Runtime/Platforms/Stub/StubCustomerCenterPresenter.cs delete mode 100644 RevenueCatUI/Runtime/Platforms/Stub/StubCustomerCenterPresenter.cs.meta create mode 100644 RevenueCatUI/Runtime/Platforms/iOS/IOSCustomerCenterPresenter.cs create mode 100644 RevenueCatUI/Runtime/Platforms/iOS/IOSCustomerCenterPresenter.cs.meta create mode 100644 RevenueCatUI/Runtime/Platforms/iOS/IOSPaywallPresenter.cs create mode 100644 RevenueCatUI/Runtime/Platforms/iOS/IOSPaywallPresenter.cs.meta diff --git a/RevenueCatUI/Runtime/Examples.meta b/RevenueCatUI/Plugins.meta similarity index 77% rename from RevenueCatUI/Runtime/Examples.meta rename to RevenueCatUI/Plugins.meta index 3c1efe57..87a8a2ee 100644 --- a/RevenueCatUI/Runtime/Examples.meta +++ b/RevenueCatUI/Plugins.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: af8cddb254f094f3a984c7cdd42000cd +guid: ebbdc005f0bc94b24be9b3d15f2d58db folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/RevenueCatUI/Plugins/Android.meta b/RevenueCatUI/Plugins/Android.meta new file mode 100644 index 00000000..80c4953e --- /dev/null +++ b/RevenueCatUI/Plugins/Android.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 6b10b69268c67402da4a4d5750cd5ffa +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/RevenueCatUI/Plugins/Android/minimal.meta b/RevenueCatUI/Plugins/Android/minimal.meta new file mode 100644 index 00000000..b5ebfc27 --- /dev/null +++ b/RevenueCatUI/Plugins/Android/minimal.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 801186ab2cd0b492a9e5333515c108bc +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/RevenueCatUI/Plugins/Android/minimal/AndroidManifest.xml b/RevenueCatUI/Plugins/Android/minimal/AndroidManifest.xml new file mode 100644 index 00000000..4fac66a6 --- /dev/null +++ b/RevenueCatUI/Plugins/Android/minimal/AndroidManifest.xml @@ -0,0 +1,8 @@ + + + + + + + diff --git a/RevenueCatUI/Plugins/Android/minimal/AndroidManifest.xml.meta b/RevenueCatUI/Plugins/Android/minimal/AndroidManifest.xml.meta new file mode 100644 index 00000000..fb4f42e3 --- /dev/null +++ b/RevenueCatUI/Plugins/Android/minimal/AndroidManifest.xml.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 7376be9be6daa4aadb2f8074b5d28eee +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/RevenueCatUI/Plugins/Android/minimal/build.gradle b/RevenueCatUI/Plugins/Android/minimal/build.gradle new file mode 100644 index 00000000..436e7198 --- /dev/null +++ b/RevenueCatUI/Plugins/Android/minimal/build.gradle @@ -0,0 +1,20 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion 33 + defaultConfig { + minSdkVersion 21 + targetSdkVersion 33 + consumerProguardFiles 'proguard-rules.pro' + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +dependencies { + // No external dependencies for the minimal stub +} + diff --git a/RevenueCatUI/Plugins/Android/minimal/build.gradle.meta b/RevenueCatUI/Plugins/Android/minimal/build.gradle.meta new file mode 100644 index 00000000..9bea0710 --- /dev/null +++ b/RevenueCatUI/Plugins/Android/minimal/build.gradle.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: d92c6e421cec541c4850ee85173daf79 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/RevenueCatUI/Plugins/Android/minimal/proguard-rules.pro b/RevenueCatUI/Plugins/Android/minimal/proguard-rules.pro new file mode 100644 index 00000000..c9e6560d --- /dev/null +++ b/RevenueCatUI/Plugins/Android/minimal/proguard-rules.pro @@ -0,0 +1,3 @@ +# Keep the UnitySendMessage references +-keep class com.unity3d.player.UnityPlayer { *; } + diff --git a/RevenueCatUI/Plugins/Android/minimal/proguard-rules.pro.meta b/RevenueCatUI/Plugins/Android/minimal/proguard-rules.pro.meta new file mode 100644 index 00000000..8f9d92f4 --- /dev/null +++ b/RevenueCatUI/Plugins/Android/minimal/proguard-rules.pro.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 8fa508341800c4713a6c76423e5fd8e3 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/RevenueCatUI/Plugins/Android/src.meta b/RevenueCatUI/Plugins/Android/minimal/src.meta similarity index 77% rename from RevenueCatUI/Plugins/Android/src.meta rename to RevenueCatUI/Plugins/Android/minimal/src.meta index e7ba918d..b4a0a448 100644 --- a/RevenueCatUI/Plugins/Android/src.meta +++ b/RevenueCatUI/Plugins/Android/minimal/src.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 58f0e6ae8c5614cafbaabc7e8eea30f9 +guid: 0c49cf57f3c154d3783c927be059b0e6 folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/RevenueCatUI/Plugins/Android/src/main.meta b/RevenueCatUI/Plugins/Android/minimal/src/main.meta similarity index 77% rename from RevenueCatUI/Plugins/Android/src/main.meta rename to RevenueCatUI/Plugins/Android/minimal/src/main.meta index c9fbbc2f..3fba932f 100644 --- a/RevenueCatUI/Plugins/Android/src/main.meta +++ b/RevenueCatUI/Plugins/Android/minimal/src/main.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 7d22fa946b31b46dbbce37b55960c4fa +guid: d01209303c5124fb4a62ba32c94f7579 folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/RevenueCatUI/Plugins/Android/src/main/java.meta b/RevenueCatUI/Plugins/Android/minimal/src/main/java.meta similarity index 77% rename from RevenueCatUI/Plugins/Android/src/main/java.meta rename to RevenueCatUI/Plugins/Android/minimal/src/main/java.meta index 5eefe323..ec81b8c3 100644 --- a/RevenueCatUI/Plugins/Android/src/main/java.meta +++ b/RevenueCatUI/Plugins/Android/minimal/src/main/java.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: d802b640849df4118b4cf2c315d23570 +guid: 93debef0bb0d54c248c9ba3b92b67346 folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/RevenueCatUI/Plugins/Android/src/main/java/com.meta b/RevenueCatUI/Plugins/Android/minimal/src/main/java/com.meta similarity index 77% rename from RevenueCatUI/Plugins/Android/src/main/java/com.meta rename to RevenueCatUI/Plugins/Android/minimal/src/main/java/com.meta index e25de037..5af49093 100644 --- a/RevenueCatUI/Plugins/Android/src/main/java/com.meta +++ b/RevenueCatUI/Plugins/Android/minimal/src/main/java/com.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: eb85ab56e19354607a0a5896d7d08885 +guid: f3945fc672ec549e1b2da93bd9611a25 folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/RevenueCatUI/Plugins/Android/src/main/java/com/revenuecat.meta b/RevenueCatUI/Plugins/Android/minimal/src/main/java/com/revenuecat.meta similarity index 77% rename from RevenueCatUI/Plugins/Android/src/main/java/com/revenuecat.meta rename to RevenueCatUI/Plugins/Android/minimal/src/main/java/com/revenuecat.meta index f3b2d6e1..0d8f2b1c 100644 --- a/RevenueCatUI/Plugins/Android/src/main/java/com/revenuecat.meta +++ b/RevenueCatUI/Plugins/Android/minimal/src/main/java/com/revenuecat.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: d85f5936727a84c5e87378f1bfe2e414 +guid: 6421f44f8ca884f469609b6374f69235 folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/RevenueCatUI/Plugins/Android/src/main/java/com/revenuecat/unity.meta b/RevenueCatUI/Plugins/Android/minimal/src/main/java/com/revenuecat/unity.meta similarity index 77% rename from RevenueCatUI/Plugins/Android/src/main/java/com/revenuecat/unity.meta rename to RevenueCatUI/Plugins/Android/minimal/src/main/java/com/revenuecat/unity.meta index 287ca030..ea8b9fb7 100644 --- a/RevenueCatUI/Plugins/Android/src/main/java/com/revenuecat/unity.meta +++ b/RevenueCatUI/Plugins/Android/minimal/src/main/java/com/revenuecat/unity.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 6e5b37958fc5346b2a411128b899edbc +guid: 4b7afd2c7b9d143d8beebc2413076295 folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/RevenueCatUI/Plugins/Android/minimal/src/main/java/com/revenuecat/unity/RevenueCatUI.java b/RevenueCatUI/Plugins/Android/minimal/src/main/java/com/revenuecat/unity/RevenueCatUI.java new file mode 100644 index 00000000..5aeeeed6 --- /dev/null +++ b/RevenueCatUI/Plugins/Android/minimal/src/main/java/com/revenuecat/unity/RevenueCatUI.java @@ -0,0 +1,37 @@ +package com.revenuecat.unity; + +import com.unity3d.player.UnityPlayer; + +public class RevenueCatUI { + + private static final String UNITY_CALLBACK_OBJECT = "RevenueCatUI"; + + public static void presentPaywall(String offeringIdentifier) { + sendPaywallResult("CANCELLED|Stub: no native UI"); + } + + public static void presentPaywallIfNeeded(String requiredEntitlementIdentifier, String offeringIdentifier) { + sendPaywallResult("NOT_PRESENTED|Stub: no native UI"); + } + + public static void presentCustomerCenter() { + sendCustomerCenterResult("DONE|Stub: no native UI"); + } + + public static boolean isSupported() { + return true; + } + + private static void sendPaywallResult(String result) { + try { + UnityPlayer.UnitySendMessage(UNITY_CALLBACK_OBJECT, "OnPaywallResult", result); + } catch (Throwable ignored) {} + } + + private static void sendCustomerCenterResult(String result) { + try { + UnityPlayer.UnitySendMessage(UNITY_CALLBACK_OBJECT, "OnCustomerCenterResult", result); + } catch (Throwable ignored) {} + } +} + diff --git a/RevenueCatUI/Plugins/Android/minimal/src/main/java/com/revenuecat/unity/RevenueCatUI.java.meta b/RevenueCatUI/Plugins/Android/minimal/src/main/java/com/revenuecat/unity/RevenueCatUI.java.meta new file mode 100644 index 00000000..6cf3f842 --- /dev/null +++ b/RevenueCatUI/Plugins/Android/minimal/src/main/java/com/revenuecat/unity/RevenueCatUI.java.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 92bed5dedded04c4cafd2b494600ad84 \ No newline at end of file diff --git a/RevenueCatUI/Plugins/iOS.meta b/RevenueCatUI/Plugins/iOS.meta new file mode 100644 index 00000000..bfee2745 --- /dev/null +++ b/RevenueCatUI/Plugins/iOS.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7409590fc13bc435ca37a81a3964c912 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/RevenueCatUI/Plugins/iOS/RevenueCatUI.m b/RevenueCatUI/Plugins/iOS/RevenueCatUI.m new file mode 100644 index 00000000..4a0b0a1c --- /dev/null +++ b/RevenueCatUI/Plugins/iOS/RevenueCatUI.m @@ -0,0 +1,29 @@ +#import + +// Minimal native stubs for iOS bridging + +typedef void (*RCUIPaywallResultCallback)(const char* result); +typedef void (*RCUICustomerCenterCallback)(void); + +void rcui_presentPaywall(const char* offeringIdentifier, bool displayCloseButton, RCUIPaywallResultCallback callback) { + if (callback) { + callback("CANCELLED|Stub: no native UI"); + } +} + +void rcui_presentPaywallIfNeeded(const char* requiredEntitlementIdentifier, const char* offeringIdentifier, bool displayCloseButton, RCUIPaywallResultCallback callback) { + if (callback) { + callback("NOT_PRESENTED|Stub: no native UI"); + } +} + +void rcui_presentCustomerCenter(RCUICustomerCenterCallback callback) { + if (callback) { + callback(); + } +} + +bool rcui_isSupported() { + return true; +} + diff --git a/RevenueCatUI/Plugins/iOS/RevenueCatUI.m.meta b/RevenueCatUI/Plugins/iOS/RevenueCatUI.m.meta new file mode 100644 index 00000000..599306e3 --- /dev/null +++ b/RevenueCatUI/Plugins/iOS/RevenueCatUI.m.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 6f596540f84544a1582c80e4f69a709f \ No newline at end of file diff --git a/RevenueCatUI/README.md b/RevenueCatUI/README.md index 7773fcbe..6a21c376 100644 --- a/RevenueCatUI/README.md +++ b/RevenueCatUI/README.md @@ -1,31 +1,23 @@ -# RevenueCat UI for Unity +# RevenueCat UI for Unity (Stub Package) -Unity UI components (paywalls and customer center) for RevenueCat's subscription management. +This package provides the Unity API surface for RevenueCat UI (paywalls and customer center), +implemented with lightweight stubs so you can wire your flows without native dependencies. -## Features - -- 🎨 **Native Paywalls**: Present beautiful, native paywalls -- 🏪 **Customer Center**: Manage subscriptions and customer support -- 🔄 **Unity Integration**: Clean, async/await APIs following Unity conventions -- 📱 **Mobile-First**: iOS and Android support -- 🎯 **Production Ready**: Built on proven purchases-hybrid-common-ui +Use this to integrate and test control flow in Editor and on devices. Device builds route +through minimal native stubs; Editor runs C# fallbacks. When ready, replace the minimal +native code with your real implementations. ## Installation -### Option 1: Git URL (Development Branch) - -Since this package is currently on the `ui-test_v1` branch, install via: - -``` -https://github.com/RevenueCat/purchases-unity.git?path=RevenueCatUI#ui-test_v1 -``` - -### Option 2: Git URL (After Merge to Main) - -Once merged to main, you can use: +Add to your Unity project's `Packages/manifest.json`: ``` -https://github.com/RevenueCat/purchases-unity.git?path=RevenueCatUI +{ + "dependencies": { + "com.revenuecat.purchases-unity": "file:../RevenueCat", // existing core SDK + "com.revenuecat.purchases-ui-unity": "file:../RevenueCatUI" // this UI package (stub) + } +} ``` ### Requirements @@ -35,9 +27,11 @@ https://github.com/RevenueCat/purchases-unity.git?path=RevenueCatUI ### Dependencies -This package is currently stub-only. It does not include native Android/iOS UI -components or dependency definitions. You can wire your flows against the API in -Editor and on devices (no-ops), and add native UI code separately when ready. +By default this package runs in stub mode. On iOS/Android device builds, the +API routes through minimal native stubs that immediately return results (no +external dependencies). In the Editor and other platforms, C# fallbacks return +the same immediate results. When you’re ready for real UI, replace the minimal +native code with your implementation and add dependencies as needed. ## Quick Start @@ -88,14 +82,12 @@ public class MySubscriptionManager : MonoBehaviour ### RevenueCatUI (Main API) -| Method | Description | -|--------|-------------| -| `PresentPaywall()` | Present paywall with default offering | -| `PresentPaywall(PaywallOptions)` | Present paywall with specific options | -| `PresentPaywallIfNeeded(string)` | Present paywall only if user lacks entitlement | -| `PresentPaywallIfNeeded(string, PaywallOptions)` | Conditional paywall with options | -| `PresentCustomerCenter()` | Present customer center | -| `IsSupported()` | Check if UI is supported on current platform | +Implemented now (stubbed behavior everywhere): +- `PresentPaywall()` — returns `Cancelled` immediately +- `PresentPaywall(PaywallOptions)` — same as above with options +- `PresentPaywallIfNeeded(string[, PaywallOptions])` — returns `NotNeeded` immediately +- `PresentCustomerCenter()` — completes immediately +- `IsSupported()` — returns `true` on devices; in Editor, `Paywall` is `true` and `Customer Center` may be `false` ### PaywallOptions @@ -122,11 +114,13 @@ public enum PaywallResultType ## Architecture -### Native Implementation (optional) -This package ships with stubs only. To enable native UI later: -- Add your Android/iOS plugin code under `Plugins/` and platform presenters under `Runtime/Platforms/`. -- Define `REVENUECAT_UI_NATIVE` in Scripting Define Symbols so the factory uses native presenters. -- Provide dependency specs (EDM4U) in `Plugins/Editor/` when you add native code. +### Native Implementation (reference only) +Minimal native code is provided as a reference scaffold: +- Android class: `com.revenuecat.unity.RevenueCatUI` +- iOS functions: `rcui_presentPaywall`, `rcui_presentPaywallIfNeeded`, `rcui_presentCustomerCenter`, `rcui_isSupported` + +If/when you add real native UI, update the platform presenters in +`RevenueCatUI/Runtime/Platforms/{Android,iOS}` to select your implementations. ### Platform Abstraction - Factory pattern with `IPaywallPresenter` and `ICustomerCenterPresenter` @@ -135,47 +129,23 @@ This package ships with stubs only. To enable native UI later: ## Platform Support -- Editor / Any platform: Stubs return immediate results (no UI) -- Native support: Add your native code and set `REVENUECAT_UI_NATIVE` +- Editor / Any platform: C# fallbacks return immediate results (no UI) +- iOS/Android device: Minimal native stubs return immediate results (no UI) ## Examples -See `RevenueCatUIExample.cs` for complete examples including: -- Basic paywall presentation -- Conditional paywalls based on entitlements -- Customer center presentation -- Error handling and result processing -- Integration with RevenueCat SDK - -## Troubleshooting +Examples are not included in this stub package. Use the Quick Start snippet above +or your own scene code to exercise the API. -### iOS Issues -- Ensure Xcode project has iOS 15.0+ deployment target -- Verify CocoaPods dependencies are resolved -- Check that `PurchasesHybridCommonUI` is included +## Troubleshooting (stub mode) -### Android Issues -- Verify Unity activity extends `FragmentActivity` -- Check that `purchases-hybrid-common-ui` is in dependencies -- Ensure minimum API level 24 - -### General - Call `RevenueCatUI.IsSupported()` before presenting UI -- Initialize RevenueCat SDK before using UI components -- Check Unity console for debug logs with `[RevenueCatUI]` prefix - -## Stub Mode (No Native Dependencies) - -To integrate the API without pulling native UI dependencies yet, enable stub mode: +- Initialize the core RevenueCat SDK before using UI components +- Check Unity console for logs tagged `[RevenueCatUI]` -- Add scripting define symbol `REVENUECAT_UI_STUBS` in Unity Player Settings. -- Behavior in stub mode: - - `PresentPaywall()` returns `Cancelled` immediately. - - `PresentPaywallIfNeeded(...)` returns `NotNeeded` immediately. - - `PresentCustomerCenter()` completes immediately. - - `IsSupported()` returns `true` so you can wire flows. +## Modes -Remove the define once you are ready to resolve Android/iOS dependencies and ship the real UI. +Single mode: stub behavior on all platforms (native stubs on devices, C# fallbacks in Editor). ## License diff --git a/RevenueCatUI/Runtime/Examples/RevenueCatUICompleteExample.cs b/RevenueCatUI/Runtime/Examples/RevenueCatUICompleteExample.cs deleted file mode 100644 index 7b6e3800..00000000 --- a/RevenueCatUI/Runtime/Examples/RevenueCatUICompleteExample.cs +++ /dev/null @@ -1,213 +0,0 @@ -using System; -using System.Threading.Tasks; -using UnityEngine; -using RevenueCat.UI; - -namespace RevenueCat.UI.Examples -{ - /// - /// Complete example demonstrating RevenueCat UI functionality. - /// This script shows how to use paywalls and customer center after the iOS implementation is complete. - /// - public class RevenueCatUICompleteExample : MonoBehaviour - { - [Header("Paywall Configuration")] - public string offeringIdentifier = ""; // Leave empty for default offering - public bool displayCloseButton = true; - - [Header("Conditional Paywall")] - public string requiredEntitlementIdentifier = "premium"; // Example entitlement ID - - [Header("UI")] - public UnityEngine.UI.Button paywallButton; - public UnityEngine.UI.Button conditionalPaywallButton; - public UnityEngine.UI.Button customerCenterButton; - public UnityEngine.UI.Text statusText; - - private void Start() - { - // Setup button listeners - if (paywallButton != null) - paywallButton.onClick.AddListener(OnPresentPaywallClicked); - - if (conditionalPaywallButton != null) - conditionalPaywallButton.onClick.AddListener(OnPresentConditionalPaywallClicked); - - if (customerCenterButton != null) - customerCenterButton.onClick.AddListener(OnPresentCustomerCenterClicked); - - // Check platform support - bool isSupported = RevenueCatUI.IsSupported(); - UpdateStatus($"RevenueCat UI Supported: {isSupported}"); - - // Enable/disable buttons based on support - if (paywallButton != null) paywallButton.interactable = isSupported; - if (conditionalPaywallButton != null) conditionalPaywallButton.interactable = isSupported; - if (customerCenterButton != null) customerCenterButton.interactable = isSupported; - - if (!isSupported) - { - Debug.LogWarning("[RevenueCatUI] Platform not supported. UI components will be disabled."); - } - } - - /// - /// Present a paywall with the configured options. - /// - public async void OnPresentPaywallClicked() - { - UpdateStatus("Presenting paywall..."); - - var options = new PaywallOptions - { - OfferingIdentifier = string.IsNullOrEmpty(offeringIdentifier) ? null : offeringIdentifier, - DisplayCloseButton = displayCloseButton - }; - - try - { - var result = await RevenueCatUI.PresentPaywall(options); - HandlePaywallResult(result); - } - catch (Exception e) - { - UpdateStatus($"Error presenting paywall: {e.Message}"); - Debug.LogError($"[RevenueCatUI] Paywall presentation error: {e}"); - } - } - - /// - /// Present a paywall only if the user doesn't have the required entitlement. - /// - public async void OnPresentConditionalPaywallClicked() - { - if (string.IsNullOrEmpty(requiredEntitlementIdentifier)) - { - UpdateStatus("Error: Please set a required entitlement identifier"); - return; - } - - UpdateStatus($"Checking entitlement '{requiredEntitlementIdentifier}' and presenting paywall if needed..."); - - var options = new PaywallOptions - { - OfferingIdentifier = string.IsNullOrEmpty(offeringIdentifier) ? null : offeringIdentifier, - DisplayCloseButton = displayCloseButton - }; - - try - { - var result = await RevenueCatUI.PresentPaywallIfNeeded(requiredEntitlementIdentifier, options); - HandlePaywallResult(result); - } - catch (Exception e) - { - UpdateStatus($"Error presenting conditional paywall: {e.Message}"); - Debug.LogError($"[RevenueCatUI] Conditional paywall presentation error: {e}"); - } - } - - /// - /// Present the customer center for subscription management. - /// - public async void OnPresentCustomerCenterClicked() - { - UpdateStatus("Presenting customer center..."); - - try - { - await RevenueCatUI.PresentCustomerCenter(); - UpdateStatus("Customer center dismissed"); - } - catch (Exception e) - { - UpdateStatus($"Error presenting customer center: {e.Message}"); - Debug.LogError($"[RevenueCatUI] Customer center presentation error: {e}"); - } - } - - /// - /// Handle the result from paywall presentation. - /// - private void HandlePaywallResult(PaywallResult result) - { - string message = result.Result switch - { - PaywallResultType.NotPresented => "Paywall not presented (user may already have entitlement)", - PaywallResultType.Cancelled => "User cancelled the paywall", - PaywallResultType.Error => "An error occurred during paywall presentation", - PaywallResultType.Purchased => "User completed a purchase! 🎉", - PaywallResultType.Restored => "User restored their purchases! ✨", - _ => $"Unknown result: {result.Result}" - }; - - UpdateStatus($"Paywall result: {message}"); - Debug.Log($"[RevenueCatUI] Paywall result: {result}"); - - // Handle successful transactions - if (result.Result == PaywallResultType.Purchased || result.Result == PaywallResultType.Restored) - { - OnSuccessfulTransaction(result.Result); - } - } - - /// - /// Called when a transaction is successful (purchase or restore). - /// - private void OnSuccessfulTransaction(PaywallResultType transactionType) - { - Debug.Log($"[RevenueCatUI] Successful transaction: {transactionType}"); - - // Here you would typically: - // 1. Update your UI to reflect the new entitlements - // 2. Unlock premium features - // 3. Show a success message - // 4. Refresh customer info if needed - - // Example: - // await RefreshCustomerInfo(); - // EnablePremiumFeatures(); - // ShowSuccessMessage(); - } - - /// - /// Update the status text (if available). - /// - private void UpdateStatus(string message) - { - if (statusText != null) - { - statusText.text = message; - } - - Debug.Log($"[RevenueCatUI] Status: {message}"); - } - - /// - /// Test method to validate the complete implementation. - /// Call this from a button or in your test suite. - /// - [ContextMenu("Test RevenueCat UI Implementation")] - public void TestImplementation() - { - Debug.Log("=== RevenueCat UI Implementation Test ==="); - - // Test platform support - bool isSupported = RevenueCatUI.IsSupported(); - Debug.Log($"Platform supported: {isSupported}"); - - if (!isSupported) - { - Debug.LogWarning("Platform not supported. Cannot test further."); - return; - } - - Debug.Log("✅ RevenueCat UI implementation appears complete!"); - Debug.Log("You can now:"); - Debug.Log("- Present paywalls with RevenueCatUI.PresentPaywall()"); - Debug.Log("- Present conditional paywalls with RevenueCatUI.PresentPaywallIfNeeded()"); - Debug.Log("- Present customer center with RevenueCatUI.PresentCustomerCenter()"); - Debug.Log("- Handle results with proper async/await patterns"); - } - } -} \ No newline at end of file diff --git a/RevenueCatUI/Runtime/Examples/RevenueCatUICompleteExample.cs.meta b/RevenueCatUI/Runtime/Examples/RevenueCatUICompleteExample.cs.meta deleted file mode 100644 index 5508b528..00000000 --- a/RevenueCatUI/Runtime/Examples/RevenueCatUICompleteExample.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: abaeb905f65af458687780388d0d2435 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/RevenueCatUI/Runtime/Examples/RevenueCatUIExample.cs b/RevenueCatUI/Runtime/Examples/RevenueCatUIExample.cs deleted file mode 100644 index b87f9d54..00000000 --- a/RevenueCatUI/Runtime/Examples/RevenueCatUIExample.cs +++ /dev/null @@ -1,183 +0,0 @@ -using UnityEngine; -using RevenueCat; -using System.Threading.Tasks; - -namespace RevenueCat.UI.Examples -{ - /// - /// Example script demonstrating how to use RevenueCat UI components. - /// Attach this to a GameObject and call the public methods from UI buttons. - /// - public class RevenueCatUIExample : MonoBehaviour - { - [Header("Configuration")] - [SerializeField] private string offeringIdentifier = "premium"; - [SerializeField] private string requiredEntitlementIdentifier = "pro_access"; - - /// - /// Example: Present a paywall with default settings - /// - public async void ShowPaywall() - { - Debug.Log("[RevenueCatUIExample] Presenting paywall..."); - - if (!RevenueCatUI.IsSupported()) - { - Debug.LogWarning("[RevenueCatUIExample] RevenueCat UI is not supported on this platform"); - return; - } - - try - { - var result = await RevenueCatUI.PresentPaywall(); - HandlePaywallResult(result); - } - catch (System.Exception e) - { - Debug.LogError($"[RevenueCatUIExample] Error presenting paywall: {e.Message}"); - } - } - - /// - /// Example: Present a paywall with specific offering and close button - /// - public async void ShowPaywallWithOptions() - { - Debug.Log($"[RevenueCatUIExample] Presenting paywall with offering: {offeringIdentifier}"); - - if (!RevenueCatUI.IsSupported()) - { - Debug.LogWarning("[RevenueCatUIExample] RevenueCat UI is not supported on this platform"); - return; - } - - try - { - var options = new PaywallOptions(offeringIdentifier, displayCloseButton: true); - var result = await RevenueCatUI.PresentPaywall(options); - HandlePaywallResult(result); - } - catch (System.Exception e) - { - Debug.LogError($"[RevenueCatUIExample] Error presenting paywall with options: {e.Message}"); - } - } - - /// - /// Example: Present paywall only if user doesn't have specific entitlement - /// - public async void ShowPaywallIfNeeded() - { - Debug.Log($"[RevenueCatUIExample] Checking for entitlement '{requiredEntitlementIdentifier}'..."); - - if (!RevenueCatUI.IsSupported()) - { - Debug.LogWarning("[RevenueCatUIExample] RevenueCat UI is not supported on this platform"); - return; - } - - try - { - var options = new PaywallOptions(offeringIdentifier, displayCloseButton: true); - var result = await RevenueCatUI.PresentPaywallIfNeeded(requiredEntitlementIdentifier, options); - - if (result.Result == PaywallResultType.NotPresented) - { - Debug.Log($"[RevenueCatUIExample] User already has '{requiredEntitlementIdentifier}' entitlement!"); - } - else - { - HandlePaywallResult(result); - } - } - catch (System.Exception e) - { - Debug.LogError($"[RevenueCatUIExample] Error presenting conditional paywall: {e.Message}"); - } - } - - /// - /// Example: Present customer center - /// - public async void ShowCustomerCenter() - { - Debug.Log("[RevenueCatUIExample] Presenting customer center..."); - - if (!RevenueCatUI.IsSupported()) - { - Debug.LogWarning("[RevenueCatUIExample] RevenueCat UI is not supported on this platform"); - return; - } - - try - { - await RevenueCatUI.PresentCustomerCenter(); - Debug.Log("[RevenueCatUIExample] Customer center was dismissed"); - } - catch (System.Exception e) - { - Debug.LogError($"[RevenueCatUIExample] Error presenting customer center: {e.Message}"); - } - } - - /// - /// Handle the result of a paywall presentation - /// - private void HandlePaywallResult(PaywallResult result) - { - switch (result.Result) - { - case PaywallResultType.Purchased: - Debug.Log("[RevenueCatUIExample] ✅ User made a purchase!"); - OnPurchaseCompleted(); - break; - - case PaywallResultType.Restored: - Debug.Log("[RevenueCatUIExample] ✅ User restored purchases!"); - OnPurchaseCompleted(); - break; - - case PaywallResultType.Cancelled: - Debug.Log("[RevenueCatUIExample] ❌ User cancelled the paywall"); - break; - - case PaywallResultType.Error: - Debug.LogError("[RevenueCatUIExample] ❌ Error during paywall presentation"); - break; - - default: - Debug.Log($"[RevenueCatUIExample] Paywall result: {result.Result}"); - break; - } - } - - /// - /// Called when a purchase or restore is completed - /// - private void OnPurchaseCompleted() - { - Debug.Log("[RevenueCatUIExample] Purchase completed - unlocking premium features!"); - - // Here you would typically: - // 1. Refresh customer info - // 2. Update UI to reflect new entitlements - // 3. Unlock premium features - // 4. Show success message to user - } - - /// - /// Check platform support on start - /// - void Start() - { - if (RevenueCatUI.IsSupported()) - { - Debug.Log("[RevenueCatUIExample] ✅ RevenueCat UI is supported on this platform"); - } - else - { - Debug.LogWarning("[RevenueCatUIExample] ⚠️ RevenueCat UI is not supported on this platform"); - } - } - } -} \ No newline at end of file diff --git a/RevenueCatUI/Runtime/Examples/RevenueCatUIExample.cs.meta b/RevenueCatUI/Runtime/Examples/RevenueCatUIExample.cs.meta deleted file mode 100644 index d90ce054..00000000 --- a/RevenueCatUI/Runtime/Examples/RevenueCatUIExample.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 57172af6589fa4fca8eed186cf7878b9 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/RevenueCatUI/Runtime/Internal/CustomerCenterPresenter.cs b/RevenueCatUI/Runtime/Internal/CustomerCenterPresenter.cs index 0a1ab71b..10ccf255 100644 --- a/RevenueCatUI/Runtime/Internal/CustomerCenterPresenter.cs +++ b/RevenueCatUI/Runtime/Internal/CustomerCenterPresenter.cs @@ -27,12 +27,12 @@ internal static ICustomerCenterPresenter Instance private static ICustomerCenterPresenter CreatePlatformPresenter() { -#if REVENUECAT_UI_NATIVE && UNITY_IOS && !UNITY_EDITOR +#if UNITY_IOS && !UNITY_EDITOR return new Platforms.IOSCustomerCenterPresenter(); -#elif REVENUECAT_UI_NATIVE && UNITY_ANDROID && !UNITY_EDITOR +#elif UNITY_ANDROID && !UNITY_EDITOR return new Platforms.AndroidCustomerCenterPresenter(); #else - return new Platforms.Stub.StubCustomerCenterPresenter(); + return new UnsupportedCustomerCenterPresenter(); #endif } } diff --git a/RevenueCatUI/Runtime/Internal/PaywallPresenter.cs b/RevenueCatUI/Runtime/Internal/PaywallPresenter.cs index f5094f7b..ec7dba7a 100644 --- a/RevenueCatUI/Runtime/Internal/PaywallPresenter.cs +++ b/RevenueCatUI/Runtime/Internal/PaywallPresenter.cs @@ -27,11 +27,12 @@ internal static IPaywallPresenter Instance private static IPaywallPresenter CreatePlatformPresenter() { -#if REVENUECAT_UI_NATIVE && UNITY_IOS && !UNITY_EDITOR +#if UNITY_IOS && !UNITY_EDITOR return new Platforms.IOSPaywallPresenter(); -#elif REVENUECAT_UI_NATIVE && UNITY_ANDROID && !UNITY_EDITOR +#elif UNITY_ANDROID && !UNITY_EDITOR return new Platforms.AndroidPaywallPresenter(); #else + // Editor/other: fall back to C# stub return new Platforms.Stub.StubPaywallPresenter(); #endif } diff --git a/RevenueCatUI/Runtime/Internal/RevenueCatUICallbackHandler.cs b/RevenueCatUI/Runtime/Internal/RevenueCatUICallbackHandler.cs index dac06dfb..35e712bd 100644 --- a/RevenueCatUI/Runtime/Internal/RevenueCatUICallbackHandler.cs +++ b/RevenueCatUI/Runtime/Internal/RevenueCatUICallbackHandler.cs @@ -9,7 +9,7 @@ internal class RevenueCatUICallbackHandler : MonoBehaviour { private static RevenueCatUICallbackHandler _instance; -#if REVENUECAT_UI_NATIVE && UNITY_ANDROID && !UNITY_EDITOR +#if UNITY_ANDROID && !UNITY_EDITOR private static Platforms.AndroidPaywallPresenter _androidPresenter; private static Platforms.AndroidCustomerCenterPresenter _androidCustomerCenterPresenter; #endif @@ -39,7 +39,7 @@ public static void SetAndroidCustomerCenterPresenter(Platforms.AndroidCustomerCe // Called from Android via UnitySendMessage public void OnPaywallResult(string resultData) { -#if REVENUECAT_UI_NATIVE && UNITY_ANDROID && !UNITY_EDITOR +#if UNITY_ANDROID && !UNITY_EDITOR _androidPresenter?.OnPaywallResult(resultData); #endif } @@ -47,7 +47,7 @@ public void OnPaywallResult(string resultData) // Called from Android via UnitySendMessage public void OnCustomerCenterResult(string resultData) { -#if REVENUECAT_UI_NATIVE && UNITY_ANDROID && !UNITY_EDITOR +#if UNITY_ANDROID && !UNITY_EDITOR _androidCustomerCenterPresenter?.OnCustomerCenterResult(resultData); #endif } diff --git a/RevenueCatUI/Runtime/Platforms/Android.meta b/RevenueCatUI/Runtime/Platforms/Android.meta new file mode 100644 index 00000000..d9109fd9 --- /dev/null +++ b/RevenueCatUI/Runtime/Platforms/Android.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: bbfcf62945ec245d3b025e80eb2a410a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/RevenueCatUI/Runtime/Platforms/Android/AndroidCustomerCenterPresenter.cs b/RevenueCatUI/Runtime/Platforms/Android/AndroidCustomerCenterPresenter.cs new file mode 100644 index 00000000..2e3d88ea --- /dev/null +++ b/RevenueCatUI/Runtime/Platforms/Android/AndroidCustomerCenterPresenter.cs @@ -0,0 +1,42 @@ +#if UNITY_ANDROID && !UNITY_EDITOR +using System; +using System.Threading.Tasks; +using UnityEngine; +using RevenueCat.UI.Internal; + +namespace RevenueCat.UI.Platforms +{ + internal class AndroidCustomerCenterPresenter : ICustomerCenterPresenter + { + private readonly AndroidJavaClass _plugin; + private TaskCompletionSource _current; + + public AndroidCustomerCenterPresenter() + { + _plugin = new AndroidJavaClass("com.revenuecat.unity.RevenueCatUI"); + RevenueCatUICallbackHandler.Initialize(); + RevenueCatUICallbackHandler.SetAndroidCustomerCenterPresenter(this); + } + + public bool IsSupported() + { + try { return _plugin.CallStatic("isSupported"); } + catch { return false; } + } + + public Task PresentCustomerCenterAsync() + { + _current = new TaskCompletionSource(); + try { _plugin.CallStatic("presentCustomerCenter"); } + catch { _current.TrySetResult(false); } + return _current.Task; + } + + // Called from Java via UnitySendMessage + public void OnCustomerCenterResult(string resultData) + { + _current?.TrySetResult(true); + } + } +} +#endif diff --git a/RevenueCatUI/Runtime/Platforms/Android/AndroidCustomerCenterPresenter.cs.meta b/RevenueCatUI/Runtime/Platforms/Android/AndroidCustomerCenterPresenter.cs.meta new file mode 100644 index 00000000..f33ddf85 --- /dev/null +++ b/RevenueCatUI/Runtime/Platforms/Android/AndroidCustomerCenterPresenter.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 9d6151bb80d874557bc6c71b90bc470c \ No newline at end of file diff --git a/RevenueCatUI/Runtime/Platforms/Android/AndroidPaywallPresenter.cs b/RevenueCatUI/Runtime/Platforms/Android/AndroidPaywallPresenter.cs new file mode 100644 index 00000000..6f7c12ca --- /dev/null +++ b/RevenueCatUI/Runtime/Platforms/Android/AndroidPaywallPresenter.cs @@ -0,0 +1,74 @@ +#if UNITY_ANDROID && !UNITY_EDITOR +using System; +using System.Threading.Tasks; +using UnityEngine; +using RevenueCat.UI.Internal; + +namespace RevenueCat.UI.Platforms +{ + internal class AndroidPaywallPresenter : IPaywallPresenter + { + private readonly AndroidJavaClass _plugin; + private TaskCompletionSource _current; + + public AndroidPaywallPresenter() + { + _plugin = new AndroidJavaClass("com.revenuecat.unity.RevenueCatUI"); + RevenueCatUICallbackHandler.Initialize(); + RevenueCatUICallbackHandler.SetAndroidPresenter(this); + } + + public bool IsSupported() + { + try { return _plugin.CallStatic("isSupported"); } + catch { return false; } + } + + public Task PresentPaywallAsync(PaywallOptions options) + { + _current = new TaskCompletionSource(); + try + { + var offering = options?.OfferingIdentifier; + _plugin.CallStatic("presentPaywall", offering); + } + catch (Exception) + { + _current.TrySetResult(PaywallResult.Error); + } + return _current.Task; + } + + public Task PresentPaywallIfNeededAsync(string requiredEntitlementIdentifier, PaywallOptions options) + { + _current = new TaskCompletionSource(); + try + { + var offering = options?.OfferingIdentifier; + _plugin.CallStatic("presentPaywallIfNeeded", requiredEntitlementIdentifier, offering); + } + catch (Exception) + { + _current.TrySetResult(PaywallResult.Error); + } + return _current.Task; + } + + // Called from Java via UnitySendMessage + public void OnPaywallResult(string resultData) + { + if (_current == null) return; + try + { + var token = resultData?.Split('|')[0] ?? "ERROR"; + var type = PaywallResultTypeExtensions.FromNativeString(token); + _current.TrySetResult(new PaywallResult(type)); + } + catch + { + _current.TrySetResult(PaywallResult.Error); + } + } + } +} +#endif diff --git a/RevenueCatUI/Runtime/Platforms/Android/AndroidPaywallPresenter.cs.meta b/RevenueCatUI/Runtime/Platforms/Android/AndroidPaywallPresenter.cs.meta new file mode 100644 index 00000000..ed788598 --- /dev/null +++ b/RevenueCatUI/Runtime/Platforms/Android/AndroidPaywallPresenter.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 824501865fc504ae3bfdcd2b256dc650 \ No newline at end of file diff --git a/RevenueCatUI/Runtime/Platforms/Stub/StubCustomerCenterPresenter.cs b/RevenueCatUI/Runtime/Platforms/Stub/StubCustomerCenterPresenter.cs deleted file mode 100644 index bf8ab14f..00000000 --- a/RevenueCatUI/Runtime/Platforms/Stub/StubCustomerCenterPresenter.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Threading.Tasks; -using UnityEngine; - -namespace RevenueCat.UI.Platforms.Stub -{ - /// - /// Stub implementation that simulates Customer Center presentation. - /// Enabled when scripting define symbol REVENUECAT_UI_STUBS is present. - /// - internal class StubCustomerCenterPresenter : ICustomerCenterPresenter - { - public bool IsSupported() - { - return true; - } - - public async Task PresentCustomerCenterAsync() - { - Debug.Log("[RevenueCatUI][Stub] PresentCustomerCenter called. Completing immediately."); - await Task.Yield(); - } - } -} - diff --git a/RevenueCatUI/Runtime/Platforms/Stub/StubCustomerCenterPresenter.cs.meta b/RevenueCatUI/Runtime/Platforms/Stub/StubCustomerCenterPresenter.cs.meta deleted file mode 100644 index f5432f4e..00000000 --- a/RevenueCatUI/Runtime/Platforms/Stub/StubCustomerCenterPresenter.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: b92f3061f8bdb4a1f8e7e70348a7454b -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/RevenueCatUI/Runtime/Platforms/iOS/IOSCustomerCenterPresenter.cs b/RevenueCatUI/Runtime/Platforms/iOS/IOSCustomerCenterPresenter.cs new file mode 100644 index 00000000..9ed3f557 --- /dev/null +++ b/RevenueCatUI/Runtime/Platforms/iOS/IOSCustomerCenterPresenter.cs @@ -0,0 +1,33 @@ +#if UNITY_IOS && !UNITY_EDITOR +using System.Runtime.InteropServices; +using System.Threading.Tasks; + +namespace RevenueCat.UI.Platforms +{ + internal class IOSCustomerCenterPresenter : ICustomerCenterPresenter + { + private delegate void CustomerCenterCallback(); + + [DllImport("__Internal")] private static extern void rcui_presentCustomerCenter(CustomerCenterCallback cb); + [DllImport("__Internal")] private static extern bool rcui_isSupported(); + + private static TaskCompletionSource s_current; + + public bool IsSupported() => rcui_isSupported(); + + public Task PresentCustomerCenterAsync() + { + s_current = new TaskCompletionSource(); + rcui_presentCustomerCenter(OnDone); + return s_current.Task; + } + + [AOT.MonoPInvokeCallback(typeof(CustomerCenterCallback))] + private static void OnDone() + { + s_current?.TrySetResult(true); + s_current = null; + } + } +} +#endif diff --git a/RevenueCatUI/Runtime/Platforms/iOS/IOSCustomerCenterPresenter.cs.meta b/RevenueCatUI/Runtime/Platforms/iOS/IOSCustomerCenterPresenter.cs.meta new file mode 100644 index 00000000..fd8aa56c --- /dev/null +++ b/RevenueCatUI/Runtime/Platforms/iOS/IOSCustomerCenterPresenter.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 3c25347f16a994257ae49e1048cf650d \ No newline at end of file diff --git a/RevenueCatUI/Runtime/Platforms/iOS/IOSPaywallPresenter.cs b/RevenueCatUI/Runtime/Platforms/iOS/IOSPaywallPresenter.cs new file mode 100644 index 00000000..fb3c3be7 --- /dev/null +++ b/RevenueCatUI/Runtime/Platforms/iOS/IOSPaywallPresenter.cs @@ -0,0 +1,45 @@ +#if UNITY_IOS && !UNITY_EDITOR +using System; +using System.Runtime.InteropServices; +using System.Threading.Tasks; + +namespace RevenueCat.UI.Platforms +{ + internal class IOSPaywallPresenter : IPaywallPresenter + { + private delegate void PaywallResultCallback(string result); + + [DllImport("__Internal")] private static extern void rcui_presentPaywall(string offeringIdentifier, bool displayCloseButton, PaywallResultCallback cb); + [DllImport("__Internal")] private static extern void rcui_presentPaywallIfNeeded(string requiredEntitlementIdentifier, string offeringIdentifier, bool displayCloseButton, PaywallResultCallback cb); + [DllImport("__Internal")] private static extern bool rcui_isSupported(); + + private static TaskCompletionSource s_current; + + public bool IsSupported() => rcui_isSupported(); + + public Task PresentPaywallAsync(PaywallOptions options) + { + s_current = new TaskCompletionSource(); + rcui_presentPaywall(options?.OfferingIdentifier, options?.DisplayCloseButton ?? false, OnResult); + return s_current.Task; + } + + public Task PresentPaywallIfNeededAsync(string requiredEntitlementIdentifier, PaywallOptions options) + { + s_current = new TaskCompletionSource(); + rcui_presentPaywallIfNeeded(requiredEntitlementIdentifier, options?.OfferingIdentifier, options?.DisplayCloseButton ?? false, OnResult); + return s_current.Task; + } + + [AOT.MonoPInvokeCallback(typeof(PaywallResultCallback))] + private static void OnResult(string result) + { + var token = (result ?? "ERROR"); + var native = token.Split('|')[0]; + var type = PaywallResultTypeExtensions.FromNativeString(native); + s_current?.TrySetResult(new PaywallResult(type)); + s_current = null; + } + } +} +#endif diff --git a/RevenueCatUI/Runtime/Platforms/iOS/IOSPaywallPresenter.cs.meta b/RevenueCatUI/Runtime/Platforms/iOS/IOSPaywallPresenter.cs.meta new file mode 100644 index 00000000..77d8624a --- /dev/null +++ b/RevenueCatUI/Runtime/Platforms/iOS/IOSPaywallPresenter.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 3f43077ff755543a5b17a0b90808573d \ No newline at end of file diff --git a/Subtester/ProjectSettings/GvhProjectSettings.xml b/Subtester/ProjectSettings/GvhProjectSettings.xml index d13f3125..e4676de9 100644 --- a/Subtester/ProjectSettings/GvhProjectSettings.xml +++ b/Subtester/ProjectSettings/GvhProjectSettings.xml @@ -1,4 +1,28 @@ - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + From 151ef8315e2e779b8d1b9c2927b7f60c2437720a Mon Sep 17 00:00:00 2001 From: Facundo Menzella Date: Mon, 15 Sep 2025 10:40:09 +0200 Subject: [PATCH 04/65] Moved files to RevenueCat/Android --- .../revenuecat/unity => }/RevenueCatUI.java | 0 .../Plugins/Android/RevenueCatUI.java.meta | 33 +++++++++++++++++++ RevenueCatUI/Plugins/Android/minimal.meta | 8 ----- .../Android/minimal/AndroidManifest.xml | 8 ----- .../Android/minimal/AndroidManifest.xml.meta | 7 ---- .../Plugins/Android/minimal/build.gradle | 20 ----------- .../Plugins/Android/minimal/build.gradle.meta | 7 ---- .../Android/minimal/proguard-rules.pro | 3 -- .../Android/minimal/proguard-rules.pro.meta | 7 ---- RevenueCatUI/Plugins/Android/minimal/src.meta | 8 ----- .../Plugins/Android/minimal/src/main.meta | 8 ----- .../Android/minimal/src/main/java.meta | 8 ----- .../Android/minimal/src/main/java/com.meta | 8 ----- .../minimal/src/main/java/com/revenuecat.meta | 8 ----- .../src/main/java/com/revenuecat/unity.meta | 8 ----- .../revenuecat/unity/RevenueCatUI.java.meta | 2 -- .../ProjectSettings/GvhProjectSettings.xml | 2 +- .../ProjectSettings/ProjectSettings.asset | 3 ++ 18 files changed, 37 insertions(+), 111 deletions(-) rename RevenueCatUI/Plugins/Android/{minimal/src/main/java/com/revenuecat/unity => }/RevenueCatUI.java (100%) create mode 100644 RevenueCatUI/Plugins/Android/RevenueCatUI.java.meta delete mode 100644 RevenueCatUI/Plugins/Android/minimal.meta delete mode 100644 RevenueCatUI/Plugins/Android/minimal/AndroidManifest.xml delete mode 100644 RevenueCatUI/Plugins/Android/minimal/AndroidManifest.xml.meta delete mode 100644 RevenueCatUI/Plugins/Android/minimal/build.gradle delete mode 100644 RevenueCatUI/Plugins/Android/minimal/build.gradle.meta delete mode 100644 RevenueCatUI/Plugins/Android/minimal/proguard-rules.pro delete mode 100644 RevenueCatUI/Plugins/Android/minimal/proguard-rules.pro.meta delete mode 100644 RevenueCatUI/Plugins/Android/minimal/src.meta delete mode 100644 RevenueCatUI/Plugins/Android/minimal/src/main.meta delete mode 100644 RevenueCatUI/Plugins/Android/minimal/src/main/java.meta delete mode 100644 RevenueCatUI/Plugins/Android/minimal/src/main/java/com.meta delete mode 100644 RevenueCatUI/Plugins/Android/minimal/src/main/java/com/revenuecat.meta delete mode 100644 RevenueCatUI/Plugins/Android/minimal/src/main/java/com/revenuecat/unity.meta delete mode 100644 RevenueCatUI/Plugins/Android/minimal/src/main/java/com/revenuecat/unity/RevenueCatUI.java.meta diff --git a/RevenueCatUI/Plugins/Android/minimal/src/main/java/com/revenuecat/unity/RevenueCatUI.java b/RevenueCatUI/Plugins/Android/RevenueCatUI.java similarity index 100% rename from RevenueCatUI/Plugins/Android/minimal/src/main/java/com/revenuecat/unity/RevenueCatUI.java rename to RevenueCatUI/Plugins/Android/RevenueCatUI.java diff --git a/RevenueCatUI/Plugins/Android/RevenueCatUI.java.meta b/RevenueCatUI/Plugins/Android/RevenueCatUI.java.meta new file mode 100644 index 00000000..c5940f37 --- /dev/null +++ b/RevenueCatUI/Plugins/Android/RevenueCatUI.java.meta @@ -0,0 +1,33 @@ +fileFormatVersion: 2 +guid: 3c9d6f1b2a2d4f3c9b9a7c1d2e3f4a5b +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + Android: Android + second: + enabled: 1 + settings: {} + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + userData: + assetBundleName: + assetBundleVariant: + diff --git a/RevenueCatUI/Plugins/Android/minimal.meta b/RevenueCatUI/Plugins/Android/minimal.meta deleted file mode 100644 index b5ebfc27..00000000 --- a/RevenueCatUI/Plugins/Android/minimal.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 801186ab2cd0b492a9e5333515c108bc -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/RevenueCatUI/Plugins/Android/minimal/AndroidManifest.xml b/RevenueCatUI/Plugins/Android/minimal/AndroidManifest.xml deleted file mode 100644 index 4fac66a6..00000000 --- a/RevenueCatUI/Plugins/Android/minimal/AndroidManifest.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - diff --git a/RevenueCatUI/Plugins/Android/minimal/AndroidManifest.xml.meta b/RevenueCatUI/Plugins/Android/minimal/AndroidManifest.xml.meta deleted file mode 100644 index fb4f42e3..00000000 --- a/RevenueCatUI/Plugins/Android/minimal/AndroidManifest.xml.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 7376be9be6daa4aadb2f8074b5d28eee -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/RevenueCatUI/Plugins/Android/minimal/build.gradle b/RevenueCatUI/Plugins/Android/minimal/build.gradle deleted file mode 100644 index 436e7198..00000000 --- a/RevenueCatUI/Plugins/Android/minimal/build.gradle +++ /dev/null @@ -1,20 +0,0 @@ -apply plugin: 'com.android.library' - -android { - compileSdkVersion 33 - defaultConfig { - minSdkVersion 21 - targetSdkVersion 33 - consumerProguardFiles 'proguard-rules.pro' - } - - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } -} - -dependencies { - // No external dependencies for the minimal stub -} - diff --git a/RevenueCatUI/Plugins/Android/minimal/build.gradle.meta b/RevenueCatUI/Plugins/Android/minimal/build.gradle.meta deleted file mode 100644 index 9bea0710..00000000 --- a/RevenueCatUI/Plugins/Android/minimal/build.gradle.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: d92c6e421cec541c4850ee85173daf79 -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/RevenueCatUI/Plugins/Android/minimal/proguard-rules.pro b/RevenueCatUI/Plugins/Android/minimal/proguard-rules.pro deleted file mode 100644 index c9e6560d..00000000 --- a/RevenueCatUI/Plugins/Android/minimal/proguard-rules.pro +++ /dev/null @@ -1,3 +0,0 @@ -# Keep the UnitySendMessage references --keep class com.unity3d.player.UnityPlayer { *; } - diff --git a/RevenueCatUI/Plugins/Android/minimal/proguard-rules.pro.meta b/RevenueCatUI/Plugins/Android/minimal/proguard-rules.pro.meta deleted file mode 100644 index 8f9d92f4..00000000 --- a/RevenueCatUI/Plugins/Android/minimal/proguard-rules.pro.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 8fa508341800c4713a6c76423e5fd8e3 -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/RevenueCatUI/Plugins/Android/minimal/src.meta b/RevenueCatUI/Plugins/Android/minimal/src.meta deleted file mode 100644 index b4a0a448..00000000 --- a/RevenueCatUI/Plugins/Android/minimal/src.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 0c49cf57f3c154d3783c927be059b0e6 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/RevenueCatUI/Plugins/Android/minimal/src/main.meta b/RevenueCatUI/Plugins/Android/minimal/src/main.meta deleted file mode 100644 index 3fba932f..00000000 --- a/RevenueCatUI/Plugins/Android/minimal/src/main.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: d01209303c5124fb4a62ba32c94f7579 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/RevenueCatUI/Plugins/Android/minimal/src/main/java.meta b/RevenueCatUI/Plugins/Android/minimal/src/main/java.meta deleted file mode 100644 index ec81b8c3..00000000 --- a/RevenueCatUI/Plugins/Android/minimal/src/main/java.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 93debef0bb0d54c248c9ba3b92b67346 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/RevenueCatUI/Plugins/Android/minimal/src/main/java/com.meta b/RevenueCatUI/Plugins/Android/minimal/src/main/java/com.meta deleted file mode 100644 index 5af49093..00000000 --- a/RevenueCatUI/Plugins/Android/minimal/src/main/java/com.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: f3945fc672ec549e1b2da93bd9611a25 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/RevenueCatUI/Plugins/Android/minimal/src/main/java/com/revenuecat.meta b/RevenueCatUI/Plugins/Android/minimal/src/main/java/com/revenuecat.meta deleted file mode 100644 index 0d8f2b1c..00000000 --- a/RevenueCatUI/Plugins/Android/minimal/src/main/java/com/revenuecat.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 6421f44f8ca884f469609b6374f69235 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/RevenueCatUI/Plugins/Android/minimal/src/main/java/com/revenuecat/unity.meta b/RevenueCatUI/Plugins/Android/minimal/src/main/java/com/revenuecat/unity.meta deleted file mode 100644 index ea8b9fb7..00000000 --- a/RevenueCatUI/Plugins/Android/minimal/src/main/java/com/revenuecat/unity.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 4b7afd2c7b9d143d8beebc2413076295 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/RevenueCatUI/Plugins/Android/minimal/src/main/java/com/revenuecat/unity/RevenueCatUI.java.meta b/RevenueCatUI/Plugins/Android/minimal/src/main/java/com/revenuecat/unity/RevenueCatUI.java.meta deleted file mode 100644 index 6cf3f842..00000000 --- a/RevenueCatUI/Plugins/Android/minimal/src/main/java/com/revenuecat/unity/RevenueCatUI.java.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 92bed5dedded04c4cafd2b494600ad84 \ No newline at end of file diff --git a/Subtester/ProjectSettings/GvhProjectSettings.xml b/Subtester/ProjectSettings/GvhProjectSettings.xml index e4676de9..82970e59 100644 --- a/Subtester/ProjectSettings/GvhProjectSettings.xml +++ b/Subtester/ProjectSettings/GvhProjectSettings.xml @@ -25,4 +25,4 @@ - + \ No newline at end of file diff --git a/Subtester/ProjectSettings/ProjectSettings.asset b/Subtester/ProjectSettings/ProjectSettings.asset index 5b7e636d..00bae901 100644 --- a/Subtester/ProjectSettings/ProjectSettings.asset +++ b/Subtester/ProjectSettings/ProjectSettings.asset @@ -570,6 +570,9 @@ PlayerSettings: - serializedVersion: 3 m_BuildTarget: Android m_Formats: 01000000 + - serializedVersion: 3 + m_BuildTarget: iOS + m_Formats: 03000000 playModeTestRunnerEnabled: 0 runPlayModeTestAsEditModeTest: 0 actionOnDotNetUnhandledException: 1 From 45ad7d03ffa394fc10e2087679475b917088873b Mon Sep 17 00:00:00 2001 From: Facundo Menzella Date: Mon, 15 Sep 2025 10:49:44 +0200 Subject: [PATCH 05/65] Moved files from Runtime to Scripts --- RevenueCatUI/README.md | 2 +- RevenueCatUI/Runtime/Internal.meta | 8 -------- RevenueCatUI/Runtime/Platforms.meta | 8 -------- RevenueCatUI/Runtime/Platforms/Android.meta | 8 -------- .../Android/AndroidCustomerCenterPresenter.cs.meta | 2 -- .../Platforms/Android/AndroidPaywallPresenter.cs.meta | 2 -- RevenueCatUI/Runtime/Platforms/Stub.meta | 8 -------- RevenueCatUI/Runtime/Platforms/iOS.meta | 8 -------- .../Platforms/iOS/IOSCustomerCenterPresenter.cs.meta | 2 -- .../Runtime/Platforms/iOS/IOSPaywallPresenter.cs.meta | 2 -- .../Internal => Scripts}/CustomerCenterPresenter.cs | 0 .../Internal => Scripts}/CustomerCenterPresenter.cs.meta | 1 + .../Internal => Scripts}/ICustomerCenterPresenter.cs | 2 +- .../Internal => Scripts}/ICustomerCenterPresenter.cs.meta | 1 + .../{Runtime/Internal => Scripts}/IPaywallPresenter.cs | 0 .../Internal => Scripts}/IPaywallPresenter.cs.meta | 1 + RevenueCatUI/{Runtime/Internal => Scripts}/JsonUtility.cs | 2 +- .../{Runtime/Internal => Scripts}/JsonUtility.cs.meta | 1 + RevenueCatUI/{Runtime => Scripts}/PaywallOptions.cs | 2 +- RevenueCatUI/{Runtime => Scripts}/PaywallOptions.cs.meta | 1 + .../{Runtime/Internal => Scripts}/PaywallPresenter.cs | 0 .../Internal => Scripts}/PaywallPresenter.cs.meta | 1 + RevenueCatUI/{Runtime => Scripts}/PaywallResult.cs | 2 +- RevenueCatUI/{Runtime => Scripts}/PaywallResult.cs.meta | 1 + .../Platforms/Android/AndroidCustomerCenterPresenter.cs | 0 .../Android/AndroidCustomerCenterPresenter.cs.meta | 3 +++ .../Platforms/Android/AndroidPaywallPresenter.cs | 0 .../Platforms/Android/AndroidPaywallPresenter.cs.meta | 3 +++ .../Platforms/Stub/StubPaywallPresenter.cs | 1 - .../Platforms/Stub/StubPaywallPresenter.cs.meta | 1 + .../Platforms/iOS/IOSCustomerCenterPresenter.cs | 0 .../Platforms/iOS/IOSCustomerCenterPresenter.cs.meta | 3 +++ .../Platforms/iOS/IOSPaywallPresenter.cs | 0 .../Scripts/Platforms/iOS/IOSPaywallPresenter.cs.meta | 3 +++ RevenueCatUI/{Runtime => Scripts}/RevenueCat.UI.asmdef | 2 +- .../{Runtime => Scripts}/RevenueCat.UI.asmdef.meta | 1 + RevenueCatUI/{Runtime => Scripts}/RevenueCatUI.cs | 2 +- RevenueCatUI/{Runtime => Scripts}/RevenueCatUI.cs.meta | 1 + .../Internal => Scripts}/RevenueCatUICallbackHandler.cs | 0 .../RevenueCatUICallbackHandler.cs.meta | 1 + 40 files changed, 30 insertions(+), 56 deletions(-) delete mode 100644 RevenueCatUI/Runtime/Internal.meta delete mode 100644 RevenueCatUI/Runtime/Platforms.meta delete mode 100644 RevenueCatUI/Runtime/Platforms/Android.meta delete mode 100644 RevenueCatUI/Runtime/Platforms/Android/AndroidCustomerCenterPresenter.cs.meta delete mode 100644 RevenueCatUI/Runtime/Platforms/Android/AndroidPaywallPresenter.cs.meta delete mode 100644 RevenueCatUI/Runtime/Platforms/Stub.meta delete mode 100644 RevenueCatUI/Runtime/Platforms/iOS.meta delete mode 100644 RevenueCatUI/Runtime/Platforms/iOS/IOSCustomerCenterPresenter.cs.meta delete mode 100644 RevenueCatUI/Runtime/Platforms/iOS/IOSPaywallPresenter.cs.meta rename RevenueCatUI/{Runtime/Internal => Scripts}/CustomerCenterPresenter.cs (100%) rename RevenueCatUI/{Runtime/Internal => Scripts}/CustomerCenterPresenter.cs.meta (99%) rename RevenueCatUI/{Runtime/Internal => Scripts}/ICustomerCenterPresenter.cs (99%) rename RevenueCatUI/{Runtime/Internal => Scripts}/ICustomerCenterPresenter.cs.meta (99%) rename RevenueCatUI/{Runtime/Internal => Scripts}/IPaywallPresenter.cs (100%) rename RevenueCatUI/{Runtime/Internal => Scripts}/IPaywallPresenter.cs.meta (99%) rename RevenueCatUI/{Runtime/Internal => Scripts}/JsonUtility.cs (99%) rename RevenueCatUI/{Runtime/Internal => Scripts}/JsonUtility.cs.meta (99%) rename RevenueCatUI/{Runtime => Scripts}/PaywallOptions.cs (99%) rename RevenueCatUI/{Runtime => Scripts}/PaywallOptions.cs.meta (99%) rename RevenueCatUI/{Runtime/Internal => Scripts}/PaywallPresenter.cs (100%) rename RevenueCatUI/{Runtime/Internal => Scripts}/PaywallPresenter.cs.meta (99%) rename RevenueCatUI/{Runtime => Scripts}/PaywallResult.cs (99%) rename RevenueCatUI/{Runtime => Scripts}/PaywallResult.cs.meta (99%) rename RevenueCatUI/{Runtime => Scripts}/Platforms/Android/AndroidCustomerCenterPresenter.cs (100%) create mode 100644 RevenueCatUI/Scripts/Platforms/Android/AndroidCustomerCenterPresenter.cs.meta rename RevenueCatUI/{Runtime => Scripts}/Platforms/Android/AndroidPaywallPresenter.cs (100%) create mode 100644 RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs.meta rename RevenueCatUI/{Runtime => Scripts}/Platforms/Stub/StubPaywallPresenter.cs (99%) rename RevenueCatUI/{Runtime => Scripts}/Platforms/Stub/StubPaywallPresenter.cs.meta (99%) rename RevenueCatUI/{Runtime => Scripts}/Platforms/iOS/IOSCustomerCenterPresenter.cs (100%) create mode 100644 RevenueCatUI/Scripts/Platforms/iOS/IOSCustomerCenterPresenter.cs.meta rename RevenueCatUI/{Runtime => Scripts}/Platforms/iOS/IOSPaywallPresenter.cs (100%) create mode 100644 RevenueCatUI/Scripts/Platforms/iOS/IOSPaywallPresenter.cs.meta rename RevenueCatUI/{Runtime => Scripts}/RevenueCat.UI.asmdef (99%) rename RevenueCatUI/{Runtime => Scripts}/RevenueCat.UI.asmdef.meta (99%) rename RevenueCatUI/{Runtime => Scripts}/RevenueCatUI.cs (99%) rename RevenueCatUI/{Runtime => Scripts}/RevenueCatUI.cs.meta (99%) rename RevenueCatUI/{Runtime/Internal => Scripts}/RevenueCatUICallbackHandler.cs (100%) rename RevenueCatUI/{Runtime/Internal => Scripts}/RevenueCatUICallbackHandler.cs.meta (99%) diff --git a/RevenueCatUI/README.md b/RevenueCatUI/README.md index 6a21c376..54f71d27 100644 --- a/RevenueCatUI/README.md +++ b/RevenueCatUI/README.md @@ -120,7 +120,7 @@ Minimal native code is provided as a reference scaffold: - iOS functions: `rcui_presentPaywall`, `rcui_presentPaywallIfNeeded`, `rcui_presentCustomerCenter`, `rcui_isSupported` If/when you add real native UI, update the platform presenters in -`RevenueCatUI/Runtime/Platforms/{Android,iOS}` to select your implementations. +`RevenueCatUI/Scripts/Platforms/{Android,iOS}` to select your implementations. ### Platform Abstraction - Factory pattern with `IPaywallPresenter` and `ICustomerCenterPresenter` diff --git a/RevenueCatUI/Runtime/Internal.meta b/RevenueCatUI/Runtime/Internal.meta deleted file mode 100644 index 97a7bd87..00000000 --- a/RevenueCatUI/Runtime/Internal.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: c3ff900372e794b3fb2921147b45df17 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/RevenueCatUI/Runtime/Platforms.meta b/RevenueCatUI/Runtime/Platforms.meta deleted file mode 100644 index 22f9238e..00000000 --- a/RevenueCatUI/Runtime/Platforms.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 528349e0bce8f40cfbd13d9a49a2a398 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/RevenueCatUI/Runtime/Platforms/Android.meta b/RevenueCatUI/Runtime/Platforms/Android.meta deleted file mode 100644 index d9109fd9..00000000 --- a/RevenueCatUI/Runtime/Platforms/Android.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: bbfcf62945ec245d3b025e80eb2a410a -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/RevenueCatUI/Runtime/Platforms/Android/AndroidCustomerCenterPresenter.cs.meta b/RevenueCatUI/Runtime/Platforms/Android/AndroidCustomerCenterPresenter.cs.meta deleted file mode 100644 index f33ddf85..00000000 --- a/RevenueCatUI/Runtime/Platforms/Android/AndroidCustomerCenterPresenter.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 9d6151bb80d874557bc6c71b90bc470c \ No newline at end of file diff --git a/RevenueCatUI/Runtime/Platforms/Android/AndroidPaywallPresenter.cs.meta b/RevenueCatUI/Runtime/Platforms/Android/AndroidPaywallPresenter.cs.meta deleted file mode 100644 index ed788598..00000000 --- a/RevenueCatUI/Runtime/Platforms/Android/AndroidPaywallPresenter.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 824501865fc504ae3bfdcd2b256dc650 \ No newline at end of file diff --git a/RevenueCatUI/Runtime/Platforms/Stub.meta b/RevenueCatUI/Runtime/Platforms/Stub.meta deleted file mode 100644 index 7a2fe6d0..00000000 --- a/RevenueCatUI/Runtime/Platforms/Stub.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 464a162a04a74496db6938e5bf8b8d56 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/RevenueCatUI/Runtime/Platforms/iOS.meta b/RevenueCatUI/Runtime/Platforms/iOS.meta deleted file mode 100644 index 420323eb..00000000 --- a/RevenueCatUI/Runtime/Platforms/iOS.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 723e91e3e063c4fe38abdd31e4ad5deb -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/RevenueCatUI/Runtime/Platforms/iOS/IOSCustomerCenterPresenter.cs.meta b/RevenueCatUI/Runtime/Platforms/iOS/IOSCustomerCenterPresenter.cs.meta deleted file mode 100644 index fd8aa56c..00000000 --- a/RevenueCatUI/Runtime/Platforms/iOS/IOSCustomerCenterPresenter.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 3c25347f16a994257ae49e1048cf650d \ No newline at end of file diff --git a/RevenueCatUI/Runtime/Platforms/iOS/IOSPaywallPresenter.cs.meta b/RevenueCatUI/Runtime/Platforms/iOS/IOSPaywallPresenter.cs.meta deleted file mode 100644 index 77d8624a..00000000 --- a/RevenueCatUI/Runtime/Platforms/iOS/IOSPaywallPresenter.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 3f43077ff755543a5b17a0b90808573d \ No newline at end of file diff --git a/RevenueCatUI/Runtime/Internal/CustomerCenterPresenter.cs b/RevenueCatUI/Scripts/CustomerCenterPresenter.cs similarity index 100% rename from RevenueCatUI/Runtime/Internal/CustomerCenterPresenter.cs rename to RevenueCatUI/Scripts/CustomerCenterPresenter.cs diff --git a/RevenueCatUI/Runtime/Internal/CustomerCenterPresenter.cs.meta b/RevenueCatUI/Scripts/CustomerCenterPresenter.cs.meta similarity index 99% rename from RevenueCatUI/Runtime/Internal/CustomerCenterPresenter.cs.meta rename to RevenueCatUI/Scripts/CustomerCenterPresenter.cs.meta index a3f9d9bc..21828aed 100644 --- a/RevenueCatUI/Runtime/Internal/CustomerCenterPresenter.cs.meta +++ b/RevenueCatUI/Scripts/CustomerCenterPresenter.cs.meta @@ -9,3 +9,4 @@ MonoImporter: userData: assetBundleName: assetBundleVariant: + diff --git a/RevenueCatUI/Runtime/Internal/ICustomerCenterPresenter.cs b/RevenueCatUI/Scripts/ICustomerCenterPresenter.cs similarity index 99% rename from RevenueCatUI/Runtime/Internal/ICustomerCenterPresenter.cs rename to RevenueCatUI/Scripts/ICustomerCenterPresenter.cs index 2e073be9..23778f44 100644 --- a/RevenueCatUI/Runtime/Internal/ICustomerCenterPresenter.cs +++ b/RevenueCatUI/Scripts/ICustomerCenterPresenter.cs @@ -20,4 +20,4 @@ internal interface ICustomerCenterPresenter /// True if supported, false otherwise bool IsSupported(); } -} \ No newline at end of file +} diff --git a/RevenueCatUI/Runtime/Internal/ICustomerCenterPresenter.cs.meta b/RevenueCatUI/Scripts/ICustomerCenterPresenter.cs.meta similarity index 99% rename from RevenueCatUI/Runtime/Internal/ICustomerCenterPresenter.cs.meta rename to RevenueCatUI/Scripts/ICustomerCenterPresenter.cs.meta index f794dba2..e0c30634 100644 --- a/RevenueCatUI/Runtime/Internal/ICustomerCenterPresenter.cs.meta +++ b/RevenueCatUI/Scripts/ICustomerCenterPresenter.cs.meta @@ -9,3 +9,4 @@ MonoImporter: userData: assetBundleName: assetBundleVariant: + diff --git a/RevenueCatUI/Runtime/Internal/IPaywallPresenter.cs b/RevenueCatUI/Scripts/IPaywallPresenter.cs similarity index 100% rename from RevenueCatUI/Runtime/Internal/IPaywallPresenter.cs rename to RevenueCatUI/Scripts/IPaywallPresenter.cs diff --git a/RevenueCatUI/Runtime/Internal/IPaywallPresenter.cs.meta b/RevenueCatUI/Scripts/IPaywallPresenter.cs.meta similarity index 99% rename from RevenueCatUI/Runtime/Internal/IPaywallPresenter.cs.meta rename to RevenueCatUI/Scripts/IPaywallPresenter.cs.meta index 32a7be3f..bf355283 100644 --- a/RevenueCatUI/Runtime/Internal/IPaywallPresenter.cs.meta +++ b/RevenueCatUI/Scripts/IPaywallPresenter.cs.meta @@ -9,3 +9,4 @@ MonoImporter: userData: assetBundleName: assetBundleVariant: + diff --git a/RevenueCatUI/Runtime/Internal/JsonUtility.cs b/RevenueCatUI/Scripts/JsonUtility.cs similarity index 99% rename from RevenueCatUI/Runtime/Internal/JsonUtility.cs rename to RevenueCatUI/Scripts/JsonUtility.cs index f113962c..963d21bf 100644 --- a/RevenueCatUI/Runtime/Internal/JsonUtility.cs +++ b/RevenueCatUI/Scripts/JsonUtility.cs @@ -38,4 +38,4 @@ public static string Serialize(Dictionary dict) return sb.ToString(); } } -} \ No newline at end of file +} diff --git a/RevenueCatUI/Runtime/Internal/JsonUtility.cs.meta b/RevenueCatUI/Scripts/JsonUtility.cs.meta similarity index 99% rename from RevenueCatUI/Runtime/Internal/JsonUtility.cs.meta rename to RevenueCatUI/Scripts/JsonUtility.cs.meta index 987d0e62..810c242e 100644 --- a/RevenueCatUI/Runtime/Internal/JsonUtility.cs.meta +++ b/RevenueCatUI/Scripts/JsonUtility.cs.meta @@ -9,3 +9,4 @@ MonoImporter: userData: assetBundleName: assetBundleVariant: + diff --git a/RevenueCatUI/Runtime/PaywallOptions.cs b/RevenueCatUI/Scripts/PaywallOptions.cs similarity index 99% rename from RevenueCatUI/Runtime/PaywallOptions.cs rename to RevenueCatUI/Scripts/PaywallOptions.cs index e68347fe..f1e01519 100644 --- a/RevenueCatUI/Runtime/PaywallOptions.cs +++ b/RevenueCatUI/Scripts/PaywallOptions.cs @@ -47,4 +47,4 @@ public PaywallOptions(string offeringIdentifier, bool displayCloseButton) DisplayCloseButton = displayCloseButton; } } -} \ No newline at end of file +} diff --git a/RevenueCatUI/Runtime/PaywallOptions.cs.meta b/RevenueCatUI/Scripts/PaywallOptions.cs.meta similarity index 99% rename from RevenueCatUI/Runtime/PaywallOptions.cs.meta rename to RevenueCatUI/Scripts/PaywallOptions.cs.meta index db35d992..5cdc4d27 100644 --- a/RevenueCatUI/Runtime/PaywallOptions.cs.meta +++ b/RevenueCatUI/Scripts/PaywallOptions.cs.meta @@ -9,3 +9,4 @@ MonoImporter: userData: assetBundleName: assetBundleVariant: + diff --git a/RevenueCatUI/Runtime/Internal/PaywallPresenter.cs b/RevenueCatUI/Scripts/PaywallPresenter.cs similarity index 100% rename from RevenueCatUI/Runtime/Internal/PaywallPresenter.cs rename to RevenueCatUI/Scripts/PaywallPresenter.cs diff --git a/RevenueCatUI/Runtime/Internal/PaywallPresenter.cs.meta b/RevenueCatUI/Scripts/PaywallPresenter.cs.meta similarity index 99% rename from RevenueCatUI/Runtime/Internal/PaywallPresenter.cs.meta rename to RevenueCatUI/Scripts/PaywallPresenter.cs.meta index 79ec76cb..c3add48c 100644 --- a/RevenueCatUI/Runtime/Internal/PaywallPresenter.cs.meta +++ b/RevenueCatUI/Scripts/PaywallPresenter.cs.meta @@ -9,3 +9,4 @@ MonoImporter: userData: assetBundleName: assetBundleVariant: + diff --git a/RevenueCatUI/Runtime/PaywallResult.cs b/RevenueCatUI/Scripts/PaywallResult.cs similarity index 99% rename from RevenueCatUI/Runtime/PaywallResult.cs rename to RevenueCatUI/Scripts/PaywallResult.cs index ff5cebd5..d90280ce 100644 --- a/RevenueCatUI/Runtime/PaywallResult.cs +++ b/RevenueCatUI/Scripts/PaywallResult.cs @@ -107,4 +107,4 @@ public static PaywallResultType FromNativeString(string nativeResult) }; } } -} \ No newline at end of file +} diff --git a/RevenueCatUI/Runtime/PaywallResult.cs.meta b/RevenueCatUI/Scripts/PaywallResult.cs.meta similarity index 99% rename from RevenueCatUI/Runtime/PaywallResult.cs.meta rename to RevenueCatUI/Scripts/PaywallResult.cs.meta index 4aecb96b..b732473c 100644 --- a/RevenueCatUI/Runtime/PaywallResult.cs.meta +++ b/RevenueCatUI/Scripts/PaywallResult.cs.meta @@ -9,3 +9,4 @@ MonoImporter: userData: assetBundleName: assetBundleVariant: + diff --git a/RevenueCatUI/Runtime/Platforms/Android/AndroidCustomerCenterPresenter.cs b/RevenueCatUI/Scripts/Platforms/Android/AndroidCustomerCenterPresenter.cs similarity index 100% rename from RevenueCatUI/Runtime/Platforms/Android/AndroidCustomerCenterPresenter.cs rename to RevenueCatUI/Scripts/Platforms/Android/AndroidCustomerCenterPresenter.cs diff --git a/RevenueCatUI/Scripts/Platforms/Android/AndroidCustomerCenterPresenter.cs.meta b/RevenueCatUI/Scripts/Platforms/Android/AndroidCustomerCenterPresenter.cs.meta new file mode 100644 index 00000000..daaf85c4 --- /dev/null +++ b/RevenueCatUI/Scripts/Platforms/Android/AndroidCustomerCenterPresenter.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 9d6151bb80d874557bc6c71b90bc470c + diff --git a/RevenueCatUI/Runtime/Platforms/Android/AndroidPaywallPresenter.cs b/RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs similarity index 100% rename from RevenueCatUI/Runtime/Platforms/Android/AndroidPaywallPresenter.cs rename to RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs diff --git a/RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs.meta b/RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs.meta new file mode 100644 index 00000000..9cdf56ad --- /dev/null +++ b/RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 824501865fc504ae3bfdcd2b256dc650 + diff --git a/RevenueCatUI/Runtime/Platforms/Stub/StubPaywallPresenter.cs b/RevenueCatUI/Scripts/Platforms/Stub/StubPaywallPresenter.cs similarity index 99% rename from RevenueCatUI/Runtime/Platforms/Stub/StubPaywallPresenter.cs rename to RevenueCatUI/Scripts/Platforms/Stub/StubPaywallPresenter.cs index 0fba5fa8..f6e39e0e 100644 --- a/RevenueCatUI/Runtime/Platforms/Stub/StubPaywallPresenter.cs +++ b/RevenueCatUI/Scripts/Platforms/Stub/StubPaywallPresenter.cs @@ -30,4 +30,3 @@ public async Task PresentPaywallIfNeededAsync(string requiredEnti } } } - diff --git a/RevenueCatUI/Runtime/Platforms/Stub/StubPaywallPresenter.cs.meta b/RevenueCatUI/Scripts/Platforms/Stub/StubPaywallPresenter.cs.meta similarity index 99% rename from RevenueCatUI/Runtime/Platforms/Stub/StubPaywallPresenter.cs.meta rename to RevenueCatUI/Scripts/Platforms/Stub/StubPaywallPresenter.cs.meta index 3c7cca20..b587fa14 100644 --- a/RevenueCatUI/Runtime/Platforms/Stub/StubPaywallPresenter.cs.meta +++ b/RevenueCatUI/Scripts/Platforms/Stub/StubPaywallPresenter.cs.meta @@ -9,3 +9,4 @@ MonoImporter: userData: assetBundleName: assetBundleVariant: + diff --git a/RevenueCatUI/Runtime/Platforms/iOS/IOSCustomerCenterPresenter.cs b/RevenueCatUI/Scripts/Platforms/iOS/IOSCustomerCenterPresenter.cs similarity index 100% rename from RevenueCatUI/Runtime/Platforms/iOS/IOSCustomerCenterPresenter.cs rename to RevenueCatUI/Scripts/Platforms/iOS/IOSCustomerCenterPresenter.cs diff --git a/RevenueCatUI/Scripts/Platforms/iOS/IOSCustomerCenterPresenter.cs.meta b/RevenueCatUI/Scripts/Platforms/iOS/IOSCustomerCenterPresenter.cs.meta new file mode 100644 index 00000000..080f6509 --- /dev/null +++ b/RevenueCatUI/Scripts/Platforms/iOS/IOSCustomerCenterPresenter.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 3c25347f16a994257ae49e1048cf650d + diff --git a/RevenueCatUI/Runtime/Platforms/iOS/IOSPaywallPresenter.cs b/RevenueCatUI/Scripts/Platforms/iOS/IOSPaywallPresenter.cs similarity index 100% rename from RevenueCatUI/Runtime/Platforms/iOS/IOSPaywallPresenter.cs rename to RevenueCatUI/Scripts/Platforms/iOS/IOSPaywallPresenter.cs diff --git a/RevenueCatUI/Scripts/Platforms/iOS/IOSPaywallPresenter.cs.meta b/RevenueCatUI/Scripts/Platforms/iOS/IOSPaywallPresenter.cs.meta new file mode 100644 index 00000000..58401443 --- /dev/null +++ b/RevenueCatUI/Scripts/Platforms/iOS/IOSPaywallPresenter.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 3f43077ff755543a5b17a0b90808573d + diff --git a/RevenueCatUI/Runtime/RevenueCat.UI.asmdef b/RevenueCatUI/Scripts/RevenueCat.UI.asmdef similarity index 99% rename from RevenueCatUI/Runtime/RevenueCat.UI.asmdef rename to RevenueCatUI/Scripts/RevenueCat.UI.asmdef index 016d0ebe..7cb67b3d 100644 --- a/RevenueCatUI/Runtime/RevenueCat.UI.asmdef +++ b/RevenueCatUI/Scripts/RevenueCat.UI.asmdef @@ -17,4 +17,4 @@ "defineConstraints": [], "versionDefines": [], "noEngineReferences": false -} \ No newline at end of file +} diff --git a/RevenueCatUI/Runtime/RevenueCat.UI.asmdef.meta b/RevenueCatUI/Scripts/RevenueCat.UI.asmdef.meta similarity index 99% rename from RevenueCatUI/Runtime/RevenueCat.UI.asmdef.meta rename to RevenueCatUI/Scripts/RevenueCat.UI.asmdef.meta index f04e0f87..c4d3572f 100644 --- a/RevenueCatUI/Runtime/RevenueCat.UI.asmdef.meta +++ b/RevenueCatUI/Scripts/RevenueCat.UI.asmdef.meta @@ -5,3 +5,4 @@ AssemblyDefinitionImporter: userData: assetBundleName: assetBundleVariant: + diff --git a/RevenueCatUI/Runtime/RevenueCatUI.cs b/RevenueCatUI/Scripts/RevenueCatUI.cs similarity index 99% rename from RevenueCatUI/Runtime/RevenueCatUI.cs rename to RevenueCatUI/Scripts/RevenueCatUI.cs index 9383f33b..23634b4f 100644 --- a/RevenueCatUI/Runtime/RevenueCatUI.cs +++ b/RevenueCatUI/Scripts/RevenueCatUI.cs @@ -99,4 +99,4 @@ public static bool IsSupported() } } } -} \ No newline at end of file +} diff --git a/RevenueCatUI/Runtime/RevenueCatUI.cs.meta b/RevenueCatUI/Scripts/RevenueCatUI.cs.meta similarity index 99% rename from RevenueCatUI/Runtime/RevenueCatUI.cs.meta rename to RevenueCatUI/Scripts/RevenueCatUI.cs.meta index 068e5675..7a23e805 100644 --- a/RevenueCatUI/Runtime/RevenueCatUI.cs.meta +++ b/RevenueCatUI/Scripts/RevenueCatUI.cs.meta @@ -9,3 +9,4 @@ MonoImporter: userData: assetBundleName: assetBundleVariant: + diff --git a/RevenueCatUI/Runtime/Internal/RevenueCatUICallbackHandler.cs b/RevenueCatUI/Scripts/RevenueCatUICallbackHandler.cs similarity index 100% rename from RevenueCatUI/Runtime/Internal/RevenueCatUICallbackHandler.cs rename to RevenueCatUI/Scripts/RevenueCatUICallbackHandler.cs diff --git a/RevenueCatUI/Runtime/Internal/RevenueCatUICallbackHandler.cs.meta b/RevenueCatUI/Scripts/RevenueCatUICallbackHandler.cs.meta similarity index 99% rename from RevenueCatUI/Runtime/Internal/RevenueCatUICallbackHandler.cs.meta rename to RevenueCatUI/Scripts/RevenueCatUICallbackHandler.cs.meta index 9b9432ee..6f9de5fc 100644 --- a/RevenueCatUI/Runtime/Internal/RevenueCatUICallbackHandler.cs.meta +++ b/RevenueCatUI/Scripts/RevenueCatUICallbackHandler.cs.meta @@ -9,3 +9,4 @@ MonoImporter: userData: assetBundleName: assetBundleVariant: + From c4ac9de89960728478e1781c63543848cd49baff Mon Sep 17 00:00:00 2001 From: Facundo Menzella Date: Mon, 15 Sep 2025 10:57:12 +0200 Subject: [PATCH 06/65] Use SimpleJSON --- RevenueCatUI/Scripts/JsonUtility.cs | 41 ------------------------ RevenueCatUI/Scripts/JsonUtility.cs.meta | 12 ------- 2 files changed, 53 deletions(-) delete mode 100644 RevenueCatUI/Scripts/JsonUtility.cs delete mode 100644 RevenueCatUI/Scripts/JsonUtility.cs.meta diff --git a/RevenueCatUI/Scripts/JsonUtility.cs b/RevenueCatUI/Scripts/JsonUtility.cs deleted file mode 100644 index 963d21bf..00000000 --- a/RevenueCatUI/Scripts/JsonUtility.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System.Collections.Generic; -using System.Text; - -namespace RevenueCat.UI -{ - /// - /// Simple JSON utility for serializing options. - /// - internal static class JsonUtility - { - public static string Serialize(Dictionary dict) - { - if (dict == null || dict.Count == 0) - return "{}"; - - var sb = new StringBuilder(); - sb.Append("{"); - - bool first = true; - foreach (var kvp in dict) - { - if (!first) - sb.Append(","); - - sb.Append($"\"{kvp.Key}\":"); - - if (kvp.Value is string) - sb.Append($"\"{kvp.Value}\""); - else if (kvp.Value is bool) - sb.Append(kvp.Value.ToString().ToLower()); - else - sb.Append(kvp.Value?.ToString() ?? "null"); - - first = false; - } - - sb.Append("}"); - return sb.ToString(); - } - } -} diff --git a/RevenueCatUI/Scripts/JsonUtility.cs.meta b/RevenueCatUI/Scripts/JsonUtility.cs.meta deleted file mode 100644 index 810c242e..00000000 --- a/RevenueCatUI/Scripts/JsonUtility.cs.meta +++ /dev/null @@ -1,12 +0,0 @@ -fileFormatVersion: 2 -guid: 04f95b42e795f4c3d97e8c6fe104e36d -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: - From e20e4ce208142b2d98d979c431efedd5e9495311 Mon Sep 17 00:00:00 2001 From: Facundo Menzella Date: Mon, 15 Sep 2025 11:05:07 +0200 Subject: [PATCH 07/65] delete extra .meta --- RevenueCatUI/Editor.meta | 8 -------- RevenueCatUI/Resources.meta | 8 -------- RevenueCatUI/Runtime.meta | 8 -------- RevenueCatUI/UI.meta | 8 -------- 4 files changed, 32 deletions(-) delete mode 100644 RevenueCatUI/Editor.meta delete mode 100644 RevenueCatUI/Resources.meta delete mode 100644 RevenueCatUI/Runtime.meta delete mode 100644 RevenueCatUI/UI.meta diff --git a/RevenueCatUI/Editor.meta b/RevenueCatUI/Editor.meta deleted file mode 100644 index 6af1502d..00000000 --- a/RevenueCatUI/Editor.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 0629f5006c10940828fdfc2b9e3e2bf2 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/RevenueCatUI/Resources.meta b/RevenueCatUI/Resources.meta deleted file mode 100644 index 94cbd6c3..00000000 --- a/RevenueCatUI/Resources.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 9dd24195234294a83bca312d81fbf71e -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/RevenueCatUI/Runtime.meta b/RevenueCatUI/Runtime.meta deleted file mode 100644 index 3ba050bb..00000000 --- a/RevenueCatUI/Runtime.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 1d689e53bae3041f297355e1618b78df -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/RevenueCatUI/UI.meta b/RevenueCatUI/UI.meta deleted file mode 100644 index e58f0e74..00000000 --- a/RevenueCatUI/UI.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 995238eb5abdd41018e10ea0c9a5913e -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: From cdd8ea2269274f5951ccf0891abb163a496b5bfb Mon Sep 17 00:00:00 2001 From: Facundo Menzella Date: Mon, 15 Sep 2025 11:08:00 +0200 Subject: [PATCH 08/65] Updated bool IsSupported(); doc --- RevenueCatUI/README.md | 2 +- RevenueCatUI/Scripts/ICustomerCenterPresenter.cs | 6 ++++-- RevenueCatUI/Scripts/IPaywallPresenter.cs | 6 ++++-- RevenueCatUI/Scripts/RevenueCatUI.cs | 7 +++++-- 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/RevenueCatUI/README.md b/RevenueCatUI/README.md index 54f71d27..972767f7 100644 --- a/RevenueCatUI/README.md +++ b/RevenueCatUI/README.md @@ -87,7 +87,7 @@ Implemented now (stubbed behavior everywhere): - `PresentPaywall(PaywallOptions)` — same as above with options - `PresentPaywallIfNeeded(string[, PaywallOptions])` — returns `NotNeeded` immediately - `PresentCustomerCenter()` — completes immediately -- `IsSupported()` — returns `true` on devices; in Editor, `Paywall` is `true` and `Customer Center` may be `false` +- `IsSupported()` — returns `true` on iOS/Android device builds when both Paywall and Customer Center are available; returns `false` on other platforms (Editor, Windows, macOS, WebGL, etc.) ### PaywallOptions diff --git a/RevenueCatUI/Scripts/ICustomerCenterPresenter.cs b/RevenueCatUI/Scripts/ICustomerCenterPresenter.cs index 23778f44..549023bc 100644 --- a/RevenueCatUI/Scripts/ICustomerCenterPresenter.cs +++ b/RevenueCatUI/Scripts/ICustomerCenterPresenter.cs @@ -15,9 +15,11 @@ internal interface ICustomerCenterPresenter Task PresentCustomerCenterAsync(); /// - /// Checks if customer center presentation is supported on this platform. + /// Whether the customer center is supported on this platform. + /// Returns true on iOS/Android device builds; false on other platforms + /// (Editor, Windows, macOS, WebGL, etc.). /// - /// True if supported, false otherwise + /// True if supported on the current platform, otherwise false. bool IsSupported(); } } diff --git a/RevenueCatUI/Scripts/IPaywallPresenter.cs b/RevenueCatUI/Scripts/IPaywallPresenter.cs index c2a4abd9..14bd8897 100644 --- a/RevenueCatUI/Scripts/IPaywallPresenter.cs +++ b/RevenueCatUI/Scripts/IPaywallPresenter.cs @@ -24,9 +24,11 @@ internal interface IPaywallPresenter Task PresentPaywallIfNeededAsync(string requiredEntitlementIdentifier, PaywallOptions options); /// - /// Checks if paywall presentation is supported on this platform. + /// Whether paywall presentation is supported on this platform. + /// Returns true on iOS/Android device builds; false on other platforms + /// (Editor, Windows, macOS, WebGL, etc.). /// - /// True if supported, false otherwise + /// True if supported on the current platform, otherwise false. bool IsSupported(); } } diff --git a/RevenueCatUI/Scripts/RevenueCatUI.cs b/RevenueCatUI/Scripts/RevenueCatUI.cs index 23634b4f..4e2d937f 100644 --- a/RevenueCatUI/Scripts/RevenueCatUI.cs +++ b/RevenueCatUI/Scripts/RevenueCatUI.cs @@ -82,9 +82,12 @@ public static async Task PresentCustomerCenter() } /// - /// Checks if the RevenueCat UI components are available on the current platform. + /// Checks if RevenueCat UI is available on the current platform. + /// Returns true on iOS/Android device builds when both paywall and + /// customer center are supported; returns false on other platforms + /// (Editor, Windows, macOS, WebGL, etc.). /// - /// True if UI components are supported on this platform + /// True if UI is supported on this platform, otherwise false. public static bool IsSupported() { try From be566dd54d235f32a7be524d9d687363cc858fc1 Mon Sep 17 00:00:00 2001 From: Facundo Menzella Date: Mon, 15 Sep 2025 11:20:36 +0200 Subject: [PATCH 09/65] Added pure code options, removed GameObjecft --- .../Plugins/Android/RevenueCatUI.java | 25 +++++++-- RevenueCatUI/README.md | 1 + .../Android/AndroidCustomerCenterPresenter.cs | 26 ++++++++- .../Android/AndroidPaywallPresenter.cs | 27 ++++++++- .../Scripts/RevenueCatUICallbackHandler.cs | 55 ------------------- .../RevenueCatUICallbackHandler.cs.meta | 12 ---- 6 files changed, 67 insertions(+), 79 deletions(-) delete mode 100644 RevenueCatUI/Scripts/RevenueCatUICallbackHandler.cs delete mode 100644 RevenueCatUI/Scripts/RevenueCatUICallbackHandler.cs.meta diff --git a/RevenueCatUI/Plugins/Android/RevenueCatUI.java b/RevenueCatUI/Plugins/Android/RevenueCatUI.java index 5aeeeed6..9f587538 100644 --- a/RevenueCatUI/Plugins/Android/RevenueCatUI.java +++ b/RevenueCatUI/Plugins/Android/RevenueCatUI.java @@ -1,10 +1,22 @@ package com.revenuecat.unity; -import com.unity3d.player.UnityPlayer; - public class RevenueCatUI { - private static final String UNITY_CALLBACK_OBJECT = "RevenueCatUI"; + // Java-side callbacks registered from C#; no UnitySendMessage fallback. + public interface RevenueCatUICallbacks { + void onPaywallResult(String result); + void onCustomerCenterResult(String result); + } + + private static volatile RevenueCatUICallbacks callbacks; + + public static void registerCallbacks(RevenueCatUICallbacks cb) { + callbacks = cb; + } + + public static void unregisterCallbacks() { + callbacks = null; + } public static void presentPaywall(String offeringIdentifier) { sendPaywallResult("CANCELLED|Stub: no native UI"); @@ -24,14 +36,15 @@ public static boolean isSupported() { private static void sendPaywallResult(String result) { try { - UnityPlayer.UnitySendMessage(UNITY_CALLBACK_OBJECT, "OnPaywallResult", result); + RevenueCatUICallbacks cb = callbacks; + if (cb != null) cb.onPaywallResult(result); } catch (Throwable ignored) {} } private static void sendCustomerCenterResult(String result) { try { - UnityPlayer.UnitySendMessage(UNITY_CALLBACK_OBJECT, "OnCustomerCenterResult", result); + RevenueCatUICallbacks cb = callbacks; + if (cb != null) cb.onCustomerCenterResult(result); } catch (Throwable ignored) {} } } - diff --git a/RevenueCatUI/README.md b/RevenueCatUI/README.md index 972767f7..0eea1073 100644 --- a/RevenueCatUI/README.md +++ b/RevenueCatUI/README.md @@ -117,6 +117,7 @@ public enum PaywallResultType ### Native Implementation (reference only) Minimal native code is provided as a reference scaffold: - Android class: `com.revenuecat.unity.RevenueCatUI` + - Uses pure-code callbacks via a Java interface registered from C# (no GameObject required) - iOS functions: `rcui_presentPaywall`, `rcui_presentPaywallIfNeeded`, `rcui_presentCustomerCenter`, `rcui_isSupported` If/when you add real native UI, update the platform presenters in diff --git a/RevenueCatUI/Scripts/Platforms/Android/AndroidCustomerCenterPresenter.cs b/RevenueCatUI/Scripts/Platforms/Android/AndroidCustomerCenterPresenter.cs index 2e3d88ea..d037d718 100644 --- a/RevenueCatUI/Scripts/Platforms/Android/AndroidCustomerCenterPresenter.cs +++ b/RevenueCatUI/Scripts/Platforms/Android/AndroidCustomerCenterPresenter.cs @@ -9,13 +9,14 @@ namespace RevenueCat.UI.Platforms internal class AndroidCustomerCenterPresenter : ICustomerCenterPresenter { private readonly AndroidJavaClass _plugin; + private readonly CallbacksProxy _callbacks; private TaskCompletionSource _current; public AndroidCustomerCenterPresenter() { _plugin = new AndroidJavaClass("com.revenuecat.unity.RevenueCatUI"); - RevenueCatUICallbackHandler.Initialize(); - RevenueCatUICallbackHandler.SetAndroidCustomerCenterPresenter(this); + _callbacks = new CallbacksProxy(this); + try { _plugin.CallStatic("registerCallbacks", _callbacks); } catch { /* ignore */ } } public bool IsSupported() @@ -32,11 +33,30 @@ public Task PresentCustomerCenterAsync() return _current.Task; } - // Called from Java via UnitySendMessage + // Called from Java via AndroidJavaProxy public void OnCustomerCenterResult(string resultData) { _current?.TrySetResult(true); } + + private class CallbacksProxy : AndroidJavaProxy + { + private readonly AndroidCustomerCenterPresenter _owner; + public CallbacksProxy(AndroidCustomerCenterPresenter owner) : base("com.revenuecat.unity.RevenueCatUI$RevenueCatUICallbacks") + { + _owner = owner; + } + + public void onPaywallResult(string result) + { + // No-op for customer center presenter + } + + public void onCustomerCenterResult(string result) + { + _owner.OnCustomerCenterResult(result); + } + } } } #endif diff --git a/RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs b/RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs index 6f7c12ca..3268e4ff 100644 --- a/RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs +++ b/RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs @@ -9,13 +9,14 @@ namespace RevenueCat.UI.Platforms internal class AndroidPaywallPresenter : IPaywallPresenter { private readonly AndroidJavaClass _plugin; + private readonly CallbacksProxy _callbacks; private TaskCompletionSource _current; public AndroidPaywallPresenter() { _plugin = new AndroidJavaClass("com.revenuecat.unity.RevenueCatUI"); - RevenueCatUICallbackHandler.Initialize(); - RevenueCatUICallbackHandler.SetAndroidPresenter(this); + _callbacks = new CallbacksProxy(this); + try { _plugin.CallStatic("registerCallbacks", _callbacks); } catch { /* ignore */ } } public bool IsSupported() @@ -54,7 +55,7 @@ public Task PresentPaywallIfNeededAsync(string requiredEntitlemen return _current.Task; } - // Called from Java via UnitySendMessage + // Called from Java via AndroidJavaProxy public void OnPaywallResult(string resultData) { if (_current == null) return; @@ -69,6 +70,26 @@ public void OnPaywallResult(string resultData) _current.TrySetResult(PaywallResult.Error); } } + + private class CallbacksProxy : AndroidJavaProxy + { + private readonly AndroidPaywallPresenter _owner; + public CallbacksProxy(AndroidPaywallPresenter owner) : base("com.revenuecat.unity.RevenueCatUI$RevenueCatUICallbacks") + { + _owner = owner; + } + + // Signature matches Java interface + public void onPaywallResult(string result) + { + _owner.OnPaywallResult(result); + } + + public void onCustomerCenterResult(string result) + { + // No-op for paywall presenter + } + } } } #endif diff --git a/RevenueCatUI/Scripts/RevenueCatUICallbackHandler.cs b/RevenueCatUI/Scripts/RevenueCatUICallbackHandler.cs deleted file mode 100644 index 35e712bd..00000000 --- a/RevenueCatUI/Scripts/RevenueCatUICallbackHandler.cs +++ /dev/null @@ -1,55 +0,0 @@ -using UnityEngine; - -namespace RevenueCat.UI.Internal -{ - /// - /// Handles callbacks from native RevenueCat UI platforms - /// - internal class RevenueCatUICallbackHandler : MonoBehaviour - { - private static RevenueCatUICallbackHandler _instance; - -#if UNITY_ANDROID && !UNITY_EDITOR - private static Platforms.AndroidPaywallPresenter _androidPresenter; - private static Platforms.AndroidCustomerCenterPresenter _androidCustomerCenterPresenter; -#endif - - public static void Initialize() - { - if (_instance == null) - { - var go = new GameObject("RevenueCatUI"); - _instance = go.AddComponent(); - DontDestroyOnLoad(go); - } - } - -#if UNITY_ANDROID && !UNITY_EDITOR - public static void SetAndroidPresenter(Platforms.AndroidPaywallPresenter presenter) - { - _androidPresenter = presenter; - } - - public static void SetAndroidCustomerCenterPresenter(Platforms.AndroidCustomerCenterPresenter presenter) - { - _androidCustomerCenterPresenter = presenter; - } -#endif - - // Called from Android via UnitySendMessage - public void OnPaywallResult(string resultData) - { -#if UNITY_ANDROID && !UNITY_EDITOR - _androidPresenter?.OnPaywallResult(resultData); -#endif - } - - // Called from Android via UnitySendMessage - public void OnCustomerCenterResult(string resultData) - { -#if UNITY_ANDROID && !UNITY_EDITOR - _androidCustomerCenterPresenter?.OnCustomerCenterResult(resultData); -#endif - } - } -} diff --git a/RevenueCatUI/Scripts/RevenueCatUICallbackHandler.cs.meta b/RevenueCatUI/Scripts/RevenueCatUICallbackHandler.cs.meta deleted file mode 100644 index 6f9de5fc..00000000 --- a/RevenueCatUI/Scripts/RevenueCatUICallbackHandler.cs.meta +++ /dev/null @@ -1,12 +0,0 @@ -fileFormatVersion: 2 -guid: e0d48b0098bd945449d998a3e7cf40d5 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: - From c550a2ea6b5eb34b944fd2f44ac74f29af300e5e Mon Sep 17 00:00:00 2001 From: Facundo Menzella Date: Mon, 15 Sep 2025 11:26:57 +0200 Subject: [PATCH 10/65] Split callback --- .../Plugins/Android/RevenueCatUI.java | 23 ++++++++----------- RevenueCatUI/README.md | 4 +++- .../Android/AndroidCustomerCenterPresenter.cs | 11 +++------ .../Android/AndroidPaywallPresenter.cs | 11 +++------ 4 files changed, 19 insertions(+), 30 deletions(-) diff --git a/RevenueCatUI/Plugins/Android/RevenueCatUI.java b/RevenueCatUI/Plugins/Android/RevenueCatUI.java index 9f587538..aff01bfd 100644 --- a/RevenueCatUI/Plugins/Android/RevenueCatUI.java +++ b/RevenueCatUI/Plugins/Android/RevenueCatUI.java @@ -3,20 +3,17 @@ public class RevenueCatUI { // Java-side callbacks registered from C#; no UnitySendMessage fallback. - public interface RevenueCatUICallbacks { - void onPaywallResult(String result); - void onCustomerCenterResult(String result); - } + public interface PaywallCallbacks { void onPaywallResult(String result); } + public interface CustomerCenterCallbacks { void onCustomerCenterResult(String result); } - private static volatile RevenueCatUICallbacks callbacks; + private static volatile PaywallCallbacks paywallCallbacks; + private static volatile CustomerCenterCallbacks customerCenterCallbacks; - public static void registerCallbacks(RevenueCatUICallbacks cb) { - callbacks = cb; - } + public static void registerPaywallCallbacks(PaywallCallbacks cb) { paywallCallbacks = cb; } + public static void unregisterPaywallCallbacks() { paywallCallbacks = null; } - public static void unregisterCallbacks() { - callbacks = null; - } + public static void registerCustomerCenterCallbacks(CustomerCenterCallbacks cb) { customerCenterCallbacks = cb; } + public static void unregisterCustomerCenterCallbacks() { customerCenterCallbacks = null; } public static void presentPaywall(String offeringIdentifier) { sendPaywallResult("CANCELLED|Stub: no native UI"); @@ -36,14 +33,14 @@ public static boolean isSupported() { private static void sendPaywallResult(String result) { try { - RevenueCatUICallbacks cb = callbacks; + PaywallCallbacks cb = paywallCallbacks; if (cb != null) cb.onPaywallResult(result); } catch (Throwable ignored) {} } private static void sendCustomerCenterResult(String result) { try { - RevenueCatUICallbacks cb = callbacks; + CustomerCenterCallbacks cb = customerCenterCallbacks; if (cb != null) cb.onCustomerCenterResult(result); } catch (Throwable ignored) {} } diff --git a/RevenueCatUI/README.md b/RevenueCatUI/README.md index 0eea1073..26bf8b93 100644 --- a/RevenueCatUI/README.md +++ b/RevenueCatUI/README.md @@ -117,8 +117,10 @@ public enum PaywallResultType ### Native Implementation (reference only) Minimal native code is provided as a reference scaffold: - Android class: `com.revenuecat.unity.RevenueCatUI` - - Uses pure-code callbacks via a Java interface registered from C# (no GameObject required) + - Uses pure-code callbacks via Java interfaces registered from C# (no GameObject required) + - Separate callbacks per feature: `PaywallCallbacks` and `CustomerCenterCallbacks` - iOS functions: `rcui_presentPaywall`, `rcui_presentPaywallIfNeeded`, `rcui_presentCustomerCenter`, `rcui_isSupported` + - Pure-code C callbacks per feature: paywall functions return results via a paywall callback; customer center uses its own callback. No GameObject required. If/when you add real native UI, update the platform presenters in `RevenueCatUI/Scripts/Platforms/{Android,iOS}` to select your implementations. diff --git a/RevenueCatUI/Scripts/Platforms/Android/AndroidCustomerCenterPresenter.cs b/RevenueCatUI/Scripts/Platforms/Android/AndroidCustomerCenterPresenter.cs index d037d718..c826d267 100644 --- a/RevenueCatUI/Scripts/Platforms/Android/AndroidCustomerCenterPresenter.cs +++ b/RevenueCatUI/Scripts/Platforms/Android/AndroidCustomerCenterPresenter.cs @@ -2,7 +2,7 @@ using System; using System.Threading.Tasks; using UnityEngine; -using RevenueCat.UI.Internal; +// No Internal handler needed; Android uses direct callbacks via AndroidJavaProxy namespace RevenueCat.UI.Platforms { @@ -16,7 +16,7 @@ public AndroidCustomerCenterPresenter() { _plugin = new AndroidJavaClass("com.revenuecat.unity.RevenueCatUI"); _callbacks = new CallbacksProxy(this); - try { _plugin.CallStatic("registerCallbacks", _callbacks); } catch { /* ignore */ } + try { _plugin.CallStatic("registerCustomerCenterCallbacks", _callbacks); } catch { /* ignore */ } } public bool IsSupported() @@ -42,16 +42,11 @@ public void OnCustomerCenterResult(string resultData) private class CallbacksProxy : AndroidJavaProxy { private readonly AndroidCustomerCenterPresenter _owner; - public CallbacksProxy(AndroidCustomerCenterPresenter owner) : base("com.revenuecat.unity.RevenueCatUI$RevenueCatUICallbacks") + public CallbacksProxy(AndroidCustomerCenterPresenter owner) : base("com.revenuecat.unity.RevenueCatUI$CustomerCenterCallbacks") { _owner = owner; } - public void onPaywallResult(string result) - { - // No-op for customer center presenter - } - public void onCustomerCenterResult(string result) { _owner.OnCustomerCenterResult(result); diff --git a/RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs b/RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs index 3268e4ff..2581d27b 100644 --- a/RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs +++ b/RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs @@ -2,7 +2,7 @@ using System; using System.Threading.Tasks; using UnityEngine; -using RevenueCat.UI.Internal; +// No Internal handler needed; Android uses direct callbacks via AndroidJavaProxy namespace RevenueCat.UI.Platforms { @@ -16,7 +16,7 @@ public AndroidPaywallPresenter() { _plugin = new AndroidJavaClass("com.revenuecat.unity.RevenueCatUI"); _callbacks = new CallbacksProxy(this); - try { _plugin.CallStatic("registerCallbacks", _callbacks); } catch { /* ignore */ } + try { _plugin.CallStatic("registerPaywallCallbacks", _callbacks); } catch { /* ignore */ } } public bool IsSupported() @@ -74,7 +74,7 @@ public void OnPaywallResult(string resultData) private class CallbacksProxy : AndroidJavaProxy { private readonly AndroidPaywallPresenter _owner; - public CallbacksProxy(AndroidPaywallPresenter owner) : base("com.revenuecat.unity.RevenueCatUI$RevenueCatUICallbacks") + public CallbacksProxy(AndroidPaywallPresenter owner) : base("com.revenuecat.unity.RevenueCatUI$PaywallCallbacks") { _owner = owner; } @@ -84,11 +84,6 @@ public void onPaywallResult(string result) { _owner.OnPaywallResult(result); } - - public void onCustomerCenterResult(string result) - { - // No-op for paywall presenter - } } } } From d1d075e1d4bdf903cb63fae266b1ac35743de68f Mon Sep 17 00:00:00 2001 From: Facundo Menzella Date: Mon, 15 Sep 2025 11:48:22 +0200 Subject: [PATCH 11/65] added debug logs --- .../Android/AndroidPaywallPresenter.cs | 11 +++-- .../Platforms/iOS/IOSPaywallPresenter.cs | 41 +++++++++++++++---- RevenueCatUI/Scripts/RevenueCatUI.cs | 7 +++- 3 files changed, 48 insertions(+), 11 deletions(-) diff --git a/RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs b/RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs index 2581d27b..c94db836 100644 --- a/RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs +++ b/RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs @@ -31,10 +31,12 @@ public Task PresentPaywallAsync(PaywallOptions options) try { var offering = options?.OfferingIdentifier; + Debug.Log($"[RevenueCatUI][Android] presentPaywall offering='{offering ?? ""}'"); _plugin.CallStatic("presentPaywall", offering); } - catch (Exception) + catch (Exception e) { + Debug.LogError($"[RevenueCatUI][Android] Exception in presentPaywall: {e.Message}"); _current.TrySetResult(PaywallResult.Error); } return _current.Task; @@ -46,10 +48,12 @@ public Task PresentPaywallIfNeededAsync(string requiredEntitlemen try { var offering = options?.OfferingIdentifier; + Debug.Log($"[RevenueCatUI][Android] presentPaywallIfNeeded entitlement='{requiredEntitlementIdentifier}', offering='{offering ?? ""}'"); _plugin.CallStatic("presentPaywallIfNeeded", requiredEntitlementIdentifier, offering); } - catch (Exception) + catch (Exception e) { + Debug.LogError($"[RevenueCatUI][Android] Exception in presentPaywallIfNeeded: {e.Message}"); _current.TrySetResult(PaywallResult.Error); } return _current.Task; @@ -65,8 +69,9 @@ public void OnPaywallResult(string resultData) var type = PaywallResultTypeExtensions.FromNativeString(token); _current.TrySetResult(new PaywallResult(type)); } - catch + catch (Exception e) { + Debug.LogError($"[RevenueCatUI][Android] Failed to handle paywall result '{resultData}': {e.Message}. Setting Error."); _current.TrySetResult(PaywallResult.Error); } } diff --git a/RevenueCatUI/Scripts/Platforms/iOS/IOSPaywallPresenter.cs b/RevenueCatUI/Scripts/Platforms/iOS/IOSPaywallPresenter.cs index fb3c3be7..899c1c8d 100644 --- a/RevenueCatUI/Scripts/Platforms/iOS/IOSPaywallPresenter.cs +++ b/RevenueCatUI/Scripts/Platforms/iOS/IOSPaywallPresenter.cs @@ -20,25 +20,52 @@ internal class IOSPaywallPresenter : IPaywallPresenter public Task PresentPaywallAsync(PaywallOptions options) { s_current = new TaskCompletionSource(); - rcui_presentPaywall(options?.OfferingIdentifier, options?.DisplayCloseButton ?? false, OnResult); + try + { + rcui_presentPaywall(options?.OfferingIdentifier, options?.DisplayCloseButton ?? false, OnResult); + } + catch (Exception e) + { + UnityEngine.Debug.LogError($"[RevenueCatUI][iOS] Exception in presentPaywall: {e.Message}"); + s_current.TrySetResult(PaywallResult.Error); + } return s_current.Task; } public Task PresentPaywallIfNeededAsync(string requiredEntitlementIdentifier, PaywallOptions options) { s_current = new TaskCompletionSource(); - rcui_presentPaywallIfNeeded(requiredEntitlementIdentifier, options?.OfferingIdentifier, options?.DisplayCloseButton ?? false, OnResult); + try + { + rcui_presentPaywallIfNeeded(requiredEntitlementIdentifier, options?.OfferingIdentifier, options?.DisplayCloseButton ?? false, OnResult); + } + catch (Exception e) + { + UnityEngine.Debug.LogError($"[RevenueCatUI][iOS] Exception in presentPaywallIfNeeded: {e.Message}"); + s_current.TrySetResult(PaywallResult.Error); + } return s_current.Task; } [AOT.MonoPInvokeCallback(typeof(PaywallResultCallback))] private static void OnResult(string result) { - var token = (result ?? "ERROR"); - var native = token.Split('|')[0]; - var type = PaywallResultTypeExtensions.FromNativeString(native); - s_current?.TrySetResult(new PaywallResult(type)); - s_current = null; + try + { + var token = (result ?? "ERROR"); + var native = token.Split('|')[0]; + var type = PaywallResultTypeExtensions.FromNativeString(native); + s_current?.TrySetResult(new PaywallResult(type)); + } + catch (Exception e) + { + UnityEngine.Debug.LogError($"[RevenueCatUI][iOS] Failed to handle paywall result '{result}': {e.Message}. Setting Error."); + s_current?.TrySetResult(PaywallResult.Error); + } + finally + { + s_current = null; + } } } } diff --git a/RevenueCatUI/Scripts/RevenueCatUI.cs b/RevenueCatUI/Scripts/RevenueCatUI.cs index 4e2d937f..bbb982bd 100644 --- a/RevenueCatUI/Scripts/RevenueCatUI.cs +++ b/RevenueCatUI/Scripts/RevenueCatUI.cs @@ -94,10 +94,15 @@ public static bool IsSupported() { var paywallPresenter = PaywallPresenter.Instance; var customerCenterPresenter = CustomerCenterPresenter.Instance; - return paywallPresenter.IsSupported() && customerCenterPresenter.IsSupported(); + var paywall = paywallPresenter.IsSupported(); + var customerCenter = customerCenterPresenter.IsSupported(); + var supported = paywall && customerCenter; + Debug.Log($"[RevenueCatUI] IsSupported check -> Paywall={paywall}, CustomerCenter={customerCenter}, Result={supported}"); + return supported; } catch { + Debug.Log("[RevenueCatUI] IsSupported check threw; returning false"); return false; } } From 1a0e59c1c3d2c31cd2f6876e4a7d0568c0dd7476 Mon Sep 17 00:00:00 2001 From: Facundo Menzella Date: Mon, 15 Sep 2025 11:53:06 +0200 Subject: [PATCH 12/65] Remove StubPaywallPresenter.cs --- RevenueCatUI/Scripts/PaywallPresenter.cs | 4 +-- .../Platforms/Stub/StubPaywallPresenter.cs | 32 ------------------- .../Stub/StubPaywallPresenter.cs.meta | 12 ------- 3 files changed, 2 insertions(+), 46 deletions(-) delete mode 100644 RevenueCatUI/Scripts/Platforms/Stub/StubPaywallPresenter.cs delete mode 100644 RevenueCatUI/Scripts/Platforms/Stub/StubPaywallPresenter.cs.meta diff --git a/RevenueCatUI/Scripts/PaywallPresenter.cs b/RevenueCatUI/Scripts/PaywallPresenter.cs index ec7dba7a..6498a926 100644 --- a/RevenueCatUI/Scripts/PaywallPresenter.cs +++ b/RevenueCatUI/Scripts/PaywallPresenter.cs @@ -32,8 +32,8 @@ private static IPaywallPresenter CreatePlatformPresenter() #elif UNITY_ANDROID && !UNITY_EDITOR return new Platforms.AndroidPaywallPresenter(); #else - // Editor/other: fall back to C# stub - return new Platforms.Stub.StubPaywallPresenter(); + // Editor/other: unsupported implementation (no UI) + return new UnsupportedPaywallPresenter(); #endif } } diff --git a/RevenueCatUI/Scripts/Platforms/Stub/StubPaywallPresenter.cs b/RevenueCatUI/Scripts/Platforms/Stub/StubPaywallPresenter.cs deleted file mode 100644 index f6e39e0e..00000000 --- a/RevenueCatUI/Scripts/Platforms/Stub/StubPaywallPresenter.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using System.Threading.Tasks; -using UnityEngine; - -namespace RevenueCat.UI.Platforms.Stub -{ - /// - /// Stub implementation that simulates paywall behavior without native SDKs. - /// Enabled when scripting define symbol REVENUECAT_UI_STUBS is present. - /// - internal class StubPaywallPresenter : IPaywallPresenter - { - public bool IsSupported() - { - return true; - } - - public async Task PresentPaywallAsync(PaywallOptions options) - { - Debug.Log("[RevenueCatUI][Stub] PresentPaywall called. Simulating user cancel."); - await Task.Yield(); - return PaywallResult.Cancelled; - } - - public async Task PresentPaywallIfNeededAsync(string requiredEntitlementIdentifier, PaywallOptions options) - { - Debug.Log($"[RevenueCatUI][Stub] PresentPaywallIfNeeded called for entitlement '{requiredEntitlementIdentifier}'. Returning NotNeeded."); - await Task.Yield(); - return PaywallResult.NotNeeded; - } - } -} diff --git a/RevenueCatUI/Scripts/Platforms/Stub/StubPaywallPresenter.cs.meta b/RevenueCatUI/Scripts/Platforms/Stub/StubPaywallPresenter.cs.meta deleted file mode 100644 index b587fa14..00000000 --- a/RevenueCatUI/Scripts/Platforms/Stub/StubPaywallPresenter.cs.meta +++ /dev/null @@ -1,12 +0,0 @@ -fileFormatVersion: 2 -guid: a214f790c33274faba8456f09bef6e29 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: - From 8b7784bc688282cd043fcd465cb1876a6d7dc758 Mon Sep 17 00:00:00 2001 From: Facundo Menzella Date: Mon, 15 Sep 2025 12:00:08 +0200 Subject: [PATCH 13/65] Delete RevenueCatUICallbackHandler.cs --- RevenueCatUI/Editor.meta | 9 +++++++++ RevenueCatUI/Editor/RevenueCat.UI.Editor.asmdef | 17 +++++++++++++++++ .../Editor/RevenueCat.UI.Editor.asmdef.meta | 8 ++++++++ 3 files changed, 34 insertions(+) create mode 100644 RevenueCatUI/Editor.meta create mode 100644 RevenueCatUI/Editor/RevenueCat.UI.Editor.asmdef create mode 100644 RevenueCatUI/Editor/RevenueCat.UI.Editor.asmdef.meta diff --git a/RevenueCatUI/Editor.meta b/RevenueCatUI/Editor.meta new file mode 100644 index 00000000..ac146dcb --- /dev/null +++ b/RevenueCatUI/Editor.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 3a90b2d732664b2b8c4640a9b65cfe21 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: + diff --git a/RevenueCatUI/Editor/RevenueCat.UI.Editor.asmdef b/RevenueCatUI/Editor/RevenueCat.UI.Editor.asmdef new file mode 100644 index 00000000..021e01c2 --- /dev/null +++ b/RevenueCatUI/Editor/RevenueCat.UI.Editor.asmdef @@ -0,0 +1,17 @@ +{ + "name": "RevenueCat.UI.Editor", + "rootNamespace": "RevenueCat.UI", + "references": [], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} + diff --git a/RevenueCatUI/Editor/RevenueCat.UI.Editor.asmdef.meta b/RevenueCatUI/Editor/RevenueCat.UI.Editor.asmdef.meta new file mode 100644 index 00000000..31499d52 --- /dev/null +++ b/RevenueCatUI/Editor/RevenueCat.UI.Editor.asmdef.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e6c6f27f8e4c4f6e9e0d8a9a8f1b2c3d +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: + From 69096d47903b5d36ca05bf15aa0461ce92ab35a5 Mon Sep 17 00:00:00 2001 From: Facundo Menzella Date: Mon, 15 Sep 2025 12:44:06 +0200 Subject: [PATCH 14/65] Add missing pieces --- RevenueCatUI/Plugins/Android/RevenueCatUI.java | 4 ++++ RevenueCatUI/Plugins/iOS/RevenueCatUI.m | 10 +++++++++- RevenueCatUI/Scripts.meta | 8 ++++++++ RevenueCatUI/Scripts/Platforms.meta | 8 ++++++++ RevenueCatUI/Scripts/Platforms/Android.meta | 8 ++++++++ RevenueCatUI/Scripts/Platforms/Stub.meta | 8 ++++++++ RevenueCatUI/Scripts/Platforms/iOS.meta | 8 ++++++++ .../Scripts/Platforms/iOS/IOSPaywallPresenter.cs | 14 ++++++++------ 8 files changed, 61 insertions(+), 7 deletions(-) create mode 100644 RevenueCatUI/Scripts.meta create mode 100644 RevenueCatUI/Scripts/Platforms.meta create mode 100644 RevenueCatUI/Scripts/Platforms/Android.meta create mode 100644 RevenueCatUI/Scripts/Platforms/Stub.meta create mode 100644 RevenueCatUI/Scripts/Platforms/iOS.meta diff --git a/RevenueCatUI/Plugins/Android/RevenueCatUI.java b/RevenueCatUI/Plugins/Android/RevenueCatUI.java index aff01bfd..39b8472f 100644 --- a/RevenueCatUI/Plugins/Android/RevenueCatUI.java +++ b/RevenueCatUI/Plugins/Android/RevenueCatUI.java @@ -16,18 +16,22 @@ public interface CustomerCenterCallbacks { void onCustomerCenterResult(String re public static void unregisterCustomerCenterCallbacks() { customerCenterCallbacks = null; } public static void presentPaywall(String offeringIdentifier) { + android.util.Log.d("RevenueCatUI", "presentPaywall(offering=" + offeringIdentifier + ")"); sendPaywallResult("CANCELLED|Stub: no native UI"); } public static void presentPaywallIfNeeded(String requiredEntitlementIdentifier, String offeringIdentifier) { + android.util.Log.d("RevenueCatUI", "presentPaywallIfNeeded(entitlement=" + requiredEntitlementIdentifier + ", offering=" + offeringIdentifier + ")"); sendPaywallResult("NOT_PRESENTED|Stub: no native UI"); } public static void presentCustomerCenter() { + android.util.Log.d("RevenueCatUI", "presentCustomerCenter()"); sendCustomerCenterResult("DONE|Stub: no native UI"); } public static boolean isSupported() { + android.util.Log.d("RevenueCatUI", "isSupported() -> true (stub)"); return true; } diff --git a/RevenueCatUI/Plugins/iOS/RevenueCatUI.m b/RevenueCatUI/Plugins/iOS/RevenueCatUI.m index 4a0b0a1c..d3100f48 100644 --- a/RevenueCatUI/Plugins/iOS/RevenueCatUI.m +++ b/RevenueCatUI/Plugins/iOS/RevenueCatUI.m @@ -6,24 +6,32 @@ typedef void (*RCUICustomerCenterCallback)(void); void rcui_presentPaywall(const char* offeringIdentifier, bool displayCloseButton, RCUIPaywallResultCallback callback) { + NSLog(@"[RevenueCatUI][iOS] presentPaywall(offering=%@, closeButton=%@)", + offeringIdentifier ? [NSString stringWithUTF8String:offeringIdentifier] : @"", + displayCloseButton ? @"true" : @"false"); if (callback) { callback("CANCELLED|Stub: no native UI"); } } void rcui_presentPaywallIfNeeded(const char* requiredEntitlementIdentifier, const char* offeringIdentifier, bool displayCloseButton, RCUIPaywallResultCallback callback) { + NSLog(@"[RevenueCatUI][iOS] presentPaywallIfNeeded(entitlement=%@, offering=%@, closeButton=%@)", + requiredEntitlementIdentifier ? [NSString stringWithUTF8String:requiredEntitlementIdentifier] : @"", + offeringIdentifier ? [NSString stringWithUTF8String:offeringIdentifier] : @"", + displayCloseButton ? @"true" : @"false"); if (callback) { callback("NOT_PRESENTED|Stub: no native UI"); } } void rcui_presentCustomerCenter(RCUICustomerCenterCallback callback) { + NSLog(@"[RevenueCatUI][iOS] presentCustomerCenter()"); if (callback) { callback(); } } bool rcui_isSupported() { + NSLog(@"[RevenueCatUI][iOS] isSupported() -> true (stub)"); return true; } - diff --git a/RevenueCatUI/Scripts.meta b/RevenueCatUI/Scripts.meta new file mode 100644 index 00000000..7392077c --- /dev/null +++ b/RevenueCatUI/Scripts.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 71014ed945e494d6ea177f82e50848fb +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/RevenueCatUI/Scripts/Platforms.meta b/RevenueCatUI/Scripts/Platforms.meta new file mode 100644 index 00000000..4565320c --- /dev/null +++ b/RevenueCatUI/Scripts/Platforms.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 2f1e7995b8997464ab5782afd00c8e36 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/RevenueCatUI/Scripts/Platforms/Android.meta b/RevenueCatUI/Scripts/Platforms/Android.meta new file mode 100644 index 00000000..a0700c28 --- /dev/null +++ b/RevenueCatUI/Scripts/Platforms/Android.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 4f907524b76324944b79bf17d92dc9d1 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/RevenueCatUI/Scripts/Platforms/Stub.meta b/RevenueCatUI/Scripts/Platforms/Stub.meta new file mode 100644 index 00000000..e7e94354 --- /dev/null +++ b/RevenueCatUI/Scripts/Platforms/Stub.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b19156d7290a3412ca61decc091e1a1d +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/RevenueCatUI/Scripts/Platforms/iOS.meta b/RevenueCatUI/Scripts/Platforms/iOS.meta new file mode 100644 index 00000000..6b39c977 --- /dev/null +++ b/RevenueCatUI/Scripts/Platforms/iOS.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 49452c74706dd4f08a6c9af8de46966e +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/RevenueCatUI/Scripts/Platforms/iOS/IOSPaywallPresenter.cs b/RevenueCatUI/Scripts/Platforms/iOS/IOSPaywallPresenter.cs index 899c1c8d..aa94b152 100644 --- a/RevenueCatUI/Scripts/Platforms/iOS/IOSPaywallPresenter.cs +++ b/RevenueCatUI/Scripts/Platforms/iOS/IOSPaywallPresenter.cs @@ -19,7 +19,8 @@ internal class IOSPaywallPresenter : IPaywallPresenter public Task PresentPaywallAsync(PaywallOptions options) { - s_current = new TaskCompletionSource(); + var tcs = new TaskCompletionSource(); + s_current = tcs; try { rcui_presentPaywall(options?.OfferingIdentifier, options?.DisplayCloseButton ?? false, OnResult); @@ -27,14 +28,15 @@ public Task PresentPaywallAsync(PaywallOptions options) catch (Exception e) { UnityEngine.Debug.LogError($"[RevenueCatUI][iOS] Exception in presentPaywall: {e.Message}"); - s_current.TrySetResult(PaywallResult.Error); + tcs.TrySetResult(PaywallResult.Error); } - return s_current.Task; + return tcs.Task; } public Task PresentPaywallIfNeededAsync(string requiredEntitlementIdentifier, PaywallOptions options) { - s_current = new TaskCompletionSource(); + var tcs = new TaskCompletionSource(); + s_current = tcs; try { rcui_presentPaywallIfNeeded(requiredEntitlementIdentifier, options?.OfferingIdentifier, options?.DisplayCloseButton ?? false, OnResult); @@ -42,9 +44,9 @@ public Task PresentPaywallIfNeededAsync(string requiredEntitlemen catch (Exception e) { UnityEngine.Debug.LogError($"[RevenueCatUI][iOS] Exception in presentPaywallIfNeeded: {e.Message}"); - s_current.TrySetResult(PaywallResult.Error); + tcs.TrySetResult(PaywallResult.Error); } - return s_current.Task; + return tcs.Task; } [AOT.MonoPInvokeCallback(typeof(PaywallResultCallback))] From 1ee5dcc313e24000fad3a021fb1c897b4bdd131b Mon Sep 17 00:00:00 2001 From: Facundo Menzella Date: Mon, 15 Sep 2025 13:26:12 +0200 Subject: [PATCH 15/65] ready --- RevenueCatUI/README.md | 27 +++++++++++------ RevenueCatUI/Scripts/PaywallResult.cs | 2 ++ .../Android/AndroidCustomerCenterPresenter.cs | 16 ++++++++-- .../Android/AndroidPaywallPresenter.cs | 23 ++++++++++++++ .../iOS/IOSCustomerCenterPresenter.cs | 22 ++++++++++++-- .../Platforms/iOS/IOSPaywallPresenter.cs | 14 +++++++++ RevenueCatUI/Scripts/RevenueCatUI.cs | 30 ++++++++++++++++++- RevenueCatUI/package.json | 4 +-- 8 files changed, 121 insertions(+), 17 deletions(-) diff --git a/RevenueCatUI/README.md b/RevenueCatUI/README.md index 26bf8b93..41a07fa7 100644 --- a/RevenueCatUI/README.md +++ b/RevenueCatUI/README.md @@ -3,9 +3,10 @@ This package provides the Unity API surface for RevenueCat UI (paywalls and customer center), implemented with lightweight stubs so you can wire your flows without native dependencies. -Use this to integrate and test control flow in Editor and on devices. Device builds route -through minimal native stubs; Editor runs C# fallbacks. When ready, replace the minimal -native code with your real implementations. +Use this to integrate and test control flow on devices. Device builds route +through minimal native stubs; in the Unity Editor the UI is reported as +unsupported by default (no UI shown). When ready, replace the minimal native +code with your real implementations. ## Installation @@ -22,16 +23,16 @@ Add to your Unity project's `Packages/manifest.json`: ### Requirements -- Unity 2022.3 LTS or later +- Unity 2022.3 LTS or later (package.json enforces 2022.3) - RevenueCat Unity SDK (this package depends on the main RevenueCat package) ### Dependencies By default this package runs in stub mode. On iOS/Android device builds, the API routes through minimal native stubs that immediately return results (no -external dependencies). In the Editor and other platforms, C# fallbacks return -the same immediate results. When you’re ready for real UI, replace the minimal -native code with your implementation and add dependencies as needed. +external dependencies). In the Editor and other platforms, the API reports +unsupported and no UI is presented. When you’re ready for real UI, replace the +minimal native code with your implementation and add dependencies as needed. ## Quick Start @@ -85,9 +86,11 @@ public class MySubscriptionManager : MonoBehaviour Implemented now (stubbed behavior everywhere): - `PresentPaywall()` — returns `Cancelled` immediately - `PresentPaywall(PaywallOptions)` — same as above with options -- `PresentPaywallIfNeeded(string[, PaywallOptions])` — returns `NotNeeded` immediately +- `PresentPaywallIfNeeded(string[, PaywallOptions])` — returns `NotPresented` immediately - `PresentCustomerCenter()` — completes immediately - `IsSupported()` — returns `true` on iOS/Android device builds when both Paywall and Customer Center are available; returns `false` on other platforms (Editor, Windows, macOS, WebGL, etc.) +- `IsPaywallSupported()` — per-feature capability +- `IsCustomerCenterSupported()` — per-feature capability ### PaywallOptions @@ -130,10 +133,16 @@ If/when you add real native UI, update the platform presenters in - Graceful fallbacks for unsupported platforms - Unity-style async/await throughout +### Threading and Logging +- Callbacks may complete on any thread. If you need to interact with Unity APIs, + dispatch back to the main thread (e.g., via a main-thread dispatcher). +- Logs are verbose in Development builds. Production builds minimize logs; gate + additional logging behind your own flags where needed. + ## Platform Support -- Editor / Any platform: C# fallbacks return immediate results (no UI) - iOS/Android device: Minimal native stubs return immediate results (no UI) +- Editor / Other platforms: Reported unsupported (no UI) ## Examples diff --git a/RevenueCatUI/Scripts/PaywallResult.cs b/RevenueCatUI/Scripts/PaywallResult.cs index d90280ce..1f4d2ecd 100644 --- a/RevenueCatUI/Scripts/PaywallResult.cs +++ b/RevenueCatUI/Scripts/PaywallResult.cs @@ -23,7 +23,9 @@ public PaywallResult(PaywallResultType result) } // Convenient static properties for common results + [System.Obsolete("Use NotPresented instead")] public static PaywallResult NotNeeded => new PaywallResult(PaywallResultType.NotPresented); + public static PaywallResult NotPresented => new PaywallResult(PaywallResultType.NotPresented); public static PaywallResult Cancelled => new PaywallResult(PaywallResultType.Cancelled); public static PaywallResult Error => new PaywallResult(PaywallResultType.Error); public static PaywallResult Purchased => new PaywallResult(PaywallResultType.Purchased); diff --git a/RevenueCatUI/Scripts/Platforms/Android/AndroidCustomerCenterPresenter.cs b/RevenueCatUI/Scripts/Platforms/Android/AndroidCustomerCenterPresenter.cs index c826d267..2480e8d0 100644 --- a/RevenueCatUI/Scripts/Platforms/Android/AndroidCustomerCenterPresenter.cs +++ b/RevenueCatUI/Scripts/Platforms/Android/AndroidCustomerCenterPresenter.cs @@ -19,6 +19,11 @@ public AndroidCustomerCenterPresenter() try { _plugin.CallStatic("registerCustomerCenterCallbacks", _callbacks); } catch { /* ignore */ } } + ~AndroidCustomerCenterPresenter() + { + try { _plugin.CallStatic("unregisterCustomerCenterCallbacks"); } catch { } + } + public bool IsSupported() { try { return _plugin.CallStatic("isSupported"); } @@ -27,16 +32,23 @@ public bool IsSupported() public Task PresentCustomerCenterAsync() { + if (_current != null && !_current.Task.IsCompleted) + { + Debug.LogWarning("[RevenueCatUI][Android] Customer Center already in progress; rejecting new request."); + return Task.CompletedTask; + } + _current = new TaskCompletionSource(); try { _plugin.CallStatic("presentCustomerCenter"); } - catch { _current.TrySetResult(false); } - return _current.Task; + catch { _current.TrySetResult(false); _current = null; } + return _current?.Task ?? Task.CompletedTask; } // Called from Java via AndroidJavaProxy public void OnCustomerCenterResult(string resultData) { _current?.TrySetResult(true); + _current = null; } private class CallbacksProxy : AndroidJavaProxy diff --git a/RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs b/RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs index c94db836..69e73f5b 100644 --- a/RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs +++ b/RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs @@ -19,6 +19,11 @@ public AndroidPaywallPresenter() try { _plugin.CallStatic("registerPaywallCallbacks", _callbacks); } catch { /* ignore */ } } + ~AndroidPaywallPresenter() + { + try { _plugin.CallStatic("unregisterPaywallCallbacks"); } catch { } + } + public bool IsSupported() { try { return _plugin.CallStatic("isSupported"); } @@ -27,6 +32,12 @@ public bool IsSupported() public Task PresentPaywallAsync(PaywallOptions options) { + if (_current != null && !_current.Task.IsCompleted) + { + Debug.LogWarning("[RevenueCatUI][Android] Paywall presentation already in progress; rejecting new request."); + return Task.FromResult(PaywallResult.Error); + } + _current = new TaskCompletionSource(); try { @@ -38,12 +49,19 @@ public Task PresentPaywallAsync(PaywallOptions options) { Debug.LogError($"[RevenueCatUI][Android] Exception in presentPaywall: {e.Message}"); _current.TrySetResult(PaywallResult.Error); + _current = null; } return _current.Task; } public Task PresentPaywallIfNeededAsync(string requiredEntitlementIdentifier, PaywallOptions options) { + if (_current != null && !_current.Task.IsCompleted) + { + Debug.LogWarning("[RevenueCatUI][Android] Paywall presentation already in progress; rejecting new request."); + return Task.FromResult(PaywallResult.Error); + } + _current = new TaskCompletionSource(); try { @@ -55,6 +73,7 @@ public Task PresentPaywallIfNeededAsync(string requiredEntitlemen { Debug.LogError($"[RevenueCatUI][Android] Exception in presentPaywallIfNeeded: {e.Message}"); _current.TrySetResult(PaywallResult.Error); + _current = null; } return _current.Task; } @@ -74,6 +93,10 @@ public void OnPaywallResult(string resultData) Debug.LogError($"[RevenueCatUI][Android] Failed to handle paywall result '{resultData}': {e.Message}. Setting Error."); _current.TrySetResult(PaywallResult.Error); } + finally + { + _current = null; + } } private class CallbacksProxy : AndroidJavaProxy diff --git a/RevenueCatUI/Scripts/Platforms/iOS/IOSCustomerCenterPresenter.cs b/RevenueCatUI/Scripts/Platforms/iOS/IOSCustomerCenterPresenter.cs index 9ed3f557..020800ba 100644 --- a/RevenueCatUI/Scripts/Platforms/iOS/IOSCustomerCenterPresenter.cs +++ b/RevenueCatUI/Scripts/Platforms/iOS/IOSCustomerCenterPresenter.cs @@ -17,9 +17,25 @@ internal class IOSCustomerCenterPresenter : ICustomerCenterPresenter public Task PresentCustomerCenterAsync() { - s_current = new TaskCompletionSource(); - rcui_presentCustomerCenter(OnDone); - return s_current.Task; + if (s_current != null && !s_current.Task.IsCompleted) + { + UnityEngine.Debug.LogWarning("[RevenueCatUI][iOS] Customer Center already in progress; rejecting new request."); + return Task.CompletedTask; + } + + var tcs = new TaskCompletionSource(); + s_current = tcs; + try + { + rcui_presentCustomerCenter(OnDone); + } + catch (System.Exception e) + { + UnityEngine.Debug.LogError($"[RevenueCatUI][iOS] Exception in presentCustomerCenter: {e.Message}"); + tcs.TrySetResult(false); + s_current = null; + } + return tcs.Task; } [AOT.MonoPInvokeCallback(typeof(CustomerCenterCallback))] diff --git a/RevenueCatUI/Scripts/Platforms/iOS/IOSPaywallPresenter.cs b/RevenueCatUI/Scripts/Platforms/iOS/IOSPaywallPresenter.cs index aa94b152..c2694649 100644 --- a/RevenueCatUI/Scripts/Platforms/iOS/IOSPaywallPresenter.cs +++ b/RevenueCatUI/Scripts/Platforms/iOS/IOSPaywallPresenter.cs @@ -19,6 +19,12 @@ internal class IOSPaywallPresenter : IPaywallPresenter public Task PresentPaywallAsync(PaywallOptions options) { + if (s_current != null && !s_current.Task.IsCompleted) + { + UnityEngine.Debug.LogWarning("[RevenueCatUI][iOS] Paywall presentation already in progress; rejecting new request."); + return Task.FromResult(PaywallResult.Error); + } + var tcs = new TaskCompletionSource(); s_current = tcs; try @@ -29,12 +35,19 @@ public Task PresentPaywallAsync(PaywallOptions options) { UnityEngine.Debug.LogError($"[RevenueCatUI][iOS] Exception in presentPaywall: {e.Message}"); tcs.TrySetResult(PaywallResult.Error); + s_current = null; } return tcs.Task; } public Task PresentPaywallIfNeededAsync(string requiredEntitlementIdentifier, PaywallOptions options) { + if (s_current != null && !s_current.Task.IsCompleted) + { + UnityEngine.Debug.LogWarning("[RevenueCatUI][iOS] Paywall presentation already in progress; rejecting new request."); + return Task.FromResult(PaywallResult.Error); + } + var tcs = new TaskCompletionSource(); s_current = tcs; try @@ -45,6 +58,7 @@ public Task PresentPaywallIfNeededAsync(string requiredEntitlemen { UnityEngine.Debug.LogError($"[RevenueCatUI][iOS] Exception in presentPaywallIfNeeded: {e.Message}"); tcs.TrySetResult(PaywallResult.Error); + s_current = null; } return tcs.Task; } diff --git a/RevenueCatUI/Scripts/RevenueCatUI.cs b/RevenueCatUI/Scripts/RevenueCatUI.cs index bbb982bd..3eba2f12 100644 --- a/RevenueCatUI/Scripts/RevenueCatUI.cs +++ b/RevenueCatUI/Scripts/RevenueCatUI.cs @@ -10,6 +10,7 @@ namespace RevenueCat.UI /// public static class RevenueCatUI { + private static bool? _isSupportedCache; /// /// Presents a paywall configured in the RevenueCat dashboard. /// @@ -90,6 +91,10 @@ public static async Task PresentCustomerCenter() /// True if UI is supported on this platform, otherwise false. public static bool IsSupported() { + if (_isSupportedCache.HasValue) + { + return _isSupportedCache.Value; + } try { var paywallPresenter = PaywallPresenter.Instance; @@ -97,14 +102,37 @@ public static bool IsSupported() var paywall = paywallPresenter.IsSupported(); var customerCenter = customerCenterPresenter.IsSupported(); var supported = paywall && customerCenter; - Debug.Log($"[RevenueCatUI] IsSupported check -> Paywall={paywall}, CustomerCenter={customerCenter}, Result={supported}"); + _isSupportedCache = supported; + if (Debug.isDebugBuild) + { + Debug.Log($"[RevenueCatUI] IsSupported -> Paywall={paywall}, CustomerCenter={customerCenter}, Result={supported}"); + } return supported; } catch { Debug.Log("[RevenueCatUI] IsSupported check threw; returning false"); + _isSupportedCache = false; return false; } } + + /// + /// Whether the Paywall feature is supported on the current platform. + /// + public static bool IsPaywallSupported() + { + try { return PaywallPresenter.Instance.IsSupported(); } + catch { return false; } + } + + /// + /// Whether the Customer Center feature is supported on the current platform. + /// + public static bool IsCustomerCenterSupported() + { + try { return CustomerCenterPresenter.Instance.IsSupported(); } + catch { return false; } + } } } diff --git a/RevenueCatUI/package.json b/RevenueCatUI/package.json index 627dd2e6..022490f8 100644 --- a/RevenueCatUI/package.json +++ b/RevenueCatUI/package.json @@ -27,6 +27,6 @@ "dependencies": { "com.revenuecat.purchases-unity": "file:../RevenueCat" }, - "unity": "2021.3", + "unity": "2022.3", "unityRelease": "0f1" -} \ No newline at end of file +} From 2cbfef6039488faac52df2a59c299103a511826a Mon Sep 17 00:00:00 2001 From: Facundo Menzella Date: Mon, 15 Sep 2025 17:36:43 +0200 Subject: [PATCH 16/65] remove reference to ui package --- Subtester/Assets/Scenes/Main.unity | 84 ++++++++++++++++++- .../Assets/Scripts/RevenueCatUITestButtons.cs | 59 ------------- .../Scripts/RevenueCatUITestButtons.cs.meta | 11 --- Subtester/Packages/manifest.json | 2 +- Subtester/Packages/packages-lock.json | 15 +--- scripts/create-unity-package.sh | 4 +- 6 files changed, 84 insertions(+), 91 deletions(-) delete mode 100644 Subtester/Assets/Scripts/RevenueCatUITestButtons.cs delete mode 100644 Subtester/Assets/Scripts/RevenueCatUITestButtons.cs.meta diff --git a/Subtester/Assets/Scenes/Main.unity b/Subtester/Assets/Scenes/Main.unity index 7dbe4a5d..11fd7cd6 100644 --- a/Subtester/Assets/Scenes/Main.unity +++ b/Subtester/Assets/Scenes/Main.unity @@ -419,7 +419,83 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: useRuntimeSetup: 0 - revenueCatAPIKeyApple: + revenueCatAPIKeyApple: appl_fEVHDkWYraujHYbxopxJknYGNUx + revenueCatAPIKeyGoogle: + revenueCatAPIKeyAmazon: + useAmazon: 0 + autoSyncPurchases: 1 + appUserID: + productIdentifiers: [] + listener: {fileID: 559298755} + userDefaultsSuiteName: + purchasesAreCompletedBy: 0 + storeKitVersion: 2 + shouldShowInAppMessagesAutomatically: 1 + entitlementVerificationMode: 1 + pendingTransactionsForPrepaidPlansEnabled: 1 + proxyURL: +--- !u!4 &559298754 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 559298752} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: -0.53463364, y: 1.2963469, z: -0.32019186} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &559298755 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 559298752} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d4d983d93d67c4367bf31ba35790ced8, type: 3} + m_Name: + m_EditorClassIdentifier: + parentPanel: {fileID: 1575378375} + buttonPrefab: {fileID: 1696602566380582, guid: 08be051e1286142ac9bb1c2a81c63bc3, + type: 3} + infoLabel: {fileID: 53425791} +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 559298754} + - component: {fileID: 559298755} + - component: {fileID: 559298753} + m_Layer: 0 + m_Name: PurchasesManager + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &559298753 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 559298752} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 98e401cc5a2ed4a7ba02f2e6ef35c6f5, type: 3} + m_Name: + m_EditorClassIdentifier: + useRuntimeSetup: 0 + revenueCatAPIKeyApple: appl_fEVHDkWYraujHYbxopxJknYGNUx revenueCatAPIKeyGoogle: revenueCatAPIKeyAmazon: useAmazon: 0 @@ -996,7 +1072,7 @@ MonoBehaviour: m_HandleRect: {fileID: 1016743159} m_Direction: 2 m_Value: 0.90756994 - m_Size: 0.021009987 + m_Size: 0.021009983 m_NumberOfSteps: 0 m_OnValueChanged: m_PersistentCalls: @@ -1390,8 +1466,8 @@ MonoBehaviour: m_TargetGraphic: {fileID: 950935161} m_HandleRect: {fileID: 950935160} m_Direction: 2 - m_Value: 1.0000001 - m_Size: 0.201855 + m_Value: 1 + m_Size: 0.20185497 m_NumberOfSteps: 0 m_OnValueChanged: m_PersistentCalls: diff --git a/Subtester/Assets/Scripts/RevenueCatUITestButtons.cs b/Subtester/Assets/Scripts/RevenueCatUITestButtons.cs deleted file mode 100644 index f05e7404..00000000 --- a/Subtester/Assets/Scripts/RevenueCatUITestButtons.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System.Threading.Tasks; -using UnityEngine; -using UnityEngine.UI; -using RevenueCat.UI; - -public class RevenueCatUITestButtons : MonoBehaviour -{ - [SerializeField] private Text infoLabel; - [SerializeField] private string requiredEntitlementIdentifier = "premium"; - [SerializeField] private string offeringIdentifier = ""; - - public async void ShowPaywall() - { - if (!RevenueCatUI.IsSupported()) - { - LogAndSet("RevenueCatUI not supported on this platform"); - return; - } - - var options = new PaywallOptions { OfferingIdentifier = string.IsNullOrEmpty(offeringIdentifier) ? null : offeringIdentifier }; - LogAndSet("Presenting paywall..."); - var result = await RevenueCatUI.PresentPaywall(options); - LogAndSet($"Paywall result: {result}"); - } - - public async void ShowPaywallIfNeeded() - { - if (!RevenueCatUI.IsSupported()) - { - LogAndSet("RevenueCatUI not supported on this platform"); - return; - } - - var options = new PaywallOptions { OfferingIdentifier = string.IsNullOrEmpty(offeringIdentifier) ? null : offeringIdentifier }; - LogAndSet($"Presenting paywall if needed for '{requiredEntitlementIdentifier}'..."); - var result = await RevenueCatUI.PresentPaywallIfNeeded(requiredEntitlementIdentifier, options); - LogAndSet($"Conditional paywall result: {result}"); - } - - public async void ShowCustomerCenter() - { - if (!RevenueCatUI.IsSupported()) - { - LogAndSet("RevenueCatUI not supported on this platform"); - return; - } - - LogAndSet("Presenting customer center..."); - await RevenueCatUI.PresentCustomerCenter(); - LogAndSet("Customer center dismissed"); - } - - private void LogAndSet(string msg) - { - Debug.Log($"[RevenueCatUI][Test] {msg}"); - if (infoLabel != null) infoLabel.text = msg; - } -} - diff --git a/Subtester/Assets/Scripts/RevenueCatUITestButtons.cs.meta b/Subtester/Assets/Scripts/RevenueCatUITestButtons.cs.meta deleted file mode 100644 index 79be3c8e..00000000 --- a/Subtester/Assets/Scripts/RevenueCatUITestButtons.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: a99f53da2849b4780b145d5d51df8935 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Subtester/Packages/manifest.json b/Subtester/Packages/manifest.json index 876c7fd8..d521a4af 100644 --- a/Subtester/Packages/manifest.json +++ b/Subtester/Packages/manifest.json @@ -1,7 +1,7 @@ { "dependencies": { "com.revenuecat.purchases-unity": "file:../../RevenueCat", - "com.revenuecat.purchases-ui-unity": "file:../../RevenueCatUI", + "com.unity.2d.sprite": "1.0.0", "com.unity.2d.tilemap": "1.0.0", "com.unity.ads": "4.4.2", diff --git a/Subtester/Packages/packages-lock.json b/Subtester/Packages/packages-lock.json index a022dda5..6a005f0f 100644 --- a/Subtester/Packages/packages-lock.json +++ b/Subtester/Packages/packages-lock.json @@ -1,19 +1,6 @@ { "dependencies": { - "com.revenuecat.purchases-ui-unity": { - "version": "file:../../RevenueCatUI", - "depth": 0, - "source": "local", - "dependencies": { - "com.revenuecat.purchases-unity": "file:../RevenueCat" - } - }, - "com.revenuecat.purchases-unity": { - "version": "file:../../RevenueCat", - "depth": 0, - "source": "local", - "dependencies": {} - }, + "com.unity.2d.sprite": { "version": "1.0.0", "depth": 0, diff --git a/scripts/create-unity-package.sh b/scripts/create-unity-package.sh index 024a2086..91007cbc 100755 --- a/scripts/create-unity-package.sh +++ b/scripts/create-unity-package.sh @@ -106,8 +106,8 @@ verbose_echo "Original manifest.json contents:" if [ "$VERBOSE" = true ]; then cat $MANIFEST_JSON_PATH fi -verbose_echo "Removing com.revenuecat.purchases-unity dependency from manifest.json" -awk '!/com.revenuecat.purchases-unity/' $MANIFEST_JSON_PATH > temp && mv temp $MANIFEST_JSON_PATH +verbose_echo "Removing com.revenuecat.purchases-unity and com.revenuecat.purchases-ui-unity dependencies from manifest.json" +awk '!/com.revenuecat.purchases-unity/ && !/com.revenuecat.purchases-ui-unity/' $MANIFEST_JSON_PATH > temp && mv temp $MANIFEST_JSON_PATH verbose_echo "Modified manifest.json contents:" if [ "$VERBOSE" = true ]; then cat $MANIFEST_JSON_PATH From 160ed40d3788e741419eb62cf6d82a28c6d948b5 Mon Sep 17 00:00:00 2001 From: Facundo Menzella Date: Tue, 16 Sep 2025 10:41:14 +0200 Subject: [PATCH 17/65] rename to com.revenuecat.unity.ui --- RevenueCatUI/Plugins/Android/RevenueCatUI.java | 2 +- RevenueCatUI/README.md | 2 +- .../Platforms/Android/AndroidCustomerCenterPresenter.cs | 4 ++-- .../Scripts/Platforms/Android/AndroidPaywallPresenter.cs | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/RevenueCatUI/Plugins/Android/RevenueCatUI.java b/RevenueCatUI/Plugins/Android/RevenueCatUI.java index 39b8472f..f5641fd1 100644 --- a/RevenueCatUI/Plugins/Android/RevenueCatUI.java +++ b/RevenueCatUI/Plugins/Android/RevenueCatUI.java @@ -1,4 +1,4 @@ -package com.revenuecat.unity; +package com.revenuecat.unity.ui; public class RevenueCatUI { diff --git a/RevenueCatUI/README.md b/RevenueCatUI/README.md index 41a07fa7..4ec05ca3 100644 --- a/RevenueCatUI/README.md +++ b/RevenueCatUI/README.md @@ -119,7 +119,7 @@ public enum PaywallResultType ### Native Implementation (reference only) Minimal native code is provided as a reference scaffold: -- Android class: `com.revenuecat.unity.RevenueCatUI` +- Android class: `com.revenuecat.unity.ui.RevenueCatUI` - Uses pure-code callbacks via Java interfaces registered from C# (no GameObject required) - Separate callbacks per feature: `PaywallCallbacks` and `CustomerCenterCallbacks` - iOS functions: `rcui_presentPaywall`, `rcui_presentPaywallIfNeeded`, `rcui_presentCustomerCenter`, `rcui_isSupported` diff --git a/RevenueCatUI/Scripts/Platforms/Android/AndroidCustomerCenterPresenter.cs b/RevenueCatUI/Scripts/Platforms/Android/AndroidCustomerCenterPresenter.cs index 2480e8d0..c69aaaaa 100644 --- a/RevenueCatUI/Scripts/Platforms/Android/AndroidCustomerCenterPresenter.cs +++ b/RevenueCatUI/Scripts/Platforms/Android/AndroidCustomerCenterPresenter.cs @@ -14,7 +14,7 @@ internal class AndroidCustomerCenterPresenter : ICustomerCenterPresenter public AndroidCustomerCenterPresenter() { - _plugin = new AndroidJavaClass("com.revenuecat.unity.RevenueCatUI"); + _plugin = new AndroidJavaClass("com.revenuecat.unity.ui.RevenueCatUI"); _callbacks = new CallbacksProxy(this); try { _plugin.CallStatic("registerCustomerCenterCallbacks", _callbacks); } catch { /* ignore */ } } @@ -54,7 +54,7 @@ public void OnCustomerCenterResult(string resultData) private class CallbacksProxy : AndroidJavaProxy { private readonly AndroidCustomerCenterPresenter _owner; - public CallbacksProxy(AndroidCustomerCenterPresenter owner) : base("com.revenuecat.unity.RevenueCatUI$CustomerCenterCallbacks") + public CallbacksProxy(AndroidCustomerCenterPresenter owner) : base("com.revenuecat.unity.ui.RevenueCatUI$CustomerCenterCallbacks") { _owner = owner; } diff --git a/RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs b/RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs index 69e73f5b..a44f7680 100644 --- a/RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs +++ b/RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs @@ -14,7 +14,7 @@ internal class AndroidPaywallPresenter : IPaywallPresenter public AndroidPaywallPresenter() { - _plugin = new AndroidJavaClass("com.revenuecat.unity.RevenueCatUI"); + _plugin = new AndroidJavaClass("com.revenuecat.unity.ui.RevenueCatUI"); _callbacks = new CallbacksProxy(this); try { _plugin.CallStatic("registerPaywallCallbacks", _callbacks); } catch { /* ignore */ } } @@ -102,7 +102,7 @@ public void OnPaywallResult(string resultData) private class CallbacksProxy : AndroidJavaProxy { private readonly AndroidPaywallPresenter _owner; - public CallbacksProxy(AndroidPaywallPresenter owner) : base("com.revenuecat.unity.RevenueCatUI$PaywallCallbacks") + public CallbacksProxy(AndroidPaywallPresenter owner) : base("com.revenuecat.unity.ui.RevenueCatUI$PaywallCallbacks") { _owner = owner; } From c3bd377f4e99d7a8e1ecbb8ffa123e259f25b508 Mon Sep 17 00:00:00 2001 From: Facundo Menzella Date: Wed, 17 Sep 2025 13:04:19 +0200 Subject: [PATCH 18/65] Update Subtester/Assets/Scenes/Main.unity Co-authored-by: Cesar de la Vega <664544+vegaro@users.noreply.github.com> --- Subtester/Assets/Scenes/Main.unity | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Subtester/Assets/Scenes/Main.unity b/Subtester/Assets/Scenes/Main.unity index 11fd7cd6..86f34c72 100644 --- a/Subtester/Assets/Scenes/Main.unity +++ b/Subtester/Assets/Scenes/Main.unity @@ -419,7 +419,7 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: useRuntimeSetup: 0 - revenueCatAPIKeyApple: appl_fEVHDkWYraujHYbxopxJknYGNUx + revenueCatAPIKeyApple: revenueCatAPIKeyGoogle: revenueCatAPIKeyAmazon: useAmazon: 0 From 0ad7ba1c7bf38d57e5679888a80b6ba8401331a2 Mon Sep 17 00:00:00 2001 From: Facundo Menzella Date: Wed, 17 Sep 2025 13:10:59 +0200 Subject: [PATCH 19/65] Remove extra Stub.meta --- RevenueCatUI/Scripts/Platforms/Stub.meta | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 RevenueCatUI/Scripts/Platforms/Stub.meta diff --git a/RevenueCatUI/Scripts/Platforms/Stub.meta b/RevenueCatUI/Scripts/Platforms/Stub.meta deleted file mode 100644 index e7e94354..00000000 --- a/RevenueCatUI/Scripts/Platforms/Stub.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: b19156d7290a3412ca61decc091e1a1d -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: From a8591ee269f5c786ba3f7262080154b08182798c Mon Sep 17 00:00:00 2001 From: Facundo Menzella Date: Wed, 17 Sep 2025 13:11:41 +0200 Subject: [PATCH 20/65] Remove README --- RevenueCatUI/README.md | 164 ----------------------------------------- 1 file changed, 164 deletions(-) delete mode 100644 RevenueCatUI/README.md diff --git a/RevenueCatUI/README.md b/RevenueCatUI/README.md deleted file mode 100644 index 4ec05ca3..00000000 --- a/RevenueCatUI/README.md +++ /dev/null @@ -1,164 +0,0 @@ -# RevenueCat UI for Unity (Stub Package) - -This package provides the Unity API surface for RevenueCat UI (paywalls and customer center), -implemented with lightweight stubs so you can wire your flows without native dependencies. - -Use this to integrate and test control flow on devices. Device builds route -through minimal native stubs; in the Unity Editor the UI is reported as -unsupported by default (no UI shown). When ready, replace the minimal native -code with your real implementations. - -## Installation - -Add to your Unity project's `Packages/manifest.json`: - -``` -{ - "dependencies": { - "com.revenuecat.purchases-unity": "file:../RevenueCat", // existing core SDK - "com.revenuecat.purchases-ui-unity": "file:../RevenueCatUI" // this UI package (stub) - } -} -``` - -### Requirements - -- Unity 2022.3 LTS or later (package.json enforces 2022.3) -- RevenueCat Unity SDK (this package depends on the main RevenueCat package) - -### Dependencies - -By default this package runs in stub mode. On iOS/Android device builds, the -API routes through minimal native stubs that immediately return results (no -external dependencies). In the Editor and other platforms, the API reports -unsupported and no UI is presented. When you’re ready for real UI, replace the -minimal native code with your implementation and add dependencies as needed. - -## Quick Start - -```csharp -using RevenueCat.UI; - -public class MySubscriptionManager : MonoBehaviour -{ - public async void ShowPaywall() - { - if (!RevenueCatUI.IsSupported()) - { - Debug.LogWarning("RevenueCat UI not supported on this platform"); - return; - } - - try - { - var result = await RevenueCatUI.PresentPaywall(); - - switch (result.Result) - { - case PaywallResultType.Purchased: - Debug.Log("User made a purchase!"); - break; - case PaywallResultType.Cancelled: - Debug.Log("User cancelled"); - break; - } - } - catch (System.Exception e) - { - Debug.LogError($"Paywall error: {e.Message}"); - } - } - - public async void ShowCustomerCenter() - { - if (RevenueCatUI.IsSupported()) - { - await RevenueCatUI.PresentCustomerCenter(); - } - } -} -``` - -## API Reference - -### RevenueCatUI (Main API) - -Implemented now (stubbed behavior everywhere): -- `PresentPaywall()` — returns `Cancelled` immediately -- `PresentPaywall(PaywallOptions)` — same as above with options -- `PresentPaywallIfNeeded(string[, PaywallOptions])` — returns `NotPresented` immediately -- `PresentCustomerCenter()` — completes immediately -- `IsSupported()` — returns `true` on iOS/Android device builds when both Paywall and Customer Center are available; returns `false` on other platforms (Editor, Windows, macOS, WebGL, etc.) -- `IsPaywallSupported()` — per-feature capability -- `IsCustomerCenterSupported()` — per-feature capability - -### PaywallOptions - -```csharp -var options = new PaywallOptions -{ - OfferingIdentifier = "premium", // Optional: specific offering - DisplayCloseButton = true // Show close button (iOS only) -}; -``` - -### PaywallResult - -```csharp -public enum PaywallResultType -{ - NotPresented, // User already has entitlement - Cancelled, // User cancelled - Error, // An error occurred - Purchased, // User made a purchase - Restored // User restored purchases -} -``` - -## Architecture - -### Native Implementation (reference only) -Minimal native code is provided as a reference scaffold: -- Android class: `com.revenuecat.unity.ui.RevenueCatUI` - - Uses pure-code callbacks via Java interfaces registered from C# (no GameObject required) - - Separate callbacks per feature: `PaywallCallbacks` and `CustomerCenterCallbacks` -- iOS functions: `rcui_presentPaywall`, `rcui_presentPaywallIfNeeded`, `rcui_presentCustomerCenter`, `rcui_isSupported` - - Pure-code C callbacks per feature: paywall functions return results via a paywall callback; customer center uses its own callback. No GameObject required. - -If/when you add real native UI, update the platform presenters in -`RevenueCatUI/Scripts/Platforms/{Android,iOS}` to select your implementations. - -### Platform Abstraction -- Factory pattern with `IPaywallPresenter` and `ICustomerCenterPresenter` -- Graceful fallbacks for unsupported platforms -- Unity-style async/await throughout - -### Threading and Logging -- Callbacks may complete on any thread. If you need to interact with Unity APIs, - dispatch back to the main thread (e.g., via a main-thread dispatcher). -- Logs are verbose in Development builds. Production builds minimize logs; gate - additional logging behind your own flags where needed. - -## Platform Support - -- iOS/Android device: Minimal native stubs return immediate results (no UI) -- Editor / Other platforms: Reported unsupported (no UI) - -## Examples - -Examples are not included in this stub package. Use the Quick Start snippet above -or your own scene code to exercise the API. - -## Troubleshooting (stub mode) - -- Call `RevenueCatUI.IsSupported()` before presenting UI -- Initialize the core RevenueCat SDK before using UI components -- Check Unity console for logs tagged `[RevenueCatUI]` - -## Modes - -Single mode: stub behavior on all platforms (native stubs on devices, C# fallbacks in Editor). - -## License - -This package follows the same license as the main RevenueCat Unity SDK. From ffca566db2d6867f64392666629ccf0f1c3448fd Mon Sep 17 00:00:00 2001 From: Facundo Menzella Date: Wed, 17 Sep 2025 13:14:03 +0200 Subject: [PATCH 21/65] Rename .asmdef files --- .../Scripts/{RevenueCat.UI.asmdef => RevenueCatUI.asmdef} | 2 +- .../{RevenueCat.UI.asmdef.meta => RevenueCatUI.asmdef.meta} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename RevenueCatUI/Scripts/{RevenueCat.UI.asmdef => RevenueCatUI.asmdef} (93%) rename RevenueCatUI/Scripts/{RevenueCat.UI.asmdef.meta => RevenueCatUI.asmdef.meta} (100%) diff --git a/RevenueCatUI/Scripts/RevenueCat.UI.asmdef b/RevenueCatUI/Scripts/RevenueCatUI.asmdef similarity index 93% rename from RevenueCatUI/Scripts/RevenueCat.UI.asmdef rename to RevenueCatUI/Scripts/RevenueCatUI.asmdef index 7cb67b3d..103e8cce 100644 --- a/RevenueCatUI/Scripts/RevenueCat.UI.asmdef +++ b/RevenueCatUI/Scripts/RevenueCatUI.asmdef @@ -1,5 +1,5 @@ { - "name": "RevenueCat.UI", + "name": "RevenueCatUI", "rootNamespace": "RevenueCat.UI", "references": [ "revenuecat.purchases-unity" diff --git a/RevenueCatUI/Scripts/RevenueCat.UI.asmdef.meta b/RevenueCatUI/Scripts/RevenueCatUI.asmdef.meta similarity index 100% rename from RevenueCatUI/Scripts/RevenueCat.UI.asmdef.meta rename to RevenueCatUI/Scripts/RevenueCatUI.asmdef.meta From 5417e16346dd8b2cbb4d80a40855f86385997153 Mon Sep 17 00:00:00 2001 From: Facundo Menzella Date: Wed, 17 Sep 2025 13:18:34 +0200 Subject: [PATCH 22/65] Remove customer center code --- .../Plugins/Android/RevenueCatUI.java | 16 +-- RevenueCatUI/Plugins/iOS/RevenueCatUI.m | 8 +- RevenueCatUI/README.md | 109 ++++++++++++++++++ .../Scripts/CustomerCenterPresenter.cs | 56 --------- .../Scripts/CustomerCenterPresenter.cs.meta | 12 -- .../Scripts/ICustomerCenterPresenter.cs | 25 ---- .../Scripts/ICustomerCenterPresenter.cs.meta | 12 -- .../Android/AndroidCustomerCenterPresenter.cs | 69 ----------- .../AndroidCustomerCenterPresenter.cs.meta | 3 - .../iOS/IOSCustomerCenterPresenter.cs | 49 -------- .../iOS/IOSCustomerCenterPresenter.cs.meta | 3 - RevenueCatUI/Scripts/RevenueCatUI.cs | 44 ++----- 12 files changed, 120 insertions(+), 286 deletions(-) create mode 100644 RevenueCatUI/README.md delete mode 100644 RevenueCatUI/Scripts/CustomerCenterPresenter.cs delete mode 100644 RevenueCatUI/Scripts/CustomerCenterPresenter.cs.meta delete mode 100644 RevenueCatUI/Scripts/ICustomerCenterPresenter.cs delete mode 100644 RevenueCatUI/Scripts/ICustomerCenterPresenter.cs.meta delete mode 100644 RevenueCatUI/Scripts/Platforms/Android/AndroidCustomerCenterPresenter.cs delete mode 100644 RevenueCatUI/Scripts/Platforms/Android/AndroidCustomerCenterPresenter.cs.meta delete mode 100644 RevenueCatUI/Scripts/Platforms/iOS/IOSCustomerCenterPresenter.cs delete mode 100644 RevenueCatUI/Scripts/Platforms/iOS/IOSCustomerCenterPresenter.cs.meta diff --git a/RevenueCatUI/Plugins/Android/RevenueCatUI.java b/RevenueCatUI/Plugins/Android/RevenueCatUI.java index f5641fd1..177de996 100644 --- a/RevenueCatUI/Plugins/Android/RevenueCatUI.java +++ b/RevenueCatUI/Plugins/Android/RevenueCatUI.java @@ -4,16 +4,12 @@ public class RevenueCatUI { // Java-side callbacks registered from C#; no UnitySendMessage fallback. public interface PaywallCallbacks { void onPaywallResult(String result); } - public interface CustomerCenterCallbacks { void onCustomerCenterResult(String result); } private static volatile PaywallCallbacks paywallCallbacks; - private static volatile CustomerCenterCallbacks customerCenterCallbacks; public static void registerPaywallCallbacks(PaywallCallbacks cb) { paywallCallbacks = cb; } public static void unregisterPaywallCallbacks() { paywallCallbacks = null; } - public static void registerCustomerCenterCallbacks(CustomerCenterCallbacks cb) { customerCenterCallbacks = cb; } - public static void unregisterCustomerCenterCallbacks() { customerCenterCallbacks = null; } public static void presentPaywall(String offeringIdentifier) { android.util.Log.d("RevenueCatUI", "presentPaywall(offering=" + offeringIdentifier + ")"); @@ -25,10 +21,7 @@ public static void presentPaywallIfNeeded(String requiredEntitlementIdentifier, sendPaywallResult("NOT_PRESENTED|Stub: no native UI"); } - public static void presentCustomerCenter() { - android.util.Log.d("RevenueCatUI", "presentCustomerCenter()"); - sendCustomerCenterResult("DONE|Stub: no native UI"); - } + // No Customer Center in this stub public static boolean isSupported() { android.util.Log.d("RevenueCatUI", "isSupported() -> true (stub)"); @@ -42,10 +35,5 @@ private static void sendPaywallResult(String result) { } catch (Throwable ignored) {} } - private static void sendCustomerCenterResult(String result) { - try { - CustomerCenterCallbacks cb = customerCenterCallbacks; - if (cb != null) cb.onCustomerCenterResult(result); - } catch (Throwable ignored) {} - } + // No Customer Center in this stub } diff --git a/RevenueCatUI/Plugins/iOS/RevenueCatUI.m b/RevenueCatUI/Plugins/iOS/RevenueCatUI.m index d3100f48..5827e657 100644 --- a/RevenueCatUI/Plugins/iOS/RevenueCatUI.m +++ b/RevenueCatUI/Plugins/iOS/RevenueCatUI.m @@ -3,7 +3,6 @@ // Minimal native stubs for iOS bridging typedef void (*RCUIPaywallResultCallback)(const char* result); -typedef void (*RCUICustomerCenterCallback)(void); void rcui_presentPaywall(const char* offeringIdentifier, bool displayCloseButton, RCUIPaywallResultCallback callback) { NSLog(@"[RevenueCatUI][iOS] presentPaywall(offering=%@, closeButton=%@)", @@ -24,12 +23,7 @@ void rcui_presentPaywallIfNeeded(const char* requiredEntitlementIdentifier, cons } } -void rcui_presentCustomerCenter(RCUICustomerCenterCallback callback) { - NSLog(@"[RevenueCatUI][iOS] presentCustomerCenter()"); - if (callback) { - callback(); - } -} +// No Customer Center in this stub bool rcui_isSupported() { NSLog(@"[RevenueCatUI][iOS] isSupported() -> true (stub)"); diff --git a/RevenueCatUI/README.md b/RevenueCatUI/README.md new file mode 100644 index 00000000..77ae900c --- /dev/null +++ b/RevenueCatUI/README.md @@ -0,0 +1,109 @@ +# RevenueCat UI for Unity (Stub Package) + +This package provides the Unity API surface for RevenueCat UI (paywalls), +implemented with lightweight stubs so you can wire flows without native dependencies. + +Use this to integrate and test control flow on devices. Device builds route +through minimal native stubs; in the Unity Editor the UI is reported as +unsupported (no UI shown). When ready, replace the minimal native code with your +real implementations. + +## Installation + +Add to your Unity project's `Packages/manifest.json` during development only: + +``` +{ + "dependencies": { + "com.revenuecat.purchases-unity": "file:../RevenueCat", + "com.revenuecat.purchases-ui-unity": "file:../RevenueCatUI" + } +} +``` + +Do not include this dependency in distribution packages. + +### Requirements + +- Unity 2022.3 LTS or later (package.json enforces 2022.3) +- RevenueCat Unity SDK (this package depends on the main RevenueCat package) + +## Quick Start + +```csharp +using RevenueCat.UI; + +public class MySubscriptionManager : MonoBehaviour +{ + public async void ShowPaywall() + { + if (!RevenueCatUI.IsSupported()) + return; + + var result = await RevenueCatUI.PresentPaywall(); + Debug.Log($"Paywall result: {result.Result}"); + } +} +``` + +## API Reference (stubbed) + +- `PresentPaywall()` — returns `Cancelled` immediately +- `PresentPaywall(PaywallOptions)` — same as above with options +- `PresentPaywallIfNeeded(string[, PaywallOptions])` — returns `NotPresented` immediately +- `IsSupported()` — returns `true` on iOS/Android device builds when Paywall is available; returns `false` on other platforms + +### PaywallOptions + +```csharp +var options = new PaywallOptions +{ + OfferingIdentifier = "premium", + DisplayCloseButton = true // iOS only (ignored by V2 paywalls) +}; +``` + +### PaywallResult + +```csharp +public enum PaywallResultType +{ + NotPresented, + Cancelled, + Error, + Purchased, + Restored +} +``` + +## Architecture + +### Native Implementation (reference only) +- Android class: `com.revenuecat.unity.ui.RevenueCatUI` + - Uses pure-code callbacks via a Java interface registered from C# (no GameObject required) + - Callback interface: `PaywallCallbacks` +- iOS functions: `rcui_presentPaywall`, `rcui_presentPaywallIfNeeded`, `rcui_isSupported` + - Pure-code C callbacks for paywall results. No GameObject required. + +If/when you add real native UI, update the platform presenters in +`RevenueCatUI/Scripts/Platforms/{Android,iOS}` to select your implementations. + +### Platform Abstraction +- Factory pattern with `IPaywallPresenter` +- Graceful fallbacks for unsupported platforms +- Unity-style async/await throughout + +### Threading and Logging +- Callbacks may complete on any thread. If you need to interact with Unity APIs, + dispatch back to the main thread. +- Logs are verbose in Development builds; production builds minimize logs. + +## Platform Support + +- iOS/Android device: Minimal native stubs return immediate results (no UI) +- Editor / Other platforms: Reported unsupported (no UI) + +## License + +This package follows the same license as the main RevenueCat Unity SDK. + diff --git a/RevenueCatUI/Scripts/CustomerCenterPresenter.cs b/RevenueCatUI/Scripts/CustomerCenterPresenter.cs deleted file mode 100644 index 10ccf255..00000000 --- a/RevenueCatUI/Scripts/CustomerCenterPresenter.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System.Threading.Tasks; -using UnityEngine; - -namespace RevenueCat.UI -{ - /// - /// Platform-agnostic factory for customer center presenters. - /// - internal static class CustomerCenterPresenter - { - private static ICustomerCenterPresenter _instance; - - /// - /// Gets the platform-specific customer center presenter instance. - /// - internal static ICustomerCenterPresenter Instance - { - get - { - if (_instance == null) - { - _instance = CreatePlatformPresenter(); - } - return _instance; - } - } - - private static ICustomerCenterPresenter CreatePlatformPresenter() - { -#if UNITY_IOS && !UNITY_EDITOR - return new Platforms.IOSCustomerCenterPresenter(); -#elif UNITY_ANDROID && !UNITY_EDITOR - return new Platforms.AndroidCustomerCenterPresenter(); -#else - return new UnsupportedCustomerCenterPresenter(); -#endif - } - } - - /// - /// Fallback presenter for unsupported platforms. - /// - internal class UnsupportedCustomerCenterPresenter : ICustomerCenterPresenter - { - public Task PresentCustomerCenterAsync() - { - Debug.LogWarning("[RevenueCatUI] Customer center presentation is not supported on this platform."); - return Task.CompletedTask; - } - - public bool IsSupported() - { - return false; - } - } -} diff --git a/RevenueCatUI/Scripts/CustomerCenterPresenter.cs.meta b/RevenueCatUI/Scripts/CustomerCenterPresenter.cs.meta deleted file mode 100644 index 21828aed..00000000 --- a/RevenueCatUI/Scripts/CustomerCenterPresenter.cs.meta +++ /dev/null @@ -1,12 +0,0 @@ -fileFormatVersion: 2 -guid: c96ee6318b1394296b8079caa65e3378 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: - diff --git a/RevenueCatUI/Scripts/ICustomerCenterPresenter.cs b/RevenueCatUI/Scripts/ICustomerCenterPresenter.cs deleted file mode 100644 index 549023bc..00000000 --- a/RevenueCatUI/Scripts/ICustomerCenterPresenter.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Threading.Tasks; - -namespace RevenueCat.UI -{ - /// - /// Internal interface for presenting customer center. - /// Implemented by platform-specific presenters. - /// - internal interface ICustomerCenterPresenter - { - /// - /// Presents the customer center. - /// - /// Task that completes when the customer center is dismissed - Task PresentCustomerCenterAsync(); - - /// - /// Whether the customer center is supported on this platform. - /// Returns true on iOS/Android device builds; false on other platforms - /// (Editor, Windows, macOS, WebGL, etc.). - /// - /// True if supported on the current platform, otherwise false. - bool IsSupported(); - } -} diff --git a/RevenueCatUI/Scripts/ICustomerCenterPresenter.cs.meta b/RevenueCatUI/Scripts/ICustomerCenterPresenter.cs.meta deleted file mode 100644 index e0c30634..00000000 --- a/RevenueCatUI/Scripts/ICustomerCenterPresenter.cs.meta +++ /dev/null @@ -1,12 +0,0 @@ -fileFormatVersion: 2 -guid: f0b6f8331246c4aeb9be069c6c780b10 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: - diff --git a/RevenueCatUI/Scripts/Platforms/Android/AndroidCustomerCenterPresenter.cs b/RevenueCatUI/Scripts/Platforms/Android/AndroidCustomerCenterPresenter.cs deleted file mode 100644 index c69aaaaa..00000000 --- a/RevenueCatUI/Scripts/Platforms/Android/AndroidCustomerCenterPresenter.cs +++ /dev/null @@ -1,69 +0,0 @@ -#if UNITY_ANDROID && !UNITY_EDITOR -using System; -using System.Threading.Tasks; -using UnityEngine; -// No Internal handler needed; Android uses direct callbacks via AndroidJavaProxy - -namespace RevenueCat.UI.Platforms -{ - internal class AndroidCustomerCenterPresenter : ICustomerCenterPresenter - { - private readonly AndroidJavaClass _plugin; - private readonly CallbacksProxy _callbacks; - private TaskCompletionSource _current; - - public AndroidCustomerCenterPresenter() - { - _plugin = new AndroidJavaClass("com.revenuecat.unity.ui.RevenueCatUI"); - _callbacks = new CallbacksProxy(this); - try { _plugin.CallStatic("registerCustomerCenterCallbacks", _callbacks); } catch { /* ignore */ } - } - - ~AndroidCustomerCenterPresenter() - { - try { _plugin.CallStatic("unregisterCustomerCenterCallbacks"); } catch { } - } - - public bool IsSupported() - { - try { return _plugin.CallStatic("isSupported"); } - catch { return false; } - } - - public Task PresentCustomerCenterAsync() - { - if (_current != null && !_current.Task.IsCompleted) - { - Debug.LogWarning("[RevenueCatUI][Android] Customer Center already in progress; rejecting new request."); - return Task.CompletedTask; - } - - _current = new TaskCompletionSource(); - try { _plugin.CallStatic("presentCustomerCenter"); } - catch { _current.TrySetResult(false); _current = null; } - return _current?.Task ?? Task.CompletedTask; - } - - // Called from Java via AndroidJavaProxy - public void OnCustomerCenterResult(string resultData) - { - _current?.TrySetResult(true); - _current = null; - } - - private class CallbacksProxy : AndroidJavaProxy - { - private readonly AndroidCustomerCenterPresenter _owner; - public CallbacksProxy(AndroidCustomerCenterPresenter owner) : base("com.revenuecat.unity.ui.RevenueCatUI$CustomerCenterCallbacks") - { - _owner = owner; - } - - public void onCustomerCenterResult(string result) - { - _owner.OnCustomerCenterResult(result); - } - } - } -} -#endif diff --git a/RevenueCatUI/Scripts/Platforms/Android/AndroidCustomerCenterPresenter.cs.meta b/RevenueCatUI/Scripts/Platforms/Android/AndroidCustomerCenterPresenter.cs.meta deleted file mode 100644 index daaf85c4..00000000 --- a/RevenueCatUI/Scripts/Platforms/Android/AndroidCustomerCenterPresenter.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 9d6151bb80d874557bc6c71b90bc470c - diff --git a/RevenueCatUI/Scripts/Platforms/iOS/IOSCustomerCenterPresenter.cs b/RevenueCatUI/Scripts/Platforms/iOS/IOSCustomerCenterPresenter.cs deleted file mode 100644 index 020800ba..00000000 --- a/RevenueCatUI/Scripts/Platforms/iOS/IOSCustomerCenterPresenter.cs +++ /dev/null @@ -1,49 +0,0 @@ -#if UNITY_IOS && !UNITY_EDITOR -using System.Runtime.InteropServices; -using System.Threading.Tasks; - -namespace RevenueCat.UI.Platforms -{ - internal class IOSCustomerCenterPresenter : ICustomerCenterPresenter - { - private delegate void CustomerCenterCallback(); - - [DllImport("__Internal")] private static extern void rcui_presentCustomerCenter(CustomerCenterCallback cb); - [DllImport("__Internal")] private static extern bool rcui_isSupported(); - - private static TaskCompletionSource s_current; - - public bool IsSupported() => rcui_isSupported(); - - public Task PresentCustomerCenterAsync() - { - if (s_current != null && !s_current.Task.IsCompleted) - { - UnityEngine.Debug.LogWarning("[RevenueCatUI][iOS] Customer Center already in progress; rejecting new request."); - return Task.CompletedTask; - } - - var tcs = new TaskCompletionSource(); - s_current = tcs; - try - { - rcui_presentCustomerCenter(OnDone); - } - catch (System.Exception e) - { - UnityEngine.Debug.LogError($"[RevenueCatUI][iOS] Exception in presentCustomerCenter: {e.Message}"); - tcs.TrySetResult(false); - s_current = null; - } - return tcs.Task; - } - - [AOT.MonoPInvokeCallback(typeof(CustomerCenterCallback))] - private static void OnDone() - { - s_current?.TrySetResult(true); - s_current = null; - } - } -} -#endif diff --git a/RevenueCatUI/Scripts/Platforms/iOS/IOSCustomerCenterPresenter.cs.meta b/RevenueCatUI/Scripts/Platforms/iOS/IOSCustomerCenterPresenter.cs.meta deleted file mode 100644 index 080f6509..00000000 --- a/RevenueCatUI/Scripts/Platforms/iOS/IOSCustomerCenterPresenter.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 3c25347f16a994257ae49e1048cf650d - diff --git a/RevenueCatUI/Scripts/RevenueCatUI.cs b/RevenueCatUI/Scripts/RevenueCatUI.cs index 3eba2f12..267ef839 100644 --- a/RevenueCatUI/Scripts/RevenueCatUI.cs +++ b/RevenueCatUI/Scripts/RevenueCatUI.cs @@ -63,30 +63,12 @@ public static async Task PresentPaywallIfNeeded( } } - /// - /// Presents the customer center where users can manage their subscriptions. - /// - /// A task that completes when the customer center is dismissed - public static async Task PresentCustomerCenter() - { - try - { - Debug.Log("[RevenueCatUI] Presenting customer center..."); - - var presenter = CustomerCenterPresenter.Instance; - await presenter.PresentCustomerCenterAsync(); - } - catch (Exception e) - { - Debug.LogError($"[RevenueCatUI] Error presenting customer center: {e.Message}"); - } - } + /// - /// Checks if RevenueCat UI is available on the current platform. - /// Returns true on iOS/Android device builds when both paywall and - /// customer center are supported; returns false on other platforms - /// (Editor, Windows, macOS, WebGL, etc.). + /// Checks if the Paywall UI is available on the current platform. + /// Returns true on iOS/Android device builds when paywall is supported; + /// returns false on other platforms (Editor, Windows, macOS, WebGL, etc.). /// /// True if UI is supported on this platform, otherwise false. public static bool IsSupported() @@ -98,16 +80,13 @@ public static bool IsSupported() try { var paywallPresenter = PaywallPresenter.Instance; - var customerCenterPresenter = CustomerCenterPresenter.Instance; var paywall = paywallPresenter.IsSupported(); - var customerCenter = customerCenterPresenter.IsSupported(); - var supported = paywall && customerCenter; - _isSupportedCache = supported; + _isSupportedCache = paywall; if (Debug.isDebugBuild) { - Debug.Log($"[RevenueCatUI] IsSupported -> Paywall={paywall}, CustomerCenter={customerCenter}, Result={supported}"); + Debug.Log($"[RevenueCatUI] IsSupported -> Paywall={paywall}"); } - return supported; + return paywall; } catch { @@ -126,13 +105,6 @@ public static bool IsPaywallSupported() catch { return false; } } - /// - /// Whether the Customer Center feature is supported on the current platform. - /// - public static bool IsCustomerCenterSupported() - { - try { return CustomerCenterPresenter.Instance.IsSupported(); } - catch { return false; } - } + } } From 6a43d860a71ce6733beaea50162fc5cf1029bad9 Mon Sep 17 00:00:00 2001 From: Facundo Menzella Date: Wed, 17 Sep 2025 13:26:44 +0200 Subject: [PATCH 23/65] remove _isSupportedCache --- RevenueCatUI/Scripts/RevenueCatUI.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/RevenueCatUI/Scripts/RevenueCatUI.cs b/RevenueCatUI/Scripts/RevenueCatUI.cs index 267ef839..730220e6 100644 --- a/RevenueCatUI/Scripts/RevenueCatUI.cs +++ b/RevenueCatUI/Scripts/RevenueCatUI.cs @@ -6,11 +6,11 @@ namespace RevenueCat.UI { /// /// Main interface for RevenueCat UI components. - /// Provides methods to present paywalls and customer center. + /// Provides methods to present paywalls. /// public static class RevenueCatUI { - private static bool? _isSupportedCache; + /// /// Presents a paywall configured in the RevenueCat dashboard. /// @@ -73,15 +73,10 @@ public static async Task PresentPaywallIfNeeded( /// True if UI is supported on this platform, otherwise false. public static bool IsSupported() { - if (_isSupportedCache.HasValue) - { - return _isSupportedCache.Value; - } try { var paywallPresenter = PaywallPresenter.Instance; var paywall = paywallPresenter.IsSupported(); - _isSupportedCache = paywall; if (Debug.isDebugBuild) { Debug.Log($"[RevenueCatUI] IsSupported -> Paywall={paywall}"); @@ -91,7 +86,6 @@ public static bool IsSupported() catch { Debug.Log("[RevenueCatUI] IsSupported check threw; returning false"); - _isSupportedCache = false; return false; } } From 0a829d0318838ccd6b490aba44b683a4771a7a1d Mon Sep 17 00:00:00 2001 From: Facundo Menzella Date: Wed, 17 Sep 2025 13:30:01 +0200 Subject: [PATCH 24/65] Update Subtester/Packages/manifest.json --- Subtester/Packages/manifest.json | 1 - 1 file changed, 1 deletion(-) diff --git a/Subtester/Packages/manifest.json b/Subtester/Packages/manifest.json index d521a4af..ca00c4ea 100644 --- a/Subtester/Packages/manifest.json +++ b/Subtester/Packages/manifest.json @@ -1,7 +1,6 @@ { "dependencies": { "com.revenuecat.purchases-unity": "file:../../RevenueCat", - "com.unity.2d.sprite": "1.0.0", "com.unity.2d.tilemap": "1.0.0", "com.unity.ads": "4.4.2", From 18b9ba14a1f1bf9fc7f3ecf011fa1347c48b0e17 Mon Sep 17 00:00:00 2001 From: Facundo Menzella Date: Wed, 17 Sep 2025 13:30:19 +0200 Subject: [PATCH 25/65] renamed editor asmdef and deleted extra files --- ...itor.asmdef => RevenueCatUI.Editor.asmdef} | 6 +- .../Editor/RevenueCatUI.Editor.asmdef.meta | 8 ++ RevenueCatUI/README.md | 109 ------------------ RevenueCatUI/package.json | 2 +- 4 files changed, 13 insertions(+), 112 deletions(-) rename RevenueCatUI/Editor/{RevenueCat.UI.Editor.asmdef => RevenueCatUI.Editor.asmdef} (79%) create mode 100644 RevenueCatUI/Editor/RevenueCatUI.Editor.asmdef.meta delete mode 100644 RevenueCatUI/README.md diff --git a/RevenueCatUI/Editor/RevenueCat.UI.Editor.asmdef b/RevenueCatUI/Editor/RevenueCatUI.Editor.asmdef similarity index 79% rename from RevenueCatUI/Editor/RevenueCat.UI.Editor.asmdef rename to RevenueCatUI/Editor/RevenueCatUI.Editor.asmdef index 021e01c2..ccb71242 100644 --- a/RevenueCatUI/Editor/RevenueCat.UI.Editor.asmdef +++ b/RevenueCatUI/Editor/RevenueCatUI.Editor.asmdef @@ -1,7 +1,9 @@ { - "name": "RevenueCat.UI.Editor", + "name": "RevenueCatUI.Editor", "rootNamespace": "RevenueCat.UI", - "references": [], + "references": [ + "RevenueCatUI" + ], "includePlatforms": [ "Editor" ], diff --git a/RevenueCatUI/Editor/RevenueCatUI.Editor.asmdef.meta b/RevenueCatUI/Editor/RevenueCatUI.Editor.asmdef.meta new file mode 100644 index 00000000..31499d52 --- /dev/null +++ b/RevenueCatUI/Editor/RevenueCatUI.Editor.asmdef.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e6c6f27f8e4c4f6e9e0d8a9a8f1b2c3d +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: + diff --git a/RevenueCatUI/README.md b/RevenueCatUI/README.md deleted file mode 100644 index 77ae900c..00000000 --- a/RevenueCatUI/README.md +++ /dev/null @@ -1,109 +0,0 @@ -# RevenueCat UI for Unity (Stub Package) - -This package provides the Unity API surface for RevenueCat UI (paywalls), -implemented with lightweight stubs so you can wire flows without native dependencies. - -Use this to integrate and test control flow on devices. Device builds route -through minimal native stubs; in the Unity Editor the UI is reported as -unsupported (no UI shown). When ready, replace the minimal native code with your -real implementations. - -## Installation - -Add to your Unity project's `Packages/manifest.json` during development only: - -``` -{ - "dependencies": { - "com.revenuecat.purchases-unity": "file:../RevenueCat", - "com.revenuecat.purchases-ui-unity": "file:../RevenueCatUI" - } -} -``` - -Do not include this dependency in distribution packages. - -### Requirements - -- Unity 2022.3 LTS or later (package.json enforces 2022.3) -- RevenueCat Unity SDK (this package depends on the main RevenueCat package) - -## Quick Start - -```csharp -using RevenueCat.UI; - -public class MySubscriptionManager : MonoBehaviour -{ - public async void ShowPaywall() - { - if (!RevenueCatUI.IsSupported()) - return; - - var result = await RevenueCatUI.PresentPaywall(); - Debug.Log($"Paywall result: {result.Result}"); - } -} -``` - -## API Reference (stubbed) - -- `PresentPaywall()` — returns `Cancelled` immediately -- `PresentPaywall(PaywallOptions)` — same as above with options -- `PresentPaywallIfNeeded(string[, PaywallOptions])` — returns `NotPresented` immediately -- `IsSupported()` — returns `true` on iOS/Android device builds when Paywall is available; returns `false` on other platforms - -### PaywallOptions - -```csharp -var options = new PaywallOptions -{ - OfferingIdentifier = "premium", - DisplayCloseButton = true // iOS only (ignored by V2 paywalls) -}; -``` - -### PaywallResult - -```csharp -public enum PaywallResultType -{ - NotPresented, - Cancelled, - Error, - Purchased, - Restored -} -``` - -## Architecture - -### Native Implementation (reference only) -- Android class: `com.revenuecat.unity.ui.RevenueCatUI` - - Uses pure-code callbacks via a Java interface registered from C# (no GameObject required) - - Callback interface: `PaywallCallbacks` -- iOS functions: `rcui_presentPaywall`, `rcui_presentPaywallIfNeeded`, `rcui_isSupported` - - Pure-code C callbacks for paywall results. No GameObject required. - -If/when you add real native UI, update the platform presenters in -`RevenueCatUI/Scripts/Platforms/{Android,iOS}` to select your implementations. - -### Platform Abstraction -- Factory pattern with `IPaywallPresenter` -- Graceful fallbacks for unsupported platforms -- Unity-style async/await throughout - -### Threading and Logging -- Callbacks may complete on any thread. If you need to interact with Unity APIs, - dispatch back to the main thread. -- Logs are verbose in Development builds; production builds minimize logs. - -## Platform Support - -- iOS/Android device: Minimal native stubs return immediate results (no UI) -- Editor / Other platforms: Reported unsupported (no UI) - -## License - -This package follows the same license as the main RevenueCat Unity SDK. - diff --git a/RevenueCatUI/package.json b/RevenueCatUI/package.json index 022490f8..155915ff 100644 --- a/RevenueCatUI/package.json +++ b/RevenueCatUI/package.json @@ -1,6 +1,6 @@ { "name": "com.revenuecat.purchases-ui-unity", - "version": "1.0.0", + "version": "8.2.3", "displayName": "RevenueCat UI SDK for Unity", "description": "Unity UI components for RevenueCat paywalls and customer center. Supports iOS and Android.", "documentationUrl": "https://www.revenuecat.com/docs/unity-ui", From 17aed4a41daecb890576b550aa5da4ca97e903d8 Mon Sep 17 00:00:00 2001 From: Facundo Menzella Date: Wed, 17 Sep 2025 13:47:53 +0200 Subject: [PATCH 26/65] revert Subtester --- .../Plugins/Android/mainTemplate.gradle | 2 +- Subtester/Assets/Scenes/Main.unity | 84 +- Subtester/Packages/packages-lock.json | 17 +- .../AndroidResolverDependencies.xml | 2 +- .../ProjectSettings/ProjectSettings.asset | 3 - .../UserSettings/Layouts/default-2021.dwlt | 999 ------------------ 6 files changed, 17 insertions(+), 1090 deletions(-) delete mode 100644 Subtester/UserSettings/Layouts/default-2021.dwlt diff --git a/Subtester/Assets/Plugins/Android/mainTemplate.gradle b/Subtester/Assets/Plugins/Android/mainTemplate.gradle index d33414b3..9d8a4b5b 100644 --- a/Subtester/Assets/Plugins/Android/mainTemplate.gradle +++ b/Subtester/Assets/Plugins/Android/mainTemplate.gradle @@ -20,7 +20,7 @@ dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) // Android Resolver Dependencies Start implementation 'androidx.annotation:annotation:[1.2.0]' // Packages/com.revenuecat.purchases-unity/Plugins/Editor/RevenueCatDependencies.xml:5 - implementation 'com.revenuecat.purchases:purchases-hybrid-common:[17.7.0]' // Packages/com.revenuecat.purchases-unity/Plugins/Editor/RevenueCatDependencies.xml:4 + implementation 'com.revenuecat.purchases:purchases-hybrid-common:[17.5.1]' // Packages/com.revenuecat.purchases-unity/Plugins/Editor/RevenueCatDependencies.xml:4 // Android Resolver Dependencies End **DEPS**} diff --git a/Subtester/Assets/Scenes/Main.unity b/Subtester/Assets/Scenes/Main.unity index 86f34c72..7dbe4a5d 100644 --- a/Subtester/Assets/Scenes/Main.unity +++ b/Subtester/Assets/Scenes/Main.unity @@ -419,83 +419,7 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: useRuntimeSetup: 0 - revenueCatAPIKeyApple: - revenueCatAPIKeyGoogle: - revenueCatAPIKeyAmazon: - useAmazon: 0 - autoSyncPurchases: 1 - appUserID: - productIdentifiers: [] - listener: {fileID: 559298755} - userDefaultsSuiteName: - purchasesAreCompletedBy: 0 - storeKitVersion: 2 - shouldShowInAppMessagesAutomatically: 1 - entitlementVerificationMode: 1 - pendingTransactionsForPrepaidPlansEnabled: 1 - proxyURL: ---- !u!4 &559298754 -Transform: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 559298752} - serializedVersion: 2 - m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: -0.53463364, y: 1.2963469, z: -0.32019186} - m_LocalScale: {x: 1, y: 1, z: 1} - m_ConstrainProportionsScale: 0 - m_Children: [] - m_Father: {fileID: 0} - m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} ---- !u!114 &559298755 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 559298752} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: d4d983d93d67c4367bf31ba35790ced8, type: 3} - m_Name: - m_EditorClassIdentifier: - parentPanel: {fileID: 1575378375} - buttonPrefab: {fileID: 1696602566380582, guid: 08be051e1286142ac9bb1c2a81c63bc3, - type: 3} - infoLabel: {fileID: 53425791} -GameObject: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - serializedVersion: 6 - m_Component: - - component: {fileID: 559298754} - - component: {fileID: 559298755} - - component: {fileID: 559298753} - m_Layer: 0 - m_Name: PurchasesManager - m_TagString: Untagged - m_Icon: {fileID: 0} - m_NavMeshLayer: 0 - m_StaticEditorFlags: 0 - m_IsActive: 1 ---- !u!114 &559298753 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 559298752} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 98e401cc5a2ed4a7ba02f2e6ef35c6f5, type: 3} - m_Name: - m_EditorClassIdentifier: - useRuntimeSetup: 0 - revenueCatAPIKeyApple: appl_fEVHDkWYraujHYbxopxJknYGNUx + revenueCatAPIKeyApple: revenueCatAPIKeyGoogle: revenueCatAPIKeyAmazon: useAmazon: 0 @@ -1072,7 +996,7 @@ MonoBehaviour: m_HandleRect: {fileID: 1016743159} m_Direction: 2 m_Value: 0.90756994 - m_Size: 0.021009983 + m_Size: 0.021009987 m_NumberOfSteps: 0 m_OnValueChanged: m_PersistentCalls: @@ -1466,8 +1390,8 @@ MonoBehaviour: m_TargetGraphic: {fileID: 950935161} m_HandleRect: {fileID: 950935160} m_Direction: 2 - m_Value: 1 - m_Size: 0.20185497 + m_Value: 1.0000001 + m_Size: 0.201855 m_NumberOfSteps: 0 m_OnValueChanged: m_PersistentCalls: diff --git a/Subtester/Packages/packages-lock.json b/Subtester/Packages/packages-lock.json index 6a005f0f..b3dd9755 100644 --- a/Subtester/Packages/packages-lock.json +++ b/Subtester/Packages/packages-lock.json @@ -1,6 +1,11 @@ { "dependencies": { - + "com.revenuecat.purchases-unity": { + "version": "file:../../RevenueCat", + "depth": 0, + "source": "local", + "dependencies": {} + }, "com.unity.2d.sprite": { "version": "1.0.0", "depth": 0, @@ -96,8 +101,8 @@ "source": "registry", "dependencies": { "com.unity.ugui": "1.0.0", - "com.unity.modules.jsonserialize": "1.0.0", - "com.unity.services.core": "1.12.4" + "com.unity.services.core": "1.12.4", + "com.unity.modules.jsonserialize": "1.0.0" }, "url": "https://packages.unity.com" }, @@ -106,9 +111,9 @@ "depth": 2, "source": "registry", "dependencies": { - "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.modules.androidjni": "1.0.0", "com.unity.nuget.newtonsoft-json": "3.2.1", - "com.unity.modules.androidjni": "1.0.0" + "com.unity.modules.unitywebrequest": "1.0.0" }, "url": "https://packages.unity.com" }, @@ -127,9 +132,9 @@ "depth": 0, "source": "registry", "dependencies": { + "com.unity.modules.audio": "1.0.0", "com.unity.modules.director": "1.0.0", "com.unity.modules.animation": "1.0.0", - "com.unity.modules.audio": "1.0.0", "com.unity.modules.particlesystem": "1.0.0" }, "url": "https://packages.unity.com" diff --git a/Subtester/ProjectSettings/AndroidResolverDependencies.xml b/Subtester/ProjectSettings/AndroidResolverDependencies.xml index c1994495..ebb92975 100644 --- a/Subtester/ProjectSettings/AndroidResolverDependencies.xml +++ b/Subtester/ProjectSettings/AndroidResolverDependencies.xml @@ -1,7 +1,7 @@ androidx.annotation:annotation:[1.2.0] - com.revenuecat.purchases:purchases-hybrid-common:[17.7.0] + com.revenuecat.purchases:purchases-hybrid-common:[17.5.1] diff --git a/Subtester/ProjectSettings/ProjectSettings.asset b/Subtester/ProjectSettings/ProjectSettings.asset index 00bae901..5b7e636d 100644 --- a/Subtester/ProjectSettings/ProjectSettings.asset +++ b/Subtester/ProjectSettings/ProjectSettings.asset @@ -570,9 +570,6 @@ PlayerSettings: - serializedVersion: 3 m_BuildTarget: Android m_Formats: 01000000 - - serializedVersion: 3 - m_BuildTarget: iOS - m_Formats: 03000000 playModeTestRunnerEnabled: 0 runPlayModeTestAsEditModeTest: 0 actionOnDotNetUnhandledException: 1 diff --git a/Subtester/UserSettings/Layouts/default-2021.dwlt b/Subtester/UserSettings/Layouts/default-2021.dwlt deleted file mode 100644 index edde8e87..00000000 --- a/Subtester/UserSettings/Layouts/default-2021.dwlt +++ /dev/null @@ -1,999 +0,0 @@ -%YAML 1.1 -%TAG !u! tag:unity3d.com,2011: ---- !u!114 &1 -MonoBehaviour: - m_ObjectHideFlags: 52 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 12004, guid: 0000000000000000e000000000000000, type: 0} - m_Name: - m_EditorClassIdentifier: - m_PixelRect: - serializedVersion: 2 - x: 0 - y: 66 - width: 1298 - height: 916 - m_ShowMode: 4 - m_Title: Console - m_RootView: {fileID: 6} - m_MinSize: {x: 875, y: 300} - m_MaxSize: {x: 10000, y: 10000} - m_Maximized: 1 ---- !u!114 &2 -MonoBehaviour: - m_ObjectHideFlags: 52 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 1 - m_Script: {fileID: 12010, guid: 0000000000000000e000000000000000, type: 0} - m_Name: - m_EditorClassIdentifier: - m_Children: - - {fileID: 9} - - {fileID: 3} - m_Position: - serializedVersion: 2 - x: 0 - y: 30 - width: 1298 - height: 866 - m_MinSize: {x: 300, y: 200} - m_MaxSize: {x: 24288, y: 16192} - vertical: 0 - controlID: 169 ---- !u!114 &3 -MonoBehaviour: - m_ObjectHideFlags: 52 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 1 - m_Script: {fileID: 12006, guid: 0000000000000000e000000000000000, type: 0} - m_Name: - m_EditorClassIdentifier: - m_Children: [] - m_Position: - serializedVersion: 2 - x: 991.5 - y: 0 - width: 306.5 - height: 866 - m_MinSize: {x: 275, y: 50} - m_MaxSize: {x: 4000, y: 4000} - m_ActualView: {fileID: 14} - m_Panes: - - {fileID: 14} - m_Selected: 0 - m_LastSelected: 0 ---- !u!114 &4 -MonoBehaviour: - m_ObjectHideFlags: 52 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 1 - m_Script: {fileID: 12006, guid: 0000000000000000e000000000000000, type: 0} - m_Name: - m_EditorClassIdentifier: - m_Children: [] - m_Position: - serializedVersion: 2 - x: 0 - y: 0 - width: 243 - height: 524 - m_MinSize: {x: 201, y: 221} - m_MaxSize: {x: 4001, y: 4021} - m_ActualView: {fileID: 15} - m_Panes: - - {fileID: 15} - m_Selected: 0 - m_LastSelected: 0 ---- !u!114 &5 -MonoBehaviour: - m_ObjectHideFlags: 52 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 1 - m_Script: {fileID: 12006, guid: 0000000000000000e000000000000000, type: 0} - m_Name: ConsoleWindow - m_EditorClassIdentifier: - m_Children: [] - m_Position: - serializedVersion: 2 - x: 0 - y: 524 - width: 991.5 - height: 342 - m_MinSize: {x: 101, y: 121} - m_MaxSize: {x: 4001, y: 4021} - m_ActualView: {fileID: 18} - m_Panes: - - {fileID: 13} - - {fileID: 18} - m_Selected: 1 - m_LastSelected: 0 ---- !u!114 &6 -MonoBehaviour: - m_ObjectHideFlags: 52 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 1 - m_Script: {fileID: 12008, guid: 0000000000000000e000000000000000, type: 0} - m_Name: - m_EditorClassIdentifier: - m_Children: - - {fileID: 7} - - {fileID: 2} - - {fileID: 8} - m_Position: - serializedVersion: 2 - x: 0 - y: 0 - width: 1298 - height: 916 - m_MinSize: {x: 875, y: 300} - m_MaxSize: {x: 10000, y: 10000} - m_UseTopView: 1 - m_TopViewHeight: 30 - m_UseBottomView: 1 - m_BottomViewHeight: 20 ---- !u!114 &7 -MonoBehaviour: - m_ObjectHideFlags: 52 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 1 - m_Script: {fileID: 12011, guid: 0000000000000000e000000000000000, type: 0} - m_Name: - m_EditorClassIdentifier: - m_Children: [] - m_Position: - serializedVersion: 2 - x: 0 - y: 0 - width: 1298 - height: 30 - m_MinSize: {x: 0, y: 0} - m_MaxSize: {x: 0, y: 0} - m_LastLoadedLayoutName: ---- !u!114 &8 -MonoBehaviour: - m_ObjectHideFlags: 52 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 1 - m_Script: {fileID: 12042, guid: 0000000000000000e000000000000000, type: 0} - m_Name: - m_EditorClassIdentifier: - m_Children: [] - m_Position: - serializedVersion: 2 - x: 0 - y: 896 - width: 1298 - height: 20 - m_MinSize: {x: 0, y: 0} - m_MaxSize: {x: 0, y: 0} ---- !u!114 &9 -MonoBehaviour: - m_ObjectHideFlags: 52 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 1 - m_Script: {fileID: 12010, guid: 0000000000000000e000000000000000, type: 0} - m_Name: - m_EditorClassIdentifier: - m_Children: - - {fileID: 10} - - {fileID: 5} - m_Position: - serializedVersion: 2 - x: 0 - y: 0 - width: 991.5 - height: 866 - m_MinSize: {x: 200, y: 200} - m_MaxSize: {x: 16192, y: 16192} - vertical: 1 - controlID: 170 ---- !u!114 &10 -MonoBehaviour: - m_ObjectHideFlags: 52 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 1 - m_Script: {fileID: 12010, guid: 0000000000000000e000000000000000, type: 0} - m_Name: - m_EditorClassIdentifier: - m_Children: - - {fileID: 4} - - {fileID: 11} - m_Position: - serializedVersion: 2 - x: 0 - y: 0 - width: 991.5 - height: 524 - m_MinSize: {x: 200, y: 100} - m_MaxSize: {x: 16192, y: 8096} - vertical: 0 - controlID: 171 ---- !u!114 &11 -MonoBehaviour: - m_ObjectHideFlags: 52 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 1 - m_Script: {fileID: 12006, guid: 0000000000000000e000000000000000, type: 0} - m_Name: SceneView - m_EditorClassIdentifier: - m_Children: [] - m_Position: - serializedVersion: 2 - x: 243 - y: 0 - width: 748.5 - height: 524 - m_MinSize: {x: 200, y: 200} - m_MaxSize: {x: 4000, y: 4000} - m_ActualView: {fileID: 16} - m_Panes: - - {fileID: 16} - - {fileID: 17} - - {fileID: 12} - m_Selected: 0 - m_LastSelected: 1 ---- !u!114 &12 -MonoBehaviour: - m_ObjectHideFlags: 52 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 1 - m_Script: {fileID: 12111, guid: 0000000000000000e000000000000000, type: 0} - m_Name: - m_EditorClassIdentifier: - m_MinSize: {x: 400, y: 100} - m_MaxSize: {x: 2048, y: 2048} - m_TitleContent: - m_Text: Asset Store - m_Image: {fileID: -7444545952099596278, guid: 0000000000000000d000000000000000, - type: 0} - m_Tooltip: - m_Pos: - serializedVersion: 2 - x: 468 - y: 181 - width: 973 - height: 501 - m_ViewDataDictionary: {fileID: 0} - m_OverlayCanvas: - m_LastAppliedPresetName: Default - m_SaveData: [] ---- !u!114 &13 -MonoBehaviour: - m_ObjectHideFlags: 52 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 1 - m_Script: {fileID: 12014, guid: 0000000000000000e000000000000000, type: 0} - m_Name: - m_EditorClassIdentifier: - m_MinSize: {x: 230, y: 250} - m_MaxSize: {x: 10000, y: 10000} - m_TitleContent: - m_Text: Project - m_Image: {fileID: -5179483145760003458, guid: 0000000000000000d000000000000000, - type: 0} - m_Tooltip: - m_Pos: - serializedVersion: 2 - x: 0 - y: 620 - width: 1154 - height: 321 - m_ViewDataDictionary: {fileID: 0} - m_OverlayCanvas: - m_LastAppliedPresetName: Default - m_SaveData: [] - m_SearchFilter: - m_NameFilter: - m_ClassNames: [] - m_AssetLabels: [] - m_AssetBundleNames: [] - m_VersionControlStates: [] - m_SoftLockControlStates: [] - m_ReferencingInstanceIDs: - m_SceneHandles: - m_ShowAllHits: 0 - m_SkipHidden: 0 - m_SearchArea: 1 - m_Folders: - - Assets/Scenes - m_Globs: [] - m_OriginalText: - m_ViewMode: 1 - m_StartGridSize: 64 - m_LastFolders: - - Assets/Scenes - m_LastFoldersGridSize: -1 - m_LastProjectPath: /Users/facundomenzella/Developer/rc/purchases-unity/Subtester - m_LockTracker: - m_IsLocked: 0 - m_FolderTreeState: - scrollPos: {x: 0, y: 143} - m_SelectedIDs: de4e0000 - m_LastClickedID: 20190 - m_ExpandedIDs: 000000002c4f000000ca9a3bffffff7f - m_RenameOverlay: - m_UserAcceptedRename: 0 - m_Name: - m_OriginalName: - m_EditFieldRect: - serializedVersion: 2 - x: 0 - y: 0 - width: 0 - height: 0 - m_UserData: 0 - m_IsWaitingForDelay: 0 - m_IsRenaming: 0 - m_OriginalEventType: 11 - m_IsRenamingFilename: 1 - m_ClientGUIView: {fileID: 0} - m_SearchString: - m_CreateAssetUtility: - m_EndAction: {fileID: 0} - m_InstanceID: 0 - m_Path: - m_Icon: {fileID: 0} - m_ResourceFile: - m_AssetTreeState: - scrollPos: {x: 0, y: 0} - m_SelectedIDs: - m_LastClickedID: 0 - m_ExpandedIDs: 000000002c4f000000ca9a3bffffff7f - m_RenameOverlay: - m_UserAcceptedRename: 0 - m_Name: - m_OriginalName: - m_EditFieldRect: - serializedVersion: 2 - x: 0 - y: 0 - width: 0 - height: 0 - m_UserData: 0 - m_IsWaitingForDelay: 0 - m_IsRenaming: 0 - m_OriginalEventType: 11 - m_IsRenamingFilename: 1 - m_ClientGUIView: {fileID: 0} - m_SearchString: - m_CreateAssetUtility: - m_EndAction: {fileID: 0} - m_InstanceID: 0 - m_Path: - m_Icon: {fileID: 0} - m_ResourceFile: - m_ListAreaState: - m_SelectedInstanceIDs: - m_LastClickedInstanceID: 0 - m_HadKeyboardFocusLastEvent: 1 - m_ExpandedInstanceIDs: c6230000 - m_RenameOverlay: - m_UserAcceptedRename: 0 - m_Name: - m_OriginalName: - m_EditFieldRect: - serializedVersion: 2 - x: 0 - y: 0 - width: 0 - height: 0 - m_UserData: 0 - m_IsWaitingForDelay: 0 - m_IsRenaming: 0 - m_OriginalEventType: 11 - m_IsRenamingFilename: 1 - m_ClientGUIView: {fileID: 0} - m_CreateAssetUtility: - m_EndAction: {fileID: 0} - m_InstanceID: 0 - m_Path: - m_Icon: {fileID: 0} - m_ResourceFile: - m_NewAssetIndexInList: -1 - m_ScrollPosition: {x: 0, y: 0} - m_GridSize: 64 - m_SkipHiddenPackages: 0 - m_DirectoriesAreaWidth: 214 ---- !u!114 &14 -MonoBehaviour: - m_ObjectHideFlags: 52 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 1 - m_Script: {fileID: 12019, guid: 0000000000000000e000000000000000, type: 0} - m_Name: - m_EditorClassIdentifier: - m_MinSize: {x: 275, y: 50} - m_MaxSize: {x: 4000, y: 4000} - m_TitleContent: - m_Text: Inspector - m_Image: {fileID: -440750813802333266, guid: 0000000000000000d000000000000000, - type: 0} - m_Tooltip: - m_Pos: - serializedVersion: 2 - x: 991.5 - y: 96 - width: 305.5 - height: 845 - m_ViewDataDictionary: {fileID: 0} - m_OverlayCanvas: - m_LastAppliedPresetName: Default - m_SaveData: [] - m_ObjectsLockedBeforeSerialization: [] - m_InstanceIDsLockedBeforeSerialization: - m_PreviewResizer: - m_CachedPref: 160 - m_ControlHash: -371814159 - m_PrefName: Preview_InspectorPreview - m_LastInspectedObjectInstanceID: -1 - m_LastVerticalScrollValue: 0 - m_GlobalObjectId: - m_InspectorMode: 0 - m_LockTracker: - m_IsLocked: 0 - m_PreviewWindow: {fileID: 0} ---- !u!114 &15 -MonoBehaviour: - m_ObjectHideFlags: 52 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 1 - m_Script: {fileID: 12061, guid: 0000000000000000e000000000000000, type: 0} - m_Name: - m_EditorClassIdentifier: - m_MinSize: {x: 200, y: 200} - m_MaxSize: {x: 4000, y: 4000} - m_TitleContent: - m_Text: Hierarchy - m_Image: {fileID: -3734745235275155857, guid: 0000000000000000d000000000000000, - type: 0} - m_Tooltip: - m_Pos: - serializedVersion: 2 - x: 0 - y: 96 - width: 242 - height: 503 - m_ViewDataDictionary: {fileID: 0} - m_OverlayCanvas: - m_LastAppliedPresetName: Default - m_SaveData: [] - m_SceneHierarchy: - m_TreeViewState: - scrollPos: {x: 0, y: 0} - m_SelectedIDs: - m_LastClickedID: 0 - m_ExpandedIDs: caefffff4afaffff - m_RenameOverlay: - m_UserAcceptedRename: 0 - m_Name: - m_OriginalName: - m_EditFieldRect: - serializedVersion: 2 - x: 0 - y: 0 - width: 0 - height: 0 - m_UserData: 0 - m_IsWaitingForDelay: 0 - m_IsRenaming: 0 - m_OriginalEventType: 11 - m_IsRenamingFilename: 0 - m_ClientGUIView: {fileID: 0} - m_SearchString: - m_ExpandedScenes: [] - m_CurrenRootInstanceID: 0 - m_LockTracker: - m_IsLocked: 0 - m_CurrentSortingName: TransformSorting - m_WindowGUID: ca75dc20719ba4dea810e8ad275cd97c ---- !u!114 &16 -MonoBehaviour: - m_ObjectHideFlags: 52 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 1 - m_Script: {fileID: 12013, guid: 0000000000000000e000000000000000, type: 0} - m_Name: - m_EditorClassIdentifier: - m_MinSize: {x: 200, y: 200} - m_MaxSize: {x: 4000, y: 4000} - m_TitleContent: - m_Text: Scene - m_Image: {fileID: 8634526014445323508, guid: 0000000000000000d000000000000000, - type: 0} - m_Tooltip: - m_Pos: - serializedVersion: 2 - x: 243 - y: 96 - width: 746.5 - height: 503 - m_ViewDataDictionary: {fileID: 0} - m_OverlayCanvas: - m_LastAppliedPresetName: Default - m_SaveData: - - dockPosition: 0 - containerId: overlay-toolbar__top - floating: 0 - collapsed: 0 - displayed: 1 - snapOffset: {x: -101, y: -26} - snapOffsetDelta: {x: 0, y: 0} - snapCorner: 3 - id: Tool Settings - index: 0 - layout: 1 - - dockPosition: 0 - containerId: overlay-toolbar__top - floating: 0 - collapsed: 0 - displayed: 1 - snapOffset: {x: -141, y: 149} - snapOffsetDelta: {x: 0, y: 0} - snapCorner: 1 - id: unity-grid-and-snap-toolbar - index: 1 - layout: 1 - - dockPosition: 1 - containerId: overlay-toolbar__top - floating: 0 - collapsed: 0 - displayed: 1 - snapOffset: {x: 0, y: 0} - snapOffsetDelta: {x: 0, y: 0} - snapCorner: 0 - id: unity-scene-view-toolbar - index: 0 - layout: 1 - - dockPosition: 1 - containerId: overlay-toolbar__top - floating: 0 - collapsed: 0 - displayed: 0 - snapOffset: {x: 0, y: 0} - snapOffsetDelta: {x: 0, y: 0} - snapCorner: 1 - id: unity-search-toolbar - index: 1 - layout: 1 - - dockPosition: 1 - containerId: overlay-toolbar__top - floating: 0 - collapsed: 0 - displayed: 0 - snapOffset: {x: 0, y: 0} - snapOffsetDelta: {x: 0, y: 0} - snapCorner: 0 - id: Scene View/Open Tile Palette - index: 2 - layout: 4 - - dockPosition: 1 - containerId: overlay-toolbar__top - floating: 0 - collapsed: 0 - displayed: 0 - snapOffset: {x: 0, y: 0} - snapOffsetDelta: {x: 0, y: 0} - snapCorner: 0 - id: Scene View/Tilemap Focus - index: 3 - layout: 4 - - dockPosition: 0 - containerId: overlay-container--left - floating: 0 - collapsed: 0 - displayed: 1 - snapOffset: {x: 0, y: 0} - snapOffsetDelta: {x: 0, y: 0} - snapCorner: 0 - id: unity-transform-toolbar - index: 0 - layout: 2 - - dockPosition: 0 - containerId: overlay-container--right - floating: 0 - collapsed: 0 - displayed: 1 - snapOffset: {x: 67.5, y: 86} - snapOffsetDelta: {x: 0, y: 0} - snapCorner: 0 - id: Orientation - index: 0 - layout: 4 - - dockPosition: 1 - containerId: overlay-container--right - floating: 0 - collapsed: 0 - displayed: 0 - snapOffset: {x: 0, y: 0} - snapOffsetDelta: {x: 0, y: 0} - snapCorner: 0 - id: Scene View/Light Settings - index: 0 - layout: 4 - - dockPosition: 1 - containerId: overlay-container--right - floating: 0 - collapsed: 0 - displayed: 0 - snapOffset: {x: 0, y: 0} - snapOffsetDelta: {x: 0, y: 0} - snapCorner: 0 - id: Scene View/Camera - index: 1 - layout: 4 - - dockPosition: 1 - containerId: overlay-container--right - floating: 0 - collapsed: 0 - displayed: 0 - snapOffset: {x: 0, y: 0} - snapOffsetDelta: {x: 0, y: 0} - snapCorner: 0 - id: Scene View/Cloth Constraints - index: 2 - layout: 4 - - dockPosition: 1 - containerId: overlay-container--right - floating: 0 - collapsed: 0 - displayed: 0 - snapOffset: {x: 0, y: 0} - snapOffsetDelta: {x: 0, y: 0} - snapCorner: 0 - id: Scene View/Cloth Collisions - index: 3 - layout: 4 - - dockPosition: 1 - containerId: overlay-container--right - floating: 0 - collapsed: 0 - displayed: 0 - snapOffset: {x: 0, y: 0} - snapOffsetDelta: {x: 0, y: 0} - snapCorner: 0 - id: Scene View/Navmesh Display - index: 4 - layout: 4 - - dockPosition: 1 - containerId: overlay-container--right - floating: 0 - collapsed: 0 - displayed: 0 - snapOffset: {x: 0, y: 0} - snapOffsetDelta: {x: 0, y: 0} - snapCorner: 0 - id: Scene View/Agent Display - index: 5 - layout: 4 - - dockPosition: 1 - containerId: overlay-container--right - floating: 0 - collapsed: 0 - displayed: 0 - snapOffset: {x: 0, y: 0} - snapOffsetDelta: {x: 0, y: 0} - snapCorner: 0 - id: Scene View/Obstacle Display - index: 6 - layout: 4 - - dockPosition: 1 - containerId: overlay-container--right - floating: 0 - collapsed: 0 - displayed: 0 - snapOffset: {x: 0, y: 0} - snapOffsetDelta: {x: 0, y: 0} - snapCorner: 0 - id: Scene View/Occlusion Culling - index: 7 - layout: 4 - - dockPosition: 1 - containerId: overlay-container--right - floating: 0 - collapsed: 0 - displayed: 0 - snapOffset: {x: 0, y: 0} - snapOffsetDelta: {x: 0, y: 0} - snapCorner: 0 - id: Scene View/Physics Debugger - index: 8 - layout: 4 - - dockPosition: 1 - containerId: overlay-container--right - floating: 0 - collapsed: 0 - displayed: 0 - snapOffset: {x: 0, y: 0} - snapOffsetDelta: {x: 0, y: 0} - snapCorner: 0 - id: Scene View/Scene Visibility - index: 9 - layout: 4 - - dockPosition: 1 - containerId: overlay-container--right - floating: 0 - collapsed: 0 - displayed: 0 - snapOffset: {x: 0, y: 0} - snapOffsetDelta: {x: 0, y: 0} - snapCorner: 0 - id: Scene View/Particles - index: 10 - layout: 4 - m_WindowGUID: 4256d097feee24264925944e861fcaa2 - m_Gizmos: 1 - m_OverrideSceneCullingMask: 6917529027641081856 - m_SceneIsLit: 1 - m_SceneLighting: 1 - m_2DMode: 0 - m_isRotationLocked: 0 - m_PlayAudio: 0 - m_AudioPlay: 0 - m_Position: - m_Target: {x: 460.2448, y: 190.76195, z: -83.29181} - speed: 2 - m_Value: {x: 460.2448, y: 190.76195, z: -83.29181} - m_RenderMode: 0 - m_CameraMode: - drawMode: 0 - name: Shaded - section: Shading Mode - m_ValidateTrueMetals: 0 - m_DoValidateTrueMetals: 0 - m_ExposureSliderValue: 0 - m_SceneViewState: - m_AlwaysRefresh: 0 - showFog: 1 - showSkybox: 1 - showFlares: 1 - showImageEffects: 1 - showParticleSystems: 1 - showVisualEffectGraphs: 1 - m_FxEnabled: 1 - m_Grid: - xGrid: - m_Fade: - m_Target: 0 - speed: 2 - m_Value: 0 - m_Color: {r: 0.5, g: 0.5, b: 0.5, a: 0.4} - m_Pivot: {x: 0, y: 0, z: 0} - m_Size: {x: 0, y: 0} - yGrid: - m_Fade: - m_Target: 1 - speed: 2 - m_Value: 1 - m_Color: {r: 0.5, g: 0.5, b: 0.5, a: 0.4} - m_Pivot: {x: 0, y: 0, z: 0} - m_Size: {x: 1, y: 1} - zGrid: - m_Fade: - m_Target: 0 - speed: 2 - m_Value: 0 - m_Color: {r: 0.5, g: 0.5, b: 0.5, a: 0.4} - m_Pivot: {x: 0, y: 0, z: 0} - m_Size: {x: 0, y: 0} - m_ShowGrid: 1 - m_GridAxis: 1 - m_gridOpacity: 0.5 - m_Rotation: - m_Target: {x: 0, y: 0, z: 0, w: 1} - speed: 2 - m_Value: {x: -0, y: 0, z: -0, w: -1} - m_Size: - m_Target: 1455.9442 - speed: 2 - m_Value: 1455.9442 - m_Ortho: - m_Target: 0 - speed: 2 - m_Value: 0 - m_CameraSettings: - m_Speed: 1 - m_SpeedNormalized: 0.5 - m_SpeedMin: 0.01 - m_SpeedMax: 2 - m_EasingEnabled: 1 - m_EasingDuration: 0.4 - m_AccelerationEnabled: 1 - m_FieldOfViewHorizontalOrVertical: 60 - m_NearClip: 0.03 - m_FarClip: 10000 - m_DynamicClip: 1 - m_OcclusionCulling: 0 - m_LastSceneViewRotation: {x: 0, y: 0, z: 0, w: 0} - m_LastSceneViewOrtho: 0 - m_ReplacementShader: {fileID: 0} - m_ReplacementString: - m_SceneVisActive: 1 - m_LastLockedObject: {fileID: 0} - m_ViewIsLockedToObject: 0 ---- !u!114 &17 -MonoBehaviour: - m_ObjectHideFlags: 52 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 1 - m_Script: {fileID: 12015, guid: 0000000000000000e000000000000000, type: 0} - m_Name: - m_EditorClassIdentifier: - m_MinSize: {x: 200, y: 200} - m_MaxSize: {x: 4000, y: 4000} - m_TitleContent: - m_Text: Game - m_Image: {fileID: 4621777727084837110, guid: 0000000000000000d000000000000000, - type: 0} - m_Tooltip: - m_Pos: - serializedVersion: 2 - x: 283 - y: 96 - width: 870 - height: 503 - m_ViewDataDictionary: {fileID: 0} - m_OverlayCanvas: - m_LastAppliedPresetName: Default - m_SaveData: [] - m_SerializedViewNames: [] - m_SerializedViewValues: [] - m_PlayModeViewName: GameView - m_ShowGizmos: 0 - m_TargetDisplay: 0 - m_ClearColor: {r: 0, g: 0, b: 0, a: 0} - m_TargetSize: {x: 1740, y: 964} - m_TextureFilterMode: 0 - m_TextureHideFlags: 61 - m_RenderIMGUI: 1 - m_EnterPlayModeBehavior: 0 - m_UseMipMap: 0 - m_VSyncEnabled: 0 - m_Gizmos: 0 - m_Stats: 0 - m_SelectedSizes: 00000000000000000000000000000000000000000000000000000000000000000000000000000000 - m_ZoomArea: - m_HRangeLocked: 0 - m_VRangeLocked: 0 - hZoomLockedByDefault: 0 - vZoomLockedByDefault: 0 - m_HBaseRangeMin: -435 - m_HBaseRangeMax: 435 - m_VBaseRangeMin: -241 - m_VBaseRangeMax: 241 - m_HAllowExceedBaseRangeMin: 1 - m_HAllowExceedBaseRangeMax: 1 - m_VAllowExceedBaseRangeMin: 1 - m_VAllowExceedBaseRangeMax: 1 - m_ScaleWithWindow: 0 - m_HSlider: 0 - m_VSlider: 0 - m_IgnoreScrollWheelUntilClicked: 0 - m_EnableMouseInput: 0 - m_EnableSliderZoomHorizontal: 0 - m_EnableSliderZoomVertical: 0 - m_UniformScale: 1 - m_UpDirection: 1 - m_DrawArea: - serializedVersion: 2 - x: 0 - y: 21 - width: 870 - height: 482 - m_Scale: {x: 1, y: 1} - m_Translation: {x: 435, y: 241} - m_MarginLeft: 0 - m_MarginRight: 0 - m_MarginTop: 0 - m_MarginBottom: 0 - m_LastShownAreaInsideMargins: - serializedVersion: 2 - x: -435 - y: -241 - width: 870 - height: 482 - m_MinimalGUI: 1 - m_defaultScale: 1 - m_LastWindowPixelSize: {x: 1740, y: 1006} - m_ClearInEditMode: 1 - m_NoCameraWarning: 1 - m_LowResolutionForAspectRatios: 01000000000000000000 - m_XRRenderMode: 0 - m_RenderTexture: {fileID: 0} ---- !u!114 &18 -MonoBehaviour: - m_ObjectHideFlags: 52 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 1 - m_Script: {fileID: 12003, guid: 0000000000000000e000000000000000, type: 0} - m_Name: - m_EditorClassIdentifier: - m_MinSize: {x: 100, y: 100} - m_MaxSize: {x: 4000, y: 4000} - m_TitleContent: - m_Text: Console - m_Image: {fileID: -4950941429401207979, guid: 0000000000000000d000000000000000, - type: 0} - m_Tooltip: - m_Pos: - serializedVersion: 2 - x: 0 - y: 620 - width: 990.5 - height: 321 - m_ViewDataDictionary: {fileID: 0} - m_OverlayCanvas: - m_LastAppliedPresetName: Default - m_SaveData: [] From d3cca4557f6c7b02866d76527bc0134dc75d8c77 Mon Sep 17 00:00:00 2001 From: Facundo Menzella Date: Wed, 17 Sep 2025 13:48:59 +0200 Subject: [PATCH 27/65] Update Subtester/Assets/Plugins/Android/mainTemplate.gradle --- Subtester/Assets/Plugins/Android/mainTemplate.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Subtester/Assets/Plugins/Android/mainTemplate.gradle b/Subtester/Assets/Plugins/Android/mainTemplate.gradle index 9d8a4b5b..d33414b3 100644 --- a/Subtester/Assets/Plugins/Android/mainTemplate.gradle +++ b/Subtester/Assets/Plugins/Android/mainTemplate.gradle @@ -20,7 +20,7 @@ dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) // Android Resolver Dependencies Start implementation 'androidx.annotation:annotation:[1.2.0]' // Packages/com.revenuecat.purchases-unity/Plugins/Editor/RevenueCatDependencies.xml:5 - implementation 'com.revenuecat.purchases:purchases-hybrid-common:[17.5.1]' // Packages/com.revenuecat.purchases-unity/Plugins/Editor/RevenueCatDependencies.xml:4 + implementation 'com.revenuecat.purchases:purchases-hybrid-common:[17.7.0]' // Packages/com.revenuecat.purchases-unity/Plugins/Editor/RevenueCatDependencies.xml:4 // Android Resolver Dependencies End **DEPS**} From f51be9e70d283712acd0c705e41964ef2f61880c Mon Sep 17 00:00:00 2001 From: Facundo Menzella Date: Wed, 17 Sep 2025 13:49:10 +0200 Subject: [PATCH 28/65] Update Subtester/ProjectSettings/AndroidResolverDependencies.xml --- Subtester/ProjectSettings/AndroidResolverDependencies.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Subtester/ProjectSettings/AndroidResolverDependencies.xml b/Subtester/ProjectSettings/AndroidResolverDependencies.xml index ebb92975..c1994495 100644 --- a/Subtester/ProjectSettings/AndroidResolverDependencies.xml +++ b/Subtester/ProjectSettings/AndroidResolverDependencies.xml @@ -1,7 +1,7 @@ androidx.annotation:annotation:[1.2.0] - com.revenuecat.purchases:purchases-hybrid-common:[17.5.1] + com.revenuecat.purchases:purchases-hybrid-common:[17.7.0] From 1e1ee0190997807f9cf9558820c8a76342ebb2ff Mon Sep 17 00:00:00 2001 From: Facundo Menzella Date: Wed, 17 Sep 2025 13:50:22 +0200 Subject: [PATCH 29/65] Make properties internal --- RevenueCatUI/Scripts/PaywallResult.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/RevenueCatUI/Scripts/PaywallResult.cs b/RevenueCatUI/Scripts/PaywallResult.cs index 0b97f0d4..60505790 100644 --- a/RevenueCatUI/Scripts/PaywallResult.cs +++ b/RevenueCatUI/Scripts/PaywallResult.cs @@ -23,11 +23,11 @@ public PaywallResult(PaywallResultType result) } // Convenient static properties for common results - public static PaywallResult NotPresented => new PaywallResult(PaywallResultType.NotPresented); - public static PaywallResult Cancelled => new PaywallResult(PaywallResultType.Cancelled); - public static PaywallResult Error => new PaywallResult(PaywallResultType.Error); - public static PaywallResult Purchased => new PaywallResult(PaywallResultType.Purchased); - public static PaywallResult Restored => new PaywallResult(PaywallResultType.Restored); + internal static PaywallResult NotPresented => new PaywallResult(PaywallResultType.NotPresented); + internal static PaywallResult Cancelled => new PaywallResult(PaywallResultType.Cancelled); + internal static PaywallResult Error => new PaywallResult(PaywallResultType.Error); + internal static PaywallResult Purchased => new PaywallResult(PaywallResultType.Purchased); + internal static PaywallResult Restored => new PaywallResult(PaywallResultType.Restored); public override string ToString() { From 2693a44fe70c8ae06d210bec5568ed91bc8114ff Mon Sep 17 00:00:00 2001 From: Cesar de la Vega <664544+vegaro@users.noreply.github.com> Date: Thu, 28 Aug 2025 12:54:15 +0200 Subject: [PATCH 30/65] minimal paywalls presentation # Conflicts: # RevenueCat/Scripts/PurchasesWrapperAndroid.cs # Subtester/Assets/Plugins/Android/mainTemplate.gradle --- .../Plugins/Android/AndroidManifest.xml | 17 +++++ .../Plugins/Android/AndroidManifest.xml.meta | 7 ++ .../Plugins/Android/PaywallProxyActivity.java | 66 +++++++++++++++++++ .../Android/PaywallProxyActivity.java.meta | 32 +++++++++ .../Plugins/Android/PurchasesWrapper.java | 19 ++++++ .../Plugins/Editor/RevenueCatDependencies.xml | 1 + RevenueCat/Scripts/PaywallResult.cs | 64 ++++++++++++++++++ RevenueCat/Scripts/PaywallResult.cs.meta | 11 ++++ RevenueCat/Scripts/Purchases.cs | 23 +++++++ RevenueCat/Scripts/PurchasesWrapper.cs | 2 + RevenueCat/Scripts/PurchasesWrapperAndroid.cs | 4 ++ RevenueCat/Scripts/PurchasesWrapperNoop.cs | 4 ++ .../Plugins/Android/mainTemplate.gradle | 4 +- Subtester/Assets/Scripts/PurchasesListener.cs | 63 ++++++++++++++++++ 14 files changed, 315 insertions(+), 2 deletions(-) create mode 100644 RevenueCat/Plugins/Android/AndroidManifest.xml create mode 100644 RevenueCat/Plugins/Android/AndroidManifest.xml.meta create mode 100644 RevenueCat/Plugins/Android/PaywallProxyActivity.java create mode 100644 RevenueCat/Plugins/Android/PaywallProxyActivity.java.meta create mode 100644 RevenueCat/Scripts/PaywallResult.cs create mode 100644 RevenueCat/Scripts/PaywallResult.cs.meta diff --git a/RevenueCat/Plugins/Android/AndroidManifest.xml b/RevenueCat/Plugins/Android/AndroidManifest.xml new file mode 100644 index 00000000..068d8dcf --- /dev/null +++ b/RevenueCat/Plugins/Android/AndroidManifest.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + diff --git a/RevenueCat/Plugins/Android/AndroidManifest.xml.meta b/RevenueCat/Plugins/Android/AndroidManifest.xml.meta new file mode 100644 index 00000000..65dcbdb4 --- /dev/null +++ b/RevenueCat/Plugins/Android/AndroidManifest.xml.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 49b506501dcda465182d421d18936d8c +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/RevenueCat/Plugins/Android/PaywallProxyActivity.java b/RevenueCat/Plugins/Android/PaywallProxyActivity.java new file mode 100644 index 00000000..2f014de8 --- /dev/null +++ b/RevenueCat/Plugins/Android/PaywallProxyActivity.java @@ -0,0 +1,66 @@ +package com.revenuecat.purchasesunity; + +import android.content.Intent; +import android.os.Bundle; +import android.util.Log; + +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; + +import com.revenuecat.purchases.ui.revenuecatui.activity.PaywallActivityLauncher; +import com.revenuecat.purchases.ui.revenuecatui.activity.PaywallResult; +import com.revenuecat.purchases.ui.revenuecatui.activity.PaywallResultHandler; +import com.unity3d.player.UnityPlayer; + +public class PaywallProxyActivity extends AppCompatActivity { + static final String EXTRA_GAME_OBJECT = "rc_proxy_game_object"; + static final String EXTRA_METHOD = "rc_proxy_method"; + static final String EXTRA_OFFERING_ID = "rc_offering_id"; + + private String gameObject; + private String method; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Intent source = getIntent(); + gameObject = source.getStringExtra(EXTRA_GAME_OBJECT); + method = source.getStringExtra(EXTRA_METHOD); + + PaywallActivityLauncher launcher = new PaywallActivityLauncher(this, result -> { + try { + if (result != null) { + sendPaywallResult(result); + } + } finally { + finish(); + } + }); + + Log.d("PurchasesUnity", "Launching paywall with PaywallActivityLauncher"); + launcher.launch(null); + } + + private void sendPaywallResult(PaywallResult result) { + if (gameObject == null || method == null) return; + + String resultName; + if (result instanceof PaywallResult.Purchased) { + resultName = "purchased"; + } else if (result instanceof PaywallResult.Restored) { + resultName = "restored"; + } else if (result instanceof PaywallResult.Cancelled) { + resultName = "cancelled"; + } else if (result instanceof PaywallResult.Error) { + resultName = "error"; + } else { + resultName = "cancelled"; // fallback + } + + Log.d("PurchasesUnity", "Sending PaywallResult: " + resultName); + UnityPlayer.UnitySendMessage(gameObject, method, resultName); + } +} + + diff --git a/RevenueCat/Plugins/Android/PaywallProxyActivity.java.meta b/RevenueCat/Plugins/Android/PaywallProxyActivity.java.meta new file mode 100644 index 00000000..4e692f82 --- /dev/null +++ b/RevenueCat/Plugins/Android/PaywallProxyActivity.java.meta @@ -0,0 +1,32 @@ +fileFormatVersion: 2 +guid: 3ac2d40cd9cdc4bfb8f95372e2b12f72 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 1 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + Android: Android + second: + enabled: 1 + settings: {} + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + userData: + assetBundleName: + assetBundleVariant: diff --git a/RevenueCat/Plugins/Android/PurchasesWrapper.java b/RevenueCat/Plugins/Android/PurchasesWrapper.java index 612d002d..f95abe34 100644 --- a/RevenueCat/Plugins/Android/PurchasesWrapper.java +++ b/RevenueCat/Plugins/Android/PurchasesWrapper.java @@ -1,5 +1,6 @@ package com.revenuecat.purchasesunity; +import android.content.Intent; import android.util.Log; import androidx.annotation.NonNull; @@ -62,6 +63,9 @@ public class PurchasesWrapper { private static final String PURCHASE_PRODUCT_WITH_WIN_BACK_OFFER = "_purchaseProductWithWinBackOffer"; private static final String PURCHASE_PACKAGE_WITH_WIN_BACK_OFFER = "_purchasePackageWithWinBackOffer"; private static final String HANDLE_LOG = "_handleLog"; + private static final String PAYWALL_EVENT = "_paywallEvent"; + private static final String PAYWALL_RESULT = "_paywallResult"; + private static final String PLATFORM_NAME = "unity"; private static final String PLUGIN_VERSION = "8.2.3"; @@ -101,6 +105,21 @@ public static void setup(String apiKey, Purchases.getSharedInstance().setUpdatedCustomerInfoListener(listener); } + public static void presentPaywallForResult(@Nullable String offeringIdentifier) { + try { + android.app.Activity activity = UnityPlayer.currentActivity; + Intent intent = new Intent(activity, com.revenuecat.purchasesunity.PaywallProxyActivity.class); + intent.putExtra(com.revenuecat.purchasesunity.PaywallProxyActivity.EXTRA_GAME_OBJECT, gameObject); + intent.putExtra(com.revenuecat.purchasesunity.PaywallProxyActivity.EXTRA_METHOD, PAYWALL_RESULT); + if (offeringIdentifier != null) { + intent.putExtra(com.revenuecat.purchasesunity.PaywallProxyActivity.EXTRA_OFFERING_ID, offeringIdentifier); + } + activity.startActivity(intent); + } catch (Throwable t) { + Log.e("Purchases", "Error launching PaywallProxyActivity", t); + } + } + public static void getStorefront() { CommonKt.getStorefront(storefrontMap -> { if (storefrontMap != null) { diff --git a/RevenueCat/Plugins/Editor/RevenueCatDependencies.xml b/RevenueCat/Plugins/Editor/RevenueCatDependencies.xml index 05ccbd95..89d9f5bb 100644 --- a/RevenueCat/Plugins/Editor/RevenueCatDependencies.xml +++ b/RevenueCat/Plugins/Editor/RevenueCatDependencies.xml @@ -3,6 +3,7 @@ + diff --git a/RevenueCat/Scripts/PaywallResult.cs b/RevenueCat/Scripts/PaywallResult.cs new file mode 100644 index 00000000..0c79242b --- /dev/null +++ b/RevenueCat/Scripts/PaywallResult.cs @@ -0,0 +1,64 @@ +public partial class Purchases +{ + /// + /// Possible values for the result of a paywall presentation. + /// Matches Flutter's PaywallResult enum for consistency across platforms. + /// + public enum PaywallResult + { + /// + /// Only returned when using presentPaywallIfNeeded. Returned if the paywall was not presented. + /// + NotPresented, + + /// + /// Returned when the paywall was presented and the user cancelled without executing an action. + /// + Cancelled, + + /// + /// Returned when the paywall was presented and an error occurred performing an operation. + /// + Error, + + /// + /// Returned when the paywall was presented and the user successfully purchased. + /// + Purchased, + + /// + /// Returned when the paywall was presented and the user successfully restored. + /// + Restored + } + + /// + /// Helper methods for PaywallResult enum. + /// + public static class PaywallResultExtensions + { + /// + /// Creates a PaywallResult enum value from a string result name. + /// + /// The string result name from the platform layer + /// The corresponding PaywallResult enum value + public static PaywallResult FromString(string resultName) + { + switch (resultName?.ToLower()) + { + case "purchased": + return PaywallResult.Purchased; + case "restored": + return PaywallResult.Restored; + case "cancelled": + return PaywallResult.Cancelled; + case "error": + return PaywallResult.Error; + case "notpresented": + return PaywallResult.NotPresented; + default: + return PaywallResult.Cancelled; // fallback + } + } + } +} diff --git a/RevenueCat/Scripts/PaywallResult.cs.meta b/RevenueCat/Scripts/PaywallResult.cs.meta new file mode 100644 index 00000000..f832a0cb --- /dev/null +++ b/RevenueCat/Scripts/PaywallResult.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a78f15c4e2b3e4e2ba4f5a67e8b21b23 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/RevenueCat/Scripts/Purchases.cs b/RevenueCat/Scripts/Purchases.cs index 852c3b78..8de6cd9d 100644 --- a/RevenueCat/Scripts/Purchases.cs +++ b/RevenueCat/Scripts/Purchases.cs @@ -608,6 +608,8 @@ public void SetLogHandler(LogHandlerFunc logHandler) _wrapper.SetLogHandler(); } + private System.Action _oneShotPaywallResultCallback; + private CustomerInfoFunc GetCustomerInfoCallback { get; set; } // ReSharper disable once UnusedMember.Global @@ -1499,6 +1501,20 @@ private void _handleLog(string logDetailsJson) LogHandler(logLevel, messageInResponse); } + // ReSharper disable once UnusedMember.Local + private void _paywallResult(string paywallResultName) + { + Debug.Log("_paywallResult " + paywallResultName); + + var callback = _oneShotPaywallResultCallback; + _oneShotPaywallResultCallback = null; + + if (callback != null) + { + var result = PaywallResultExtensions.FromString(paywallResultName); + callback(result); + } + } // ReSharper disable once UnusedMember.Local private void _restorePurchases(string customerInfoJson) @@ -1879,4 +1895,11 @@ private static bool ResponseHasError(JSONNode response) { return response != null && response.HasKey("error") && response["error"] != null && !response["error"].IsNull; } + + // Present paywall and receive a PaywallResult + public void PresentPaywall(System.Action callback) + { + _oneShotPaywallResultCallback = callback; + _wrapper.PresentPaywall(callback); + } } diff --git a/RevenueCat/Scripts/PurchasesWrapper.cs b/RevenueCat/Scripts/PurchasesWrapper.cs index d073e438..49eb6713 100644 --- a/RevenueCat/Scripts/PurchasesWrapper.cs +++ b/RevenueCat/Scripts/PurchasesWrapper.cs @@ -87,4 +87,6 @@ void SyncAmazonPurchase(string productID, string receiptID, string amazonUserID, void GetEligibleWinBackOffersForPackage(Purchases.Package package); void PurchaseProductWithWinBackOffer(Purchases.StoreProduct storeProduct, Purchases.WinBackOffer winBackOffer); void PurchasePackageWithWinBackOffer(Purchases.Package package, Purchases.WinBackOffer winBackOffer); + + void PresentPaywall(System.Action callback); } diff --git a/RevenueCat/Scripts/PurchasesWrapperAndroid.cs b/RevenueCat/Scripts/PurchasesWrapperAndroid.cs index 7fcf67df..fc1100cc 100644 --- a/RevenueCat/Scripts/PurchasesWrapperAndroid.cs +++ b/RevenueCat/Scripts/PurchasesWrapperAndroid.cs @@ -443,5 +443,9 @@ private static ReturnType CallPurchases(string methodName, params ob } } + public void PresentPaywall(System.Action callback) + { + CallPurchases("presentPaywall"); + } } #endif diff --git a/RevenueCat/Scripts/PurchasesWrapperNoop.cs b/RevenueCat/Scripts/PurchasesWrapperNoop.cs index 4da81a90..b58939dc 100644 --- a/RevenueCat/Scripts/PurchasesWrapperNoop.cs +++ b/RevenueCat/Scripts/PurchasesWrapperNoop.cs @@ -279,5 +279,9 @@ public void PurchaseProductWithWinBackOffer(Purchases.StoreProduct storeProduct, public void PurchasePackageWithWinBackOffer(Purchases.Package package, Purchases.WinBackOffer winBackOffer) { } + + public void PresentPaywall(System.Action callback) + { + } } } diff --git a/Subtester/Assets/Plugins/Android/mainTemplate.gradle b/Subtester/Assets/Plugins/Android/mainTemplate.gradle index d33414b3..2bb71282 100644 --- a/Subtester/Assets/Plugins/Android/mainTemplate.gradle +++ b/Subtester/Assets/Plugins/Android/mainTemplate.gradle @@ -3,7 +3,6 @@ // Android Resolver Repos Start ([rootProject] + (rootProject.subprojects as List)).each { project -> project.repositories { - def unityProjectPath = $/file:///**DIR_UNITYPROJECT**/$.replace("\\", "/") maven { url "https://maven.google.com" } @@ -20,7 +19,8 @@ dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) // Android Resolver Dependencies Start implementation 'androidx.annotation:annotation:[1.2.0]' // Packages/com.revenuecat.purchases-unity/Plugins/Editor/RevenueCatDependencies.xml:5 - implementation 'com.revenuecat.purchases:purchases-hybrid-common:[17.7.0]' // Packages/com.revenuecat.purchases-unity/Plugins/Editor/RevenueCatDependencies.xml:4 + implementation 'androidx.appcompat:appcompat:[1.6.1]' // Packages/com.revenuecat.purchases-unity/Plugins/Editor/RevenueCatDependencies.xml:6 + implementation 'com.revenuecat.purchases:purchases-hybrid-common:[17.0.0]' // Packages/com.revenuecat.purchases-unity/Plugins/Editor/RevenueCatDependencies.xml:4 // Android Resolver Dependencies End **DEPS**} diff --git a/Subtester/Assets/Scripts/PurchasesListener.cs b/Subtester/Assets/Scripts/PurchasesListener.cs index ec8e20e4..668ad26d 100644 --- a/Subtester/Assets/Scripts/PurchasesListener.cs +++ b/Subtester/Assets/Scripts/PurchasesListener.cs @@ -66,6 +66,7 @@ private void Start() CreateButton("Purchase Package For WinBack Testing", PurchasePackageForWinBackTesting); CreateButton("Fetch & Redeem WinBack for Package", FetchAndRedeemWinBackForPackage); CreateButton("Get Storefront", GetStorefront); + CreateButton("Present Paywall", PresentPaywallResult); var purchases = GetComponent(); purchases.SetLogLevel(Purchases.LogLevel.Verbose); @@ -196,6 +197,68 @@ private void CreatePurchasePackageForPlacementButtons() }); } + void PresentPaywallResult() + { + var purchases = GetComponent(); + Debug.Log("Subtester: launching paywall"); + if (infoLabel != null) infoLabel.text = "Launching paywall..."; + purchases.PresentPaywall(result => + { + Debug.Log("Subtester: paywall result = " + result); + if (infoLabel != null) + { + string status = ""; + + switch (result) + { + case Purchases.PaywallResult.Purchased: + status = "PURCHASED - User completed a purchase"; + GetComponent().GetCustomerInfo((customerInfo, error) => { + if (error != null) + { + Debug.LogError("Subtester: Error refreshing customer info after purchase: " + error); + } + else + { + Debug.Log("Subtester: Refreshed customer info after purchase"); + DisplayCustomerInfo(customerInfo); + } + }); + break; + case Purchases.PaywallResult.Restored: + status = "RESTORED - User restored previous purchases"; + GetComponent().GetCustomerInfo((customerInfo, error) => { + if (error != null) + { + Debug.LogError("Subtester: Error refreshing customer info after restore: " + error); + } + else + { + Debug.Log("Subtester: Refreshed customer info after restore"); + DisplayCustomerInfo(customerInfo); + } + }); + break; + case Purchases.PaywallResult.Cancelled: + status = "CANCELLED - User dismissed the paywall"; + break; + case Purchases.PaywallResult.Error: + status = "ERROR - An error occurred during paywall"; + break; + case Purchases.PaywallResult.NotPresented: + status = "NOT PRESENTED - Paywall was not needed"; + break; + default: + status = $"UNKNOWN - Received: {result}"; + break; + } + + infoLabel.text = $"Paywall result: {status}"; + Debug.Log($"Subtester: {status}"); + } + }); + } + private void CreateButton(string label, UnityAction action) { var button = Instantiate(buttonPrefab, parentPanel, false); From 97dec08cbea9294b8bfd900b626e3ff6deaf75c0 Mon Sep 17 00:00:00 2001 From: Cesar de la Vega <664544+vegaro@users.noreply.github.com> Date: Wed, 17 Sep 2025 18:32:58 +0200 Subject: [PATCH 31/65] clean up --- .../Plugins/Android/PurchasesWrapper.java | 19 ---- .../Plugins/Editor/RevenueCatDependencies.xml | 1 - RevenueCat/Scripts/PaywallResult.cs | 64 -------------- RevenueCat/Scripts/PaywallResult.cs.meta | 11 --- RevenueCat/Scripts/Purchases.cs | 24 ----- RevenueCat/Scripts/PurchasesWrapper.cs | 2 - RevenueCat/Scripts/PurchasesWrapperAndroid.cs | 5 -- RevenueCat/Scripts/PurchasesWrapperNoop.cs | 4 - .../Editor/RevenueCat.UI.Editor.asmdef.meta | 8 -- .../Editor/RevenueCatUI.Editor.asmdef | 19 ---- .../Editor/RevenueCatUI.Editor.asmdef.meta | 8 -- ...venuecat.purchases-unity-ui.Editor.asmdef} | 0 ...cat.purchases-unity-ui.Editor.asmdef.meta} | 0 .../Plugins/Android/AndroidManifest.xml | 0 .../Plugins/Android/AndroidManifest.xml.meta | 0 .../Plugins/Android/PaywallProxyActivity.java | 0 .../Android/PaywallProxyActivity.java.meta | 0 .../Plugins/Android/RevenueCatUI.java | 21 ++++- .../Editor/RevenueCatUIDependencies.xml | 11 +++ RevenueCatUI/Plugins/iOS/RevenueCatUI.m | 31 ------- RevenueCatUI/Plugins/iOS/RevenueCatUI.m.meta | 2 - RevenueCatUI/Scripts/Platforms/iOS.meta | 8 -- .../Platforms/iOS/IOSPaywallPresenter.cs | 88 ------------------- .../Platforms/iOS/IOSPaywallPresenter.cs.meta | 3 - 24 files changed, 30 insertions(+), 299 deletions(-) delete mode 100644 RevenueCat/Scripts/PaywallResult.cs delete mode 100644 RevenueCat/Scripts/PaywallResult.cs.meta delete mode 100644 RevenueCatUI/Editor/RevenueCat.UI.Editor.asmdef.meta delete mode 100644 RevenueCatUI/Editor/RevenueCatUI.Editor.asmdef delete mode 100644 RevenueCatUI/Editor/RevenueCatUI.Editor.asmdef.meta rename RevenueCatUI/{Scripts/RevenueCatUI.asmdef => Editor/revenuecat.purchases-unity-ui.Editor.asmdef} (100%) rename RevenueCatUI/{Scripts/RevenueCatUI.asmdef.meta => Editor/revenuecat.purchases-unity-ui.Editor.asmdef.meta} (100%) rename {RevenueCat => RevenueCatUI}/Plugins/Android/AndroidManifest.xml (100%) rename {RevenueCat => RevenueCatUI}/Plugins/Android/AndroidManifest.xml.meta (100%) rename {RevenueCat => RevenueCatUI}/Plugins/Android/PaywallProxyActivity.java (100%) rename {RevenueCat => RevenueCatUI}/Plugins/Android/PaywallProxyActivity.java.meta (100%) create mode 100644 RevenueCatUI/Plugins/Editor/RevenueCatUIDependencies.xml delete mode 100644 RevenueCatUI/Plugins/iOS/RevenueCatUI.m delete mode 100644 RevenueCatUI/Plugins/iOS/RevenueCatUI.m.meta delete mode 100644 RevenueCatUI/Scripts/Platforms/iOS.meta delete mode 100644 RevenueCatUI/Scripts/Platforms/iOS/IOSPaywallPresenter.cs delete mode 100644 RevenueCatUI/Scripts/Platforms/iOS/IOSPaywallPresenter.cs.meta diff --git a/RevenueCat/Plugins/Android/PurchasesWrapper.java b/RevenueCat/Plugins/Android/PurchasesWrapper.java index f95abe34..612d002d 100644 --- a/RevenueCat/Plugins/Android/PurchasesWrapper.java +++ b/RevenueCat/Plugins/Android/PurchasesWrapper.java @@ -1,6 +1,5 @@ package com.revenuecat.purchasesunity; -import android.content.Intent; import android.util.Log; import androidx.annotation.NonNull; @@ -63,9 +62,6 @@ public class PurchasesWrapper { private static final String PURCHASE_PRODUCT_WITH_WIN_BACK_OFFER = "_purchaseProductWithWinBackOffer"; private static final String PURCHASE_PACKAGE_WITH_WIN_BACK_OFFER = "_purchasePackageWithWinBackOffer"; private static final String HANDLE_LOG = "_handleLog"; - private static final String PAYWALL_EVENT = "_paywallEvent"; - private static final String PAYWALL_RESULT = "_paywallResult"; - private static final String PLATFORM_NAME = "unity"; private static final String PLUGIN_VERSION = "8.2.3"; @@ -105,21 +101,6 @@ public static void setup(String apiKey, Purchases.getSharedInstance().setUpdatedCustomerInfoListener(listener); } - public static void presentPaywallForResult(@Nullable String offeringIdentifier) { - try { - android.app.Activity activity = UnityPlayer.currentActivity; - Intent intent = new Intent(activity, com.revenuecat.purchasesunity.PaywallProxyActivity.class); - intent.putExtra(com.revenuecat.purchasesunity.PaywallProxyActivity.EXTRA_GAME_OBJECT, gameObject); - intent.putExtra(com.revenuecat.purchasesunity.PaywallProxyActivity.EXTRA_METHOD, PAYWALL_RESULT); - if (offeringIdentifier != null) { - intent.putExtra(com.revenuecat.purchasesunity.PaywallProxyActivity.EXTRA_OFFERING_ID, offeringIdentifier); - } - activity.startActivity(intent); - } catch (Throwable t) { - Log.e("Purchases", "Error launching PaywallProxyActivity", t); - } - } - public static void getStorefront() { CommonKt.getStorefront(storefrontMap -> { if (storefrontMap != null) { diff --git a/RevenueCat/Plugins/Editor/RevenueCatDependencies.xml b/RevenueCat/Plugins/Editor/RevenueCatDependencies.xml index 89d9f5bb..05ccbd95 100644 --- a/RevenueCat/Plugins/Editor/RevenueCatDependencies.xml +++ b/RevenueCat/Plugins/Editor/RevenueCatDependencies.xml @@ -3,7 +3,6 @@ - diff --git a/RevenueCat/Scripts/PaywallResult.cs b/RevenueCat/Scripts/PaywallResult.cs deleted file mode 100644 index 0c79242b..00000000 --- a/RevenueCat/Scripts/PaywallResult.cs +++ /dev/null @@ -1,64 +0,0 @@ -public partial class Purchases -{ - /// - /// Possible values for the result of a paywall presentation. - /// Matches Flutter's PaywallResult enum for consistency across platforms. - /// - public enum PaywallResult - { - /// - /// Only returned when using presentPaywallIfNeeded. Returned if the paywall was not presented. - /// - NotPresented, - - /// - /// Returned when the paywall was presented and the user cancelled without executing an action. - /// - Cancelled, - - /// - /// Returned when the paywall was presented and an error occurred performing an operation. - /// - Error, - - /// - /// Returned when the paywall was presented and the user successfully purchased. - /// - Purchased, - - /// - /// Returned when the paywall was presented and the user successfully restored. - /// - Restored - } - - /// - /// Helper methods for PaywallResult enum. - /// - public static class PaywallResultExtensions - { - /// - /// Creates a PaywallResult enum value from a string result name. - /// - /// The string result name from the platform layer - /// The corresponding PaywallResult enum value - public static PaywallResult FromString(string resultName) - { - switch (resultName?.ToLower()) - { - case "purchased": - return PaywallResult.Purchased; - case "restored": - return PaywallResult.Restored; - case "cancelled": - return PaywallResult.Cancelled; - case "error": - return PaywallResult.Error; - case "notpresented": - return PaywallResult.NotPresented; - default: - return PaywallResult.Cancelled; // fallback - } - } - } -} diff --git a/RevenueCat/Scripts/PaywallResult.cs.meta b/RevenueCat/Scripts/PaywallResult.cs.meta deleted file mode 100644 index f832a0cb..00000000 --- a/RevenueCat/Scripts/PaywallResult.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: a78f15c4e2b3e4e2ba4f5a67e8b21b23 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/RevenueCat/Scripts/Purchases.cs b/RevenueCat/Scripts/Purchases.cs index 8de6cd9d..b4a208dc 100644 --- a/RevenueCat/Scripts/Purchases.cs +++ b/RevenueCat/Scripts/Purchases.cs @@ -608,8 +608,6 @@ public void SetLogHandler(LogHandlerFunc logHandler) _wrapper.SetLogHandler(); } - private System.Action _oneShotPaywallResultCallback; - private CustomerInfoFunc GetCustomerInfoCallback { get; set; } // ReSharper disable once UnusedMember.Global @@ -1501,21 +1499,6 @@ private void _handleLog(string logDetailsJson) LogHandler(logLevel, messageInResponse); } - // ReSharper disable once UnusedMember.Local - private void _paywallResult(string paywallResultName) - { - Debug.Log("_paywallResult " + paywallResultName); - - var callback = _oneShotPaywallResultCallback; - _oneShotPaywallResultCallback = null; - - if (callback != null) - { - var result = PaywallResultExtensions.FromString(paywallResultName); - callback(result); - } - } - // ReSharper disable once UnusedMember.Local private void _restorePurchases(string customerInfoJson) { @@ -1895,11 +1878,4 @@ private static bool ResponseHasError(JSONNode response) { return response != null && response.HasKey("error") && response["error"] != null && !response["error"].IsNull; } - - // Present paywall and receive a PaywallResult - public void PresentPaywall(System.Action callback) - { - _oneShotPaywallResultCallback = callback; - _wrapper.PresentPaywall(callback); - } } diff --git a/RevenueCat/Scripts/PurchasesWrapper.cs b/RevenueCat/Scripts/PurchasesWrapper.cs index 49eb6713..d073e438 100644 --- a/RevenueCat/Scripts/PurchasesWrapper.cs +++ b/RevenueCat/Scripts/PurchasesWrapper.cs @@ -87,6 +87,4 @@ void SyncAmazonPurchase(string productID, string receiptID, string amazonUserID, void GetEligibleWinBackOffersForPackage(Purchases.Package package); void PurchaseProductWithWinBackOffer(Purchases.StoreProduct storeProduct, Purchases.WinBackOffer winBackOffer); void PurchasePackageWithWinBackOffer(Purchases.Package package, Purchases.WinBackOffer winBackOffer); - - void PresentPaywall(System.Action callback); } diff --git a/RevenueCat/Scripts/PurchasesWrapperAndroid.cs b/RevenueCat/Scripts/PurchasesWrapperAndroid.cs index fc1100cc..23f40e1a 100644 --- a/RevenueCat/Scripts/PurchasesWrapperAndroid.cs +++ b/RevenueCat/Scripts/PurchasesWrapperAndroid.cs @@ -442,10 +442,5 @@ private static ReturnType CallPurchases(string methodName, params ob return purchases.CallStatic(methodName, args); } } - - public void PresentPaywall(System.Action callback) - { - CallPurchases("presentPaywall"); - } } #endif diff --git a/RevenueCat/Scripts/PurchasesWrapperNoop.cs b/RevenueCat/Scripts/PurchasesWrapperNoop.cs index b58939dc..4da81a90 100644 --- a/RevenueCat/Scripts/PurchasesWrapperNoop.cs +++ b/RevenueCat/Scripts/PurchasesWrapperNoop.cs @@ -279,9 +279,5 @@ public void PurchaseProductWithWinBackOffer(Purchases.StoreProduct storeProduct, public void PurchasePackageWithWinBackOffer(Purchases.Package package, Purchases.WinBackOffer winBackOffer) { } - - public void PresentPaywall(System.Action callback) - { - } } } diff --git a/RevenueCatUI/Editor/RevenueCat.UI.Editor.asmdef.meta b/RevenueCatUI/Editor/RevenueCat.UI.Editor.asmdef.meta deleted file mode 100644 index 31499d52..00000000 --- a/RevenueCatUI/Editor/RevenueCat.UI.Editor.asmdef.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: e6c6f27f8e4c4f6e9e0d8a9a8f1b2c3d -AssemblyDefinitionImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: - diff --git a/RevenueCatUI/Editor/RevenueCatUI.Editor.asmdef b/RevenueCatUI/Editor/RevenueCatUI.Editor.asmdef deleted file mode 100644 index ccb71242..00000000 --- a/RevenueCatUI/Editor/RevenueCatUI.Editor.asmdef +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "RevenueCatUI.Editor", - "rootNamespace": "RevenueCat.UI", - "references": [ - "RevenueCatUI" - ], - "includePlatforms": [ - "Editor" - ], - "excludePlatforms": [], - "allowUnsafeCode": false, - "overrideReferences": false, - "precompiledReferences": [], - "autoReferenced": true, - "defineConstraints": [], - "versionDefines": [], - "noEngineReferences": false -} - diff --git a/RevenueCatUI/Editor/RevenueCatUI.Editor.asmdef.meta b/RevenueCatUI/Editor/RevenueCatUI.Editor.asmdef.meta deleted file mode 100644 index 31499d52..00000000 --- a/RevenueCatUI/Editor/RevenueCatUI.Editor.asmdef.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: e6c6f27f8e4c4f6e9e0d8a9a8f1b2c3d -AssemblyDefinitionImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: - diff --git a/RevenueCatUI/Scripts/RevenueCatUI.asmdef b/RevenueCatUI/Editor/revenuecat.purchases-unity-ui.Editor.asmdef similarity index 100% rename from RevenueCatUI/Scripts/RevenueCatUI.asmdef rename to RevenueCatUI/Editor/revenuecat.purchases-unity-ui.Editor.asmdef diff --git a/RevenueCatUI/Scripts/RevenueCatUI.asmdef.meta b/RevenueCatUI/Editor/revenuecat.purchases-unity-ui.Editor.asmdef.meta similarity index 100% rename from RevenueCatUI/Scripts/RevenueCatUI.asmdef.meta rename to RevenueCatUI/Editor/revenuecat.purchases-unity-ui.Editor.asmdef.meta diff --git a/RevenueCat/Plugins/Android/AndroidManifest.xml b/RevenueCatUI/Plugins/Android/AndroidManifest.xml similarity index 100% rename from RevenueCat/Plugins/Android/AndroidManifest.xml rename to RevenueCatUI/Plugins/Android/AndroidManifest.xml diff --git a/RevenueCat/Plugins/Android/AndroidManifest.xml.meta b/RevenueCatUI/Plugins/Android/AndroidManifest.xml.meta similarity index 100% rename from RevenueCat/Plugins/Android/AndroidManifest.xml.meta rename to RevenueCatUI/Plugins/Android/AndroidManifest.xml.meta diff --git a/RevenueCat/Plugins/Android/PaywallProxyActivity.java b/RevenueCatUI/Plugins/Android/PaywallProxyActivity.java similarity index 100% rename from RevenueCat/Plugins/Android/PaywallProxyActivity.java rename to RevenueCatUI/Plugins/Android/PaywallProxyActivity.java diff --git a/RevenueCat/Plugins/Android/PaywallProxyActivity.java.meta b/RevenueCatUI/Plugins/Android/PaywallProxyActivity.java.meta similarity index 100% rename from RevenueCat/Plugins/Android/PaywallProxyActivity.java.meta rename to RevenueCatUI/Plugins/Android/PaywallProxyActivity.java.meta diff --git a/RevenueCatUI/Plugins/Android/RevenueCatUI.java b/RevenueCatUI/Plugins/Android/RevenueCatUI.java index 5050c444..43d777da 100644 --- a/RevenueCatUI/Plugins/Android/RevenueCatUI.java +++ b/RevenueCatUI/Plugins/Android/RevenueCatUI.java @@ -1,6 +1,9 @@ package com.revenuecat.unity.ui; public class RevenueCatUI { + + private static final String PAYWALL_EVENT = "_paywallEvent"; + private static final String PAYWALL_RESULT = "_paywallResult"; // Java-side callbacks registered from C#; no UnitySendMessage fallback. public interface PaywallCallbacks { void onPaywallResult(String result); } @@ -10,12 +13,26 @@ public interface PaywallCallbacks { void onPaywallResult(String result); } public static void registerPaywallCallbacks(PaywallCallbacks cb) { paywallCallbacks = cb; } public static void unregisterPaywallCallbacks() { paywallCallbacks = null; } - public static void presentPaywall(String offeringIdentifier) { - android.util.Log.d("RevenueCatUI", "presentPaywall(offering=" + offeringIdentifier + ")"); + android.app.Activity activity = UnityPlayer.currentActivity; sendPaywallResult("CANCELLED|Stub: no native UI"); } + public static void presentPaywallForResult(@Nullable String offeringIdentifier) { + try { + android.app.Activity activity = UnityPlayer.currentActivity; + Intent intent = new Intent(activity, com.revenuecat.purchasesunity.PaywallProxyActivity.class); + intent.putExtra(com.revenuecat.purchasesunity.PaywallProxyActivity.EXTRA_GAME_OBJECT, gameObject); + intent.putExtra(com.revenuecat.purchasesunity.PaywallProxyActivity.EXTRA_METHOD, PAYWALL_RESULT); + if (offeringIdentifier != null) { + intent.putExtra(com.revenuecat.purchasesunity.PaywallProxyActivity.EXTRA_OFFERING_ID, offeringIdentifier); + } + activity.startActivity(intent); + } catch (Throwable t) { + Log.e("Purchases", "Error launching PaywallProxyActivity", t); + } + } + public static void presentPaywallIfNeeded(String requiredEntitlementIdentifier, String offeringIdentifier) { android.util.Log.d("RevenueCatUI", "presentPaywallIfNeeded(entitlement=" + requiredEntitlementIdentifier + ", offering=" + offeringIdentifier + ")"); sendPaywallResult("NOT_PRESENTED|Stub: no native UI"); diff --git a/RevenueCatUI/Plugins/Editor/RevenueCatUIDependencies.xml b/RevenueCatUI/Plugins/Editor/RevenueCatUIDependencies.xml new file mode 100644 index 00000000..f41d2613 --- /dev/null +++ b/RevenueCatUI/Plugins/Editor/RevenueCatUIDependencies.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/RevenueCatUI/Plugins/iOS/RevenueCatUI.m b/RevenueCatUI/Plugins/iOS/RevenueCatUI.m deleted file mode 100644 index 5827e657..00000000 --- a/RevenueCatUI/Plugins/iOS/RevenueCatUI.m +++ /dev/null @@ -1,31 +0,0 @@ -#import - -// Minimal native stubs for iOS bridging - -typedef void (*RCUIPaywallResultCallback)(const char* result); - -void rcui_presentPaywall(const char* offeringIdentifier, bool displayCloseButton, RCUIPaywallResultCallback callback) { - NSLog(@"[RevenueCatUI][iOS] presentPaywall(offering=%@, closeButton=%@)", - offeringIdentifier ? [NSString stringWithUTF8String:offeringIdentifier] : @"", - displayCloseButton ? @"true" : @"false"); - if (callback) { - callback("CANCELLED|Stub: no native UI"); - } -} - -void rcui_presentPaywallIfNeeded(const char* requiredEntitlementIdentifier, const char* offeringIdentifier, bool displayCloseButton, RCUIPaywallResultCallback callback) { - NSLog(@"[RevenueCatUI][iOS] presentPaywallIfNeeded(entitlement=%@, offering=%@, closeButton=%@)", - requiredEntitlementIdentifier ? [NSString stringWithUTF8String:requiredEntitlementIdentifier] : @"", - offeringIdentifier ? [NSString stringWithUTF8String:offeringIdentifier] : @"", - displayCloseButton ? @"true" : @"false"); - if (callback) { - callback("NOT_PRESENTED|Stub: no native UI"); - } -} - -// No Customer Center in this stub - -bool rcui_isSupported() { - NSLog(@"[RevenueCatUI][iOS] isSupported() -> true (stub)"); - return true; -} diff --git a/RevenueCatUI/Plugins/iOS/RevenueCatUI.m.meta b/RevenueCatUI/Plugins/iOS/RevenueCatUI.m.meta deleted file mode 100644 index 599306e3..00000000 --- a/RevenueCatUI/Plugins/iOS/RevenueCatUI.m.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 6f596540f84544a1582c80e4f69a709f \ No newline at end of file diff --git a/RevenueCatUI/Scripts/Platforms/iOS.meta b/RevenueCatUI/Scripts/Platforms/iOS.meta deleted file mode 100644 index 6b39c977..00000000 --- a/RevenueCatUI/Scripts/Platforms/iOS.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 49452c74706dd4f08a6c9af8de46966e -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/RevenueCatUI/Scripts/Platforms/iOS/IOSPaywallPresenter.cs b/RevenueCatUI/Scripts/Platforms/iOS/IOSPaywallPresenter.cs deleted file mode 100644 index c2694649..00000000 --- a/RevenueCatUI/Scripts/Platforms/iOS/IOSPaywallPresenter.cs +++ /dev/null @@ -1,88 +0,0 @@ -#if UNITY_IOS && !UNITY_EDITOR -using System; -using System.Runtime.InteropServices; -using System.Threading.Tasks; - -namespace RevenueCat.UI.Platforms -{ - internal class IOSPaywallPresenter : IPaywallPresenter - { - private delegate void PaywallResultCallback(string result); - - [DllImport("__Internal")] private static extern void rcui_presentPaywall(string offeringIdentifier, bool displayCloseButton, PaywallResultCallback cb); - [DllImport("__Internal")] private static extern void rcui_presentPaywallIfNeeded(string requiredEntitlementIdentifier, string offeringIdentifier, bool displayCloseButton, PaywallResultCallback cb); - [DllImport("__Internal")] private static extern bool rcui_isSupported(); - - private static TaskCompletionSource s_current; - - public bool IsSupported() => rcui_isSupported(); - - public Task PresentPaywallAsync(PaywallOptions options) - { - if (s_current != null && !s_current.Task.IsCompleted) - { - UnityEngine.Debug.LogWarning("[RevenueCatUI][iOS] Paywall presentation already in progress; rejecting new request."); - return Task.FromResult(PaywallResult.Error); - } - - var tcs = new TaskCompletionSource(); - s_current = tcs; - try - { - rcui_presentPaywall(options?.OfferingIdentifier, options?.DisplayCloseButton ?? false, OnResult); - } - catch (Exception e) - { - UnityEngine.Debug.LogError($"[RevenueCatUI][iOS] Exception in presentPaywall: {e.Message}"); - tcs.TrySetResult(PaywallResult.Error); - s_current = null; - } - return tcs.Task; - } - - public Task PresentPaywallIfNeededAsync(string requiredEntitlementIdentifier, PaywallOptions options) - { - if (s_current != null && !s_current.Task.IsCompleted) - { - UnityEngine.Debug.LogWarning("[RevenueCatUI][iOS] Paywall presentation already in progress; rejecting new request."); - return Task.FromResult(PaywallResult.Error); - } - - var tcs = new TaskCompletionSource(); - s_current = tcs; - try - { - rcui_presentPaywallIfNeeded(requiredEntitlementIdentifier, options?.OfferingIdentifier, options?.DisplayCloseButton ?? false, OnResult); - } - catch (Exception e) - { - UnityEngine.Debug.LogError($"[RevenueCatUI][iOS] Exception in presentPaywallIfNeeded: {e.Message}"); - tcs.TrySetResult(PaywallResult.Error); - s_current = null; - } - return tcs.Task; - } - - [AOT.MonoPInvokeCallback(typeof(PaywallResultCallback))] - private static void OnResult(string result) - { - try - { - var token = (result ?? "ERROR"); - var native = token.Split('|')[0]; - var type = PaywallResultTypeExtensions.FromNativeString(native); - s_current?.TrySetResult(new PaywallResult(type)); - } - catch (Exception e) - { - UnityEngine.Debug.LogError($"[RevenueCatUI][iOS] Failed to handle paywall result '{result}': {e.Message}. Setting Error."); - s_current?.TrySetResult(PaywallResult.Error); - } - finally - { - s_current = null; - } - } - } -} -#endif diff --git a/RevenueCatUI/Scripts/Platforms/iOS/IOSPaywallPresenter.cs.meta b/RevenueCatUI/Scripts/Platforms/iOS/IOSPaywallPresenter.cs.meta deleted file mode 100644 index 58401443..00000000 --- a/RevenueCatUI/Scripts/Platforms/iOS/IOSPaywallPresenter.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 3f43077ff755543a5b17a0b90808573d - From b9bb1e0fb1b63bb4e4cbee8c3d8ed3c1b488ea64 Mon Sep 17 00:00:00 2001 From: Cesar de la Vega <664544+vegaro@users.noreply.github.com> Date: Wed, 17 Sep 2025 18:48:48 +0200 Subject: [PATCH 32/65] clean up --- .../Plugins/Android/RevenueCatUI.java | 5 ----- RevenueCatUI/Scripts/RevenueCatUI.asmdef | 20 ------------------- RevenueCatUI/Scripts/RevenueCatUI.asmdef.meta | 8 -------- 3 files changed, 33 deletions(-) delete mode 100644 RevenueCatUI/Scripts/RevenueCatUI.asmdef delete mode 100644 RevenueCatUI/Scripts/RevenueCatUI.asmdef.meta diff --git a/RevenueCatUI/Plugins/Android/RevenueCatUI.java b/RevenueCatUI/Plugins/Android/RevenueCatUI.java index 43d777da..c8ab0a4f 100644 --- a/RevenueCatUI/Plugins/Android/RevenueCatUI.java +++ b/RevenueCatUI/Plugins/Android/RevenueCatUI.java @@ -14,11 +14,6 @@ public interface PaywallCallbacks { void onPaywallResult(String result); } public static void unregisterPaywallCallbacks() { paywallCallbacks = null; } public static void presentPaywall(String offeringIdentifier) { - android.app.Activity activity = UnityPlayer.currentActivity; - sendPaywallResult("CANCELLED|Stub: no native UI"); - } - - public static void presentPaywallForResult(@Nullable String offeringIdentifier) { try { android.app.Activity activity = UnityPlayer.currentActivity; Intent intent = new Intent(activity, com.revenuecat.purchasesunity.PaywallProxyActivity.class); diff --git a/RevenueCatUI/Scripts/RevenueCatUI.asmdef b/RevenueCatUI/Scripts/RevenueCatUI.asmdef deleted file mode 100644 index 103e8cce..00000000 --- a/RevenueCatUI/Scripts/RevenueCatUI.asmdef +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "RevenueCatUI", - "rootNamespace": "RevenueCat.UI", - "references": [ - "revenuecat.purchases-unity" - ], - "includePlatforms": [ - "Editor", - "Android", - "iOS" - ], - "excludePlatforms": [], - "allowUnsafeCode": false, - "overrideReferences": false, - "precompiledReferences": [], - "autoReferenced": true, - "defineConstraints": [], - "versionDefines": [], - "noEngineReferences": false -} diff --git a/RevenueCatUI/Scripts/RevenueCatUI.asmdef.meta b/RevenueCatUI/Scripts/RevenueCatUI.asmdef.meta deleted file mode 100644 index c4d3572f..00000000 --- a/RevenueCatUI/Scripts/RevenueCatUI.asmdef.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 538af592d6f3141c99c5b816a379fb80 -AssemblyDefinitionImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: - From b048332b83af6ed3c3a5177162a1f9a654abb77c Mon Sep 17 00:00:00 2001 From: Cesar de la Vega <664544+vegaro@users.noreply.github.com> Date: Mon, 22 Sep 2025 08:11:29 +0200 Subject: [PATCH 33/65] clean up --- .../Editor/RevenueCatUI.Editor.asmdef | 19 ------------------- ...ecat.purchases-unity-ui.Editor.asmdef.meta | 8 -------- RevenueCatUI/{ => Plugins}/Editor.meta | 3 +-- .../Editor/RevenueCatUIDependencies.xml.meta} | 2 +- .../revenuecat.purchases-unity-ui.asmdef} | 10 ++++------ ...revenuecat.purchases-unity-ui.asmdef.meta} | 3 +-- 6 files changed, 7 insertions(+), 38 deletions(-) delete mode 100644 RevenueCatUI/Editor/RevenueCatUI.Editor.asmdef delete mode 100644 RevenueCatUI/Editor/revenuecat.purchases-unity-ui.Editor.asmdef.meta rename RevenueCatUI/{ => Plugins}/Editor.meta (76%) rename RevenueCatUI/{README.md.meta => Plugins/Editor/RevenueCatUIDependencies.xml.meta} (75%) rename RevenueCatUI/{Editor/revenuecat.purchases-unity-ui.Editor.asmdef => Scripts/revenuecat.purchases-unity-ui.asmdef} (75%) rename RevenueCatUI/{Editor/RevenueCatUI.Editor.asmdef.meta => Scripts/revenuecat.purchases-unity-ui.asmdef.meta} (76%) diff --git a/RevenueCatUI/Editor/RevenueCatUI.Editor.asmdef b/RevenueCatUI/Editor/RevenueCatUI.Editor.asmdef deleted file mode 100644 index ccb71242..00000000 --- a/RevenueCatUI/Editor/RevenueCatUI.Editor.asmdef +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "RevenueCatUI.Editor", - "rootNamespace": "RevenueCat.UI", - "references": [ - "RevenueCatUI" - ], - "includePlatforms": [ - "Editor" - ], - "excludePlatforms": [], - "allowUnsafeCode": false, - "overrideReferences": false, - "precompiledReferences": [], - "autoReferenced": true, - "defineConstraints": [], - "versionDefines": [], - "noEngineReferences": false -} - diff --git a/RevenueCatUI/Editor/revenuecat.purchases-unity-ui.Editor.asmdef.meta b/RevenueCatUI/Editor/revenuecat.purchases-unity-ui.Editor.asmdef.meta deleted file mode 100644 index c4d3572f..00000000 --- a/RevenueCatUI/Editor/revenuecat.purchases-unity-ui.Editor.asmdef.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 538af592d6f3141c99c5b816a379fb80 -AssemblyDefinitionImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: - diff --git a/RevenueCatUI/Editor.meta b/RevenueCatUI/Plugins/Editor.meta similarity index 76% rename from RevenueCatUI/Editor.meta rename to RevenueCatUI/Plugins/Editor.meta index ac146dcb..8d81d3d4 100644 --- a/RevenueCatUI/Editor.meta +++ b/RevenueCatUI/Plugins/Editor.meta @@ -1,9 +1,8 @@ fileFormatVersion: 2 -guid: 3a90b2d732664b2b8c4640a9b65cfe21 +guid: b15cc059b5b7042a89e4559144a97d80 folderAsset: yes DefaultImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: - diff --git a/RevenueCatUI/README.md.meta b/RevenueCatUI/Plugins/Editor/RevenueCatUIDependencies.xml.meta similarity index 75% rename from RevenueCatUI/README.md.meta rename to RevenueCatUI/Plugins/Editor/RevenueCatUIDependencies.xml.meta index b033aee6..69c57db1 100644 --- a/RevenueCatUI/README.md.meta +++ b/RevenueCatUI/Plugins/Editor/RevenueCatUIDependencies.xml.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 4181f8939ea324c7f8014d9ec9d6a417 +guid: efd8c8f1bf9ea40b2aead1f4904cf88f TextScriptImporter: externalObjects: {} userData: diff --git a/RevenueCatUI/Editor/revenuecat.purchases-unity-ui.Editor.asmdef b/RevenueCatUI/Scripts/revenuecat.purchases-unity-ui.asmdef similarity index 75% rename from RevenueCatUI/Editor/revenuecat.purchases-unity-ui.Editor.asmdef rename to RevenueCatUI/Scripts/revenuecat.purchases-unity-ui.asmdef index 103e8cce..40602dde 100644 --- a/RevenueCatUI/Editor/revenuecat.purchases-unity-ui.Editor.asmdef +++ b/RevenueCatUI/Scripts/revenuecat.purchases-unity-ui.asmdef @@ -1,14 +1,10 @@ { - "name": "RevenueCatUI", + "name": "revenuecat.purchases-unity-ui", "rootNamespace": "RevenueCat.UI", "references": [ "revenuecat.purchases-unity" ], - "includePlatforms": [ - "Editor", - "Android", - "iOS" - ], + "includePlatforms": [], "excludePlatforms": [], "allowUnsafeCode": false, "overrideReferences": false, @@ -18,3 +14,5 @@ "versionDefines": [], "noEngineReferences": false } + + diff --git a/RevenueCatUI/Editor/RevenueCatUI.Editor.asmdef.meta b/RevenueCatUI/Scripts/revenuecat.purchases-unity-ui.asmdef.meta similarity index 76% rename from RevenueCatUI/Editor/RevenueCatUI.Editor.asmdef.meta rename to RevenueCatUI/Scripts/revenuecat.purchases-unity-ui.asmdef.meta index 31499d52..1f80601f 100644 --- a/RevenueCatUI/Editor/RevenueCatUI.Editor.asmdef.meta +++ b/RevenueCatUI/Scripts/revenuecat.purchases-unity-ui.asmdef.meta @@ -1,8 +1,7 @@ fileFormatVersion: 2 -guid: e6c6f27f8e4c4f6e9e0d8a9a8f1b2c3d +guid: a5a908ddb20be478ea0c2bcafb5eb6c9 AssemblyDefinitionImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: - From cfbc8d9fd913f256df181e005c76c52613313614 Mon Sep 17 00:00:00 2001 From: Cesar de la Vega <664544+vegaro@users.noreply.github.com> Date: Mon, 22 Sep 2025 08:13:56 +0200 Subject: [PATCH 34/65] move into androidlib --- .../Plugins/Android/AndroidManifest.xml.meta | 7 -- .../Plugins/Android/PaywallProxyActivity.java | 66 ----------------- .../Android/PaywallProxyActivity.java.meta | 32 -------- .../Android/RevenueCatUI.androidlib.meta | 61 +++++++++++++++ .../AndroidManifest.xml | 2 +- .../RevenueCatUI.androidlib/build.gradle | 49 ++++++++++++ .../proguard-android-optimize.txt | 1 + .../ui/PaywallProxyActivity.java | 74 +++++++++++++++++++ .../purchasesunity/ui/RevenueCatUI.java | 67 +++++++++++++++++ .../purchasesunity/ui/UnityBridge.java | 28 +++++++ .../Plugins/Android/RevenueCatUI.java | 50 ------------- .../Plugins/Android/RevenueCatUI.java.meta | 33 --------- RevenueCatUI/Scripts/IPaywallPresenter.cs | 6 +- RevenueCatUI/Scripts/PaywallPresenter.cs | 4 +- .../Android/AndroidPaywallPresenter.cs | 15 ++-- .../Platforms/iOS/IOSPaywallPresenter.cs | 4 +- RevenueCatUI/Scripts/RevenueCatUI.cs | 35 +++++---- 17 files changed, 316 insertions(+), 218 deletions(-) delete mode 100644 RevenueCatUI/Plugins/Android/AndroidManifest.xml.meta delete mode 100644 RevenueCatUI/Plugins/Android/PaywallProxyActivity.java delete mode 100644 RevenueCatUI/Plugins/Android/PaywallProxyActivity.java.meta create mode 100644 RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib.meta rename RevenueCatUI/Plugins/Android/{ => RevenueCatUI.androidlib}/AndroidManifest.xml (84%) create mode 100644 RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/build.gradle create mode 100644 RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/proguard-android-optimize.txt create mode 100644 RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/src/main/java/com/revenuecat/purchasesunity/ui/PaywallProxyActivity.java create mode 100644 RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/src/main/java/com/revenuecat/purchasesunity/ui/RevenueCatUI.java create mode 100644 RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/src/main/java/com/revenuecat/purchasesunity/ui/UnityBridge.java delete mode 100644 RevenueCatUI/Plugins/Android/RevenueCatUI.java delete mode 100644 RevenueCatUI/Plugins/Android/RevenueCatUI.java.meta diff --git a/RevenueCatUI/Plugins/Android/AndroidManifest.xml.meta b/RevenueCatUI/Plugins/Android/AndroidManifest.xml.meta deleted file mode 100644 index 65dcbdb4..00000000 --- a/RevenueCatUI/Plugins/Android/AndroidManifest.xml.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 49b506501dcda465182d421d18936d8c -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/RevenueCatUI/Plugins/Android/PaywallProxyActivity.java b/RevenueCatUI/Plugins/Android/PaywallProxyActivity.java deleted file mode 100644 index 2f014de8..00000000 --- a/RevenueCatUI/Plugins/Android/PaywallProxyActivity.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.revenuecat.purchasesunity; - -import android.content.Intent; -import android.os.Bundle; -import android.util.Log; - -import androidx.annotation.Nullable; -import androidx.appcompat.app.AppCompatActivity; - -import com.revenuecat.purchases.ui.revenuecatui.activity.PaywallActivityLauncher; -import com.revenuecat.purchases.ui.revenuecatui.activity.PaywallResult; -import com.revenuecat.purchases.ui.revenuecatui.activity.PaywallResultHandler; -import com.unity3d.player.UnityPlayer; - -public class PaywallProxyActivity extends AppCompatActivity { - static final String EXTRA_GAME_OBJECT = "rc_proxy_game_object"; - static final String EXTRA_METHOD = "rc_proxy_method"; - static final String EXTRA_OFFERING_ID = "rc_offering_id"; - - private String gameObject; - private String method; - - @Override - protected void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - Intent source = getIntent(); - gameObject = source.getStringExtra(EXTRA_GAME_OBJECT); - method = source.getStringExtra(EXTRA_METHOD); - - PaywallActivityLauncher launcher = new PaywallActivityLauncher(this, result -> { - try { - if (result != null) { - sendPaywallResult(result); - } - } finally { - finish(); - } - }); - - Log.d("PurchasesUnity", "Launching paywall with PaywallActivityLauncher"); - launcher.launch(null); - } - - private void sendPaywallResult(PaywallResult result) { - if (gameObject == null || method == null) return; - - String resultName; - if (result instanceof PaywallResult.Purchased) { - resultName = "purchased"; - } else if (result instanceof PaywallResult.Restored) { - resultName = "restored"; - } else if (result instanceof PaywallResult.Cancelled) { - resultName = "cancelled"; - } else if (result instanceof PaywallResult.Error) { - resultName = "error"; - } else { - resultName = "cancelled"; // fallback - } - - Log.d("PurchasesUnity", "Sending PaywallResult: " + resultName); - UnityPlayer.UnitySendMessage(gameObject, method, resultName); - } -} - - diff --git a/RevenueCatUI/Plugins/Android/PaywallProxyActivity.java.meta b/RevenueCatUI/Plugins/Android/PaywallProxyActivity.java.meta deleted file mode 100644 index 4e692f82..00000000 --- a/RevenueCatUI/Plugins/Android/PaywallProxyActivity.java.meta +++ /dev/null @@ -1,32 +0,0 @@ -fileFormatVersion: 2 -guid: 3ac2d40cd9cdc4bfb8f95372e2b12f72 -PluginImporter: - externalObjects: {} - serializedVersion: 2 - iconMap: {} - executionOrder: {} - defineConstraints: [] - isPreloaded: 0 - isOverridable: 1 - isExplicitlyReferenced: 0 - validateReferences: 1 - platformData: - - first: - Android: Android - second: - enabled: 1 - settings: {} - - first: - Any: - second: - enabled: 0 - settings: {} - - first: - Editor: Editor - second: - enabled: 0 - settings: - DefaultValueInitialized: true - userData: - assetBundleName: - assetBundleVariant: diff --git a/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib.meta b/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib.meta new file mode 100644 index 00000000..2a3b1281 --- /dev/null +++ b/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib.meta @@ -0,0 +1,61 @@ +fileFormatVersion: 2 +guid: 1792fdc2d528e45f89941d54e8fd9c7f +PluginImporter: + externalObjects: {} + serializedVersion: 3 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 1 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + Android: + enabled: 1 + settings: + AndroidLibraryDependee: UnityLibrary + AndroidSharedLibraryType: Executable + CPU: ARMv7 + Any: + enabled: 1 + settings: + Exclude Android: 0 + Exclude Editor: 0 + Exclude Linux64: 0 + Exclude OSXUniversal: 0 + Exclude Win: 0 + Exclude Win64: 0 + Exclude iOS: 0 + Editor: + enabled: 1 + settings: + CPU: AnyCPU + DefaultValueInitialized: true + OS: AnyOS + Linux64: + enabled: 1 + settings: + CPU: x86_64 + OSXUniversal: + enabled: 1 + settings: + CPU: None + Win: + enabled: 1 + settings: + CPU: x86 + Win64: + enabled: 1 + settings: + CPU: None + iOS: + enabled: 1 + settings: + AddToEmbeddedBinaries: false + CPU: AnyCPU + CompileFlags: + FrameworkDependencies: + userData: + assetBundleName: + assetBundleVariant: diff --git a/RevenueCatUI/Plugins/Android/AndroidManifest.xml b/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/AndroidManifest.xml similarity index 84% rename from RevenueCatUI/Plugins/Android/AndroidManifest.xml rename to RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/AndroidManifest.xml index 068d8dcf..de6a0905 100644 --- a/RevenueCatUI/Plugins/Android/AndroidManifest.xml +++ b/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/AndroidManifest.xml @@ -4,7 +4,7 @@ diff --git a/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/build.gradle b/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/build.gradle new file mode 100644 index 00000000..33e5eaba --- /dev/null +++ b/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/build.gradle @@ -0,0 +1,49 @@ +apply plugin: 'com.android.library' + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation 'androidx.appcompat:appcompat:1.6.1' + implementation 'com.revenuecat.purchases:purchases-hybrid-common:17.7.0' + implementation 'com.revenuecat.purchases:purchases-hybrid-common-ui:17.7.0' +} + +android { + namespace 'com.revenuecat.purchasesunity.ui' + + compileSdk getProperty("unity.compileSdkVersion") as int + buildToolsVersion = getProperty("unity.buildToolsVersion") + + compileOptions { + sourceCompatibility JavaVersion.valueOf(getProperty("unity.javaCompatabilityVersion")) + targetCompatibility JavaVersion.valueOf(getProperty("unity.javaCompatabilityVersion")) + } + + sourceSets { + main { + manifest.srcFile 'AndroidManifest.xml' + java.srcDirs = ['src/main/java'] + res.srcDirs = ['src/main/res'] + assets.srcDirs = ['src/main/assets'] + jniLibs.srcDirs = ['src/main/jniLibs'] + } + } + + def unityLib = project(':unityLibrary').extensions.getByName('android') + + defaultConfig { + consumerProguardFiles "consumer-proguard.pro" + minSdkVersion unityLib.defaultConfig.minSdkVersion.mApiLevel + targetSdkVersion unityLib.defaultConfig.targetSdkVersion.mApiLevel + } + + lintOptions { + abortOnError false + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules. pro' + } + } +} \ No newline at end of file diff --git a/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/proguard-android-optimize.txt b/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/proguard-android-optimize.txt new file mode 100644 index 00000000..69ab6d92 --- /dev/null +++ b/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/proguard-android-optimize.txt @@ -0,0 +1 @@ +-keep class com.unity3d.player.** { *; } diff --git a/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/src/main/java/com/revenuecat/purchasesunity/ui/PaywallProxyActivity.java b/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/src/main/java/com/revenuecat/purchasesunity/ui/PaywallProxyActivity.java new file mode 100644 index 00000000..dd79d03a --- /dev/null +++ b/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/src/main/java/com/revenuecat/purchasesunity/ui/PaywallProxyActivity.java @@ -0,0 +1,74 @@ +package com.revenuecat.purchasesunity.ui; + +import android.content.Intent; +import android.os.Bundle; +import android.util.Log; + +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; + +import com.revenuecat.purchases.ui.revenuecatui.activity.PaywallActivityLauncher; +import com.revenuecat.purchases.ui.revenuecatui.activity.PaywallResult; + +public class PaywallProxyActivity extends AppCompatActivity { + public static final String EXTRA_GAME_OBJECT = "rc_proxy_game_object"; + public static final String EXTRA_METHOD = "rc_proxy_method"; + public static final String EXTRA_OFFERING_ID = "rc_offering_id"; + + private static final String TAG = "PurchasesUnity"; + + private String gameObject; + private String method; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + final Intent source = getIntent(); + gameObject = source.getStringExtra(EXTRA_GAME_OBJECT); + method = source.getStringExtra(EXTRA_METHOD); + + if (gameObject == null || method == null) { + Log.w(TAG, "Missing gameObject/method extras; finishing."); + finish(); + return; + } + + PaywallActivityLauncher launcher = new PaywallActivityLauncher( + this, + result -> { + try { + if (result != null) { + sendPaywallResult(result); + } + } finally { + finish(); + } + } + ); + + Log.d(TAG, "Launching paywall with PaywallActivityLauncher"); + // If you ever need to pass an offering, wire it via intent extras and into the launcher + launcher.launch(null); + } + + private void sendPaywallResult(PaywallResult result) { + final String resultName; + if (result instanceof PaywallResult.Purchased) { + resultName = "purchased"; + } else if (result instanceof PaywallResult.Restored) { + resultName = "restored"; + } else if (result instanceof PaywallResult.Cancelled) { + resultName = "cancelled"; + } else if (result instanceof PaywallResult.Error) { + resultName = "error"; + } else { + resultName = "cancelled"; + } + + Log.d(TAG, "Sending PaywallResult: " + resultName); + + // UnitySendMessage is safest from the UI thread. We're already on it, but guard anyway. + runOnUiThread(() -> UnityBridge.sendMessage(gameObject, method, resultName)); + } +} \ No newline at end of file diff --git a/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/src/main/java/com/revenuecat/purchasesunity/ui/RevenueCatUI.java b/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/src/main/java/com/revenuecat/purchasesunity/ui/RevenueCatUI.java new file mode 100644 index 00000000..65e29f07 --- /dev/null +++ b/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/src/main/java/com/revenuecat/purchasesunity/ui/RevenueCatUI.java @@ -0,0 +1,67 @@ +package com.revenuecat.purchasesunity.ui; + +import static com.revenuecat.purchasesunity.ui.PaywallProxyActivity.EXTRA_GAME_OBJECT; +import static com.revenuecat.purchasesunity.ui.PaywallProxyActivity.EXTRA_METHOD; + +import android.app.Activity; +import android.content.Intent; +import android.util.Log; + +public class RevenueCatUI { + public interface PaywallCallbacks { void onPaywallResult(String result); } + + private static final String TAG = "RevenueCatUI"; + private static volatile PaywallCallbacks paywallCallbacks; + + public static void registerPaywallCallbacks(PaywallCallbacks cb) { paywallCallbacks = cb; } + public static void unregisterPaywallCallbacks() { paywallCallbacks = null; } + + public static void presentPaywall(String gameObject, String offeringIdentifier) { + try { + Activity activity = UnityBridge.currentActivityOrNull(); + if (activity == null) { + Log.e(TAG, "currentActivity is null; cannot launch paywall"); + UnityBridge.sendMessage(gameObject, "OnPaywallResultFromActivity", "ERROR|NoActivity"); + return; + } + + Intent intent = new Intent(activity, PaywallProxyActivity.class); + intent.putExtra(EXTRA_GAME_OBJECT, gameObject); + intent.putExtra(EXTRA_METHOD, "OnPaywallResultFromActivity"); + if (offeringIdentifier != null) { + intent.putExtra(PaywallProxyActivity.EXTRA_OFFERING_ID, offeringIdentifier); + } + + Log.d(TAG, "Launching PaywallProxyActivity for gameObject=" + gameObject + + ", offering=" + offeringIdentifier); + activity.startActivity(intent); + } catch (Throwable t) { + Log.e(TAG, "Error launching PaywallProxyActivity", t); + UnityBridge.sendMessage(gameObject, "OnPaywallResultFromActivity", "ERROR|" + t.getClass().getSimpleName()); + } + } + + public static void presentPaywallIfNeeded(String gameObject, String requiredEntitlementIdentifier, String offeringIdentifier) { + Log.d(TAG, "presentPaywallIfNeeded(go=" + gameObject + ", entitlement=" + + requiredEntitlementIdentifier + ", offering=" + offeringIdentifier + ")"); + // Stubbed behavior (adjust when you add native logic) + UnityBridge.sendMessage(gameObject, "OnPaywallResultFromActivity", "NOT_PRESENTED|Stub: no native UI"); + } + + public static boolean isSupported() { return true; } + + // Keeps your callback path intact if you use it internally (not used by SendMessage flow) + private static void sendPaywallResult(String result) { + try { + PaywallCallbacks cb = paywallCallbacks; + if (cb != null) { + Log.d(TAG, "Forwarding result to registered callback: " + result); + cb.onPaywallResult(result); + } else { + Log.w(TAG, "No callback registered to receive paywall result: " + result); + } + } catch (Throwable e) { + Log.e(TAG, "Error sending paywall result: " + e.getMessage()); + } + } +} \ No newline at end of file diff --git a/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/src/main/java/com/revenuecat/purchasesunity/ui/UnityBridge.java b/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/src/main/java/com/revenuecat/purchasesunity/ui/UnityBridge.java new file mode 100644 index 00000000..6f52099f --- /dev/null +++ b/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/src/main/java/com/revenuecat/purchasesunity/ui/UnityBridge.java @@ -0,0 +1,28 @@ +package com.revenuecat.purchasesunity.ui; + +import android.app.Activity; +import android.util.Log; + +final class UnityBridge { + private static final String TAG = "UnityBridge"; + private UnityBridge() {} + + static Activity currentActivityOrNull() { + try { + Class up = Class.forName("com.unity3d.player.UnityPlayer"); + return (Activity) up.getField("currentActivity").get(null); + } catch (Throwable t) { + return null; + } + } + + static void sendMessage(String gameObject, String method, String message) { + try { + Class up = Class.forName("com.unity3d.player.UnityPlayer"); + up.getMethod("UnitySendMessage", String.class, String.class, String.class) + .invoke(null, gameObject, method, message); + } catch (Throwable t) { + Log.w(TAG, "UnitySendMessage failed (" + gameObject + "." + method + "): " + message, t); + } + } +} \ No newline at end of file diff --git a/RevenueCatUI/Plugins/Android/RevenueCatUI.java b/RevenueCatUI/Plugins/Android/RevenueCatUI.java deleted file mode 100644 index c8ab0a4f..00000000 --- a/RevenueCatUI/Plugins/Android/RevenueCatUI.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.revenuecat.unity.ui; - -public class RevenueCatUI { - - private static final String PAYWALL_EVENT = "_paywallEvent"; - private static final String PAYWALL_RESULT = "_paywallResult"; - - // Java-side callbacks registered from C#; no UnitySendMessage fallback. - public interface PaywallCallbacks { void onPaywallResult(String result); } - - private static volatile PaywallCallbacks paywallCallbacks; - - public static void registerPaywallCallbacks(PaywallCallbacks cb) { paywallCallbacks = cb; } - public static void unregisterPaywallCallbacks() { paywallCallbacks = null; } - - public static void presentPaywall(String offeringIdentifier) { - try { - android.app.Activity activity = UnityPlayer.currentActivity; - Intent intent = new Intent(activity, com.revenuecat.purchasesunity.PaywallProxyActivity.class); - intent.putExtra(com.revenuecat.purchasesunity.PaywallProxyActivity.EXTRA_GAME_OBJECT, gameObject); - intent.putExtra(com.revenuecat.purchasesunity.PaywallProxyActivity.EXTRA_METHOD, PAYWALL_RESULT); - if (offeringIdentifier != null) { - intent.putExtra(com.revenuecat.purchasesunity.PaywallProxyActivity.EXTRA_OFFERING_ID, offeringIdentifier); - } - activity.startActivity(intent); - } catch (Throwable t) { - Log.e("Purchases", "Error launching PaywallProxyActivity", t); - } - } - - public static void presentPaywallIfNeeded(String requiredEntitlementIdentifier, String offeringIdentifier) { - android.util.Log.d("RevenueCatUI", "presentPaywallIfNeeded(entitlement=" + requiredEntitlementIdentifier + ", offering=" + offeringIdentifier + ")"); - sendPaywallResult("NOT_PRESENTED|Stub: no native UI"); - } - - // No Customer Center in this stub - - public static boolean isSupported() { - return true; - } - - private static void sendPaywallResult(String result) { - try { - PaywallCallbacks cb = paywallCallbacks; - if (cb != null) cb.onPaywallResult(result); - } catch (Throwable ignored) {} - } - - // No Customer Center in this stub -} diff --git a/RevenueCatUI/Plugins/Android/RevenueCatUI.java.meta b/RevenueCatUI/Plugins/Android/RevenueCatUI.java.meta deleted file mode 100644 index c5940f37..00000000 --- a/RevenueCatUI/Plugins/Android/RevenueCatUI.java.meta +++ /dev/null @@ -1,33 +0,0 @@ -fileFormatVersion: 2 -guid: 3c9d6f1b2a2d4f3c9b9a7c1d2e3f4a5b -PluginImporter: - externalObjects: {} - serializedVersion: 2 - iconMap: {} - executionOrder: {} - defineConstraints: [] - isPreloaded: 0 - isOverridable: 0 - isExplicitlyReferenced: 0 - validateReferences: 1 - platformData: - - first: - Android: Android - second: - enabled: 1 - settings: {} - - first: - Any: - second: - enabled: 0 - settings: {} - - first: - Editor: Editor - second: - enabled: 0 - settings: - DefaultValueInitialized: true - userData: - assetBundleName: - assetBundleVariant: - diff --git a/RevenueCatUI/Scripts/IPaywallPresenter.cs b/RevenueCatUI/Scripts/IPaywallPresenter.cs index 14bd8897..a43aba38 100644 --- a/RevenueCatUI/Scripts/IPaywallPresenter.cs +++ b/RevenueCatUI/Scripts/IPaywallPresenter.cs @@ -11,17 +11,19 @@ internal interface IPaywallPresenter /// /// Presents a paywall with the given options. /// + /// Name of GameObject to receive callbacks /// Paywall presentation options /// Result of the paywall presentation - Task PresentPaywallAsync(PaywallOptions options); + Task PresentPaywallAsync(string gameObjectName, PaywallOptions options); /// /// Presents a paywall only if the user does not have the specified entitlement. /// + /// Name of GameObject to receive callbacks /// Required entitlement identifier /// Paywall presentation options /// Result of the paywall presentation - Task PresentPaywallIfNeededAsync(string requiredEntitlementIdentifier, PaywallOptions options); + Task PresentPaywallIfNeededAsync(string gameObjectName, string requiredEntitlementIdentifier, PaywallOptions options); /// /// Whether paywall presentation is supported on this platform. diff --git a/RevenueCatUI/Scripts/PaywallPresenter.cs b/RevenueCatUI/Scripts/PaywallPresenter.cs index 6498a926..eb3cb68b 100644 --- a/RevenueCatUI/Scripts/PaywallPresenter.cs +++ b/RevenueCatUI/Scripts/PaywallPresenter.cs @@ -43,13 +43,13 @@ private static IPaywallPresenter CreatePlatformPresenter() /// internal class UnsupportedPaywallPresenter : IPaywallPresenter { - public Task PresentPaywallAsync(PaywallOptions options) + public Task PresentPaywallAsync(string gameObjectName, PaywallOptions options) { Debug.LogWarning("[RevenueCatUI] Paywall presentation is not supported on this platform."); return Task.FromResult(PaywallResult.Error); } - public Task PresentPaywallIfNeededAsync(string requiredEntitlementIdentifier, PaywallOptions options) + public Task PresentPaywallIfNeededAsync(string gameObjectName, string requiredEntitlementIdentifier, PaywallOptions options) { Debug.LogWarning("[RevenueCatUI] Paywall presentation is not supported on this platform."); return Task.FromResult(PaywallResult.Error); diff --git a/RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs b/RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs index a44f7680..03268917 100644 --- a/RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs +++ b/RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs @@ -2,7 +2,6 @@ using System; using System.Threading.Tasks; using UnityEngine; -// No Internal handler needed; Android uses direct callbacks via AndroidJavaProxy namespace RevenueCat.UI.Platforms { @@ -14,7 +13,7 @@ internal class AndroidPaywallPresenter : IPaywallPresenter public AndroidPaywallPresenter() { - _plugin = new AndroidJavaClass("com.revenuecat.unity.ui.RevenueCatUI"); + _plugin = new AndroidJavaClass("com.revenuecat.purchasesunity.ui.RevenueCatUI"); _callbacks = new CallbacksProxy(this); try { _plugin.CallStatic("registerPaywallCallbacks", _callbacks); } catch { /* ignore */ } } @@ -30,7 +29,7 @@ public bool IsSupported() catch { return false; } } - public Task PresentPaywallAsync(PaywallOptions options) + public Task PresentPaywallAsync(string gameObjectName, PaywallOptions options) { if (_current != null && !_current.Task.IsCompleted) { @@ -43,7 +42,7 @@ public Task PresentPaywallAsync(PaywallOptions options) { var offering = options?.OfferingIdentifier; Debug.Log($"[RevenueCatUI][Android] presentPaywall offering='{offering ?? ""}'"); - _plugin.CallStatic("presentPaywall", offering); + _plugin.CallStatic("presentPaywall", gameObjectName, offering); } catch (Exception e) { @@ -54,7 +53,7 @@ public Task PresentPaywallAsync(PaywallOptions options) return _current.Task; } - public Task PresentPaywallIfNeededAsync(string requiredEntitlementIdentifier, PaywallOptions options) + public Task PresentPaywallIfNeededAsync(string gameObjectName, string requiredEntitlementIdentifier, PaywallOptions options) { if (_current != null && !_current.Task.IsCompleted) { @@ -67,7 +66,7 @@ public Task PresentPaywallIfNeededAsync(string requiredEntitlemen { var offering = options?.OfferingIdentifier; Debug.Log($"[RevenueCatUI][Android] presentPaywallIfNeeded entitlement='{requiredEntitlementIdentifier}', offering='{offering ?? ""}'"); - _plugin.CallStatic("presentPaywallIfNeeded", requiredEntitlementIdentifier, offering); + _plugin.CallStatic("presentPaywallIfNeeded", gameObjectName, requiredEntitlementIdentifier, offering); } catch (Exception e) { @@ -78,7 +77,7 @@ public Task PresentPaywallIfNeededAsync(string requiredEntitlemen return _current.Task; } - // Called from Java via AndroidJavaProxy + // Called from RevenueCatUI MonoBehaviour or Java via AndroidJavaProxy public void OnPaywallResult(string resultData) { if (_current == null) return; @@ -102,7 +101,7 @@ public void OnPaywallResult(string resultData) private class CallbacksProxy : AndroidJavaProxy { private readonly AndroidPaywallPresenter _owner; - public CallbacksProxy(AndroidPaywallPresenter owner) : base("com.revenuecat.unity.ui.RevenueCatUI$PaywallCallbacks") + public CallbacksProxy(AndroidPaywallPresenter owner) : base("com.revenuecat.purchasesunity.ui.RevenueCatUI$PaywallCallbacks") { _owner = owner; } diff --git a/RevenueCatUI/Scripts/Platforms/iOS/IOSPaywallPresenter.cs b/RevenueCatUI/Scripts/Platforms/iOS/IOSPaywallPresenter.cs index c2694649..26b16802 100644 --- a/RevenueCatUI/Scripts/Platforms/iOS/IOSPaywallPresenter.cs +++ b/RevenueCatUI/Scripts/Platforms/iOS/IOSPaywallPresenter.cs @@ -17,7 +17,7 @@ internal class IOSPaywallPresenter : IPaywallPresenter public bool IsSupported() => rcui_isSupported(); - public Task PresentPaywallAsync(PaywallOptions options) + public Task PresentPaywallAsync(string gameObjectName, PaywallOptions options) { if (s_current != null && !s_current.Task.IsCompleted) { @@ -40,7 +40,7 @@ public Task PresentPaywallAsync(PaywallOptions options) return tcs.Task; } - public Task PresentPaywallIfNeededAsync(string requiredEntitlementIdentifier, PaywallOptions options) + public Task PresentPaywallIfNeededAsync(string gameObjectName, string requiredEntitlementIdentifier, PaywallOptions options) { if (s_current != null && !s_current.Task.IsCompleted) { diff --git a/RevenueCatUI/Scripts/RevenueCatUI.cs b/RevenueCatUI/Scripts/RevenueCatUI.cs index 730220e6..416d7ca5 100644 --- a/RevenueCatUI/Scripts/RevenueCatUI.cs +++ b/RevenueCatUI/Scripts/RevenueCatUI.cs @@ -7,8 +7,9 @@ namespace RevenueCat.UI /// /// Main interface for RevenueCat UI components. /// Provides methods to present paywalls. + /// Add this component to a GameObject to enable paywall functionality. /// - public static class RevenueCatUI + public class RevenueCatUI : MonoBehaviour { /// @@ -16,15 +17,14 @@ public static class RevenueCatUI /// /// Options for presenting the paywall /// A PaywallResult indicating what happened during the paywall presentation - public static async Task PresentPaywall(PaywallOptions options = null) + public async Task PresentPaywall(PaywallOptions options = null) { try { Debug.Log("[RevenueCatUI] Presenting paywall..."); - // Use the platform-specific implementation var presenter = PaywallPresenter.Instance; - return await presenter.PresentPaywallAsync(options ?? new PaywallOptions()); + return await presenter.PresentPaywallAsync(gameObject.name, options ?? new PaywallOptions()); } catch (Exception e) { @@ -39,7 +39,7 @@ public static async Task PresentPaywall(PaywallOptions options = /// Entitlement identifier to check before presenting /// Options for presenting the paywall /// A PaywallResult indicating what happened during the paywall presentation - public static async Task PresentPaywallIfNeeded( + public async Task PresentPaywallIfNeeded( string requiredEntitlementIdentifier, PaywallOptions options = null) { @@ -54,7 +54,7 @@ public static async Task PresentPaywallIfNeeded( Debug.Log($"[RevenueCatUI] Presenting paywall if needed for entitlement: {requiredEntitlementIdentifier}"); var presenter = PaywallPresenter.Instance; - return await presenter.PresentPaywallIfNeededAsync(requiredEntitlementIdentifier, options ?? new PaywallOptions()); + return await presenter.PresentPaywallIfNeededAsync(gameObject.name, requiredEntitlementIdentifier, options ?? new PaywallOptions()); } catch (Exception e) { @@ -71,7 +71,7 @@ public static async Task PresentPaywallIfNeeded( /// returns false on other platforms (Editor, Windows, macOS, WebGL, etc.). /// /// True if UI is supported on this platform, otherwise false. - public static bool IsSupported() + public bool IsSupported() { try { @@ -90,15 +90,20 @@ public static bool IsSupported() } } - /// - /// Whether the Paywall feature is supported on the current platform. - /// - public static bool IsPaywallSupported() + + // Called from PaywallProxyActivity via UnitySendMessage + private void OnPaywallResultFromActivity(string resultName) { - try { return PaywallPresenter.Instance.IsSupported(); } - catch { return false; } +#if UNITY_ANDROID && !UNITY_EDITOR + // Convert simple result name to format expected by AndroidPaywallPresenter + string formattedResult = resultName.ToUpper(); + + var presenter = PaywallPresenter.Instance; + if (presenter is Platforms.AndroidPaywallPresenter androidPresenter) + { + androidPresenter.OnPaywallResult(formattedResult); + } +#endif } - - } } From 1c4c865b344fe0c531520e9c6c9e0698f1ff58d8 Mon Sep 17 00:00:00 2001 From: Cesar de la Vega <664544+vegaro@users.noreply.github.com> Date: Mon, 22 Sep 2025 08:16:44 +0200 Subject: [PATCH 35/65] add local dependency --- Subtester/Packages/manifest.json | 1 + 1 file changed, 1 insertion(+) diff --git a/Subtester/Packages/manifest.json b/Subtester/Packages/manifest.json index ca00c4ea..876c7fd8 100644 --- a/Subtester/Packages/manifest.json +++ b/Subtester/Packages/manifest.json @@ -1,6 +1,7 @@ { "dependencies": { "com.revenuecat.purchases-unity": "file:../../RevenueCat", + "com.revenuecat.purchases-ui-unity": "file:../../RevenueCatUI", "com.unity.2d.sprite": "1.0.0", "com.unity.2d.tilemap": "1.0.0", "com.unity.ads": "4.4.2", From f072bdeba71dd9c3efe478059587ea45eacd390a Mon Sep 17 00:00:00 2001 From: Cesar de la Vega <664544+vegaro@users.noreply.github.com> Date: Mon, 22 Sep 2025 08:21:47 +0200 Subject: [PATCH 36/65] updated mainTemplate.gradle --- .../Assets/Plugins/Android/mainTemplate.gradle | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/Subtester/Assets/Plugins/Android/mainTemplate.gradle b/Subtester/Assets/Plugins/Android/mainTemplate.gradle index 2bb71282..afd5650c 100644 --- a/Subtester/Assets/Plugins/Android/mainTemplate.gradle +++ b/Subtester/Assets/Plugins/Android/mainTemplate.gradle @@ -1,16 +1,5 @@ // GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN -// Android Resolver Repos Start -([rootProject] + (rootProject.subprojects as List)).each { project -> - project.repositories { - maven { - url "https://maven.google.com" - } - mavenLocal() - mavenCentral() - } -} -// Android Resolver Repos End apply plugin: 'com.android.library' apply from: '../shared/keepUnitySymbols.gradle' **APPLY_PLUGINS** @@ -19,8 +8,9 @@ dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) // Android Resolver Dependencies Start implementation 'androidx.annotation:annotation:[1.2.0]' // Packages/com.revenuecat.purchases-unity/Plugins/Editor/RevenueCatDependencies.xml:5 - implementation 'androidx.appcompat:appcompat:[1.6.1]' // Packages/com.revenuecat.purchases-unity/Plugins/Editor/RevenueCatDependencies.xml:6 - implementation 'com.revenuecat.purchases:purchases-hybrid-common:[17.0.0]' // Packages/com.revenuecat.purchases-unity/Plugins/Editor/RevenueCatDependencies.xml:4 + implementation 'androidx.appcompat:appcompat:1.6.1' // Packages/com.revenuecat.purchases-ui-unity/Plugins/Editor/RevenueCatUIDependencies.xml:5 + implementation 'com.revenuecat.purchases:purchases-hybrid-common:[17.7.0]' // Packages/com.revenuecat.purchases-unity/Plugins/Editor/RevenueCatDependencies.xml:4 + implementation 'com.revenuecat.purchases:purchases-hybrid-common-ui:[17.7.0]' // Packages/com.revenuecat.purchases-ui-unity/Plugins/Editor/RevenueCatUIDependencies.xml:4 // Android Resolver Dependencies End **DEPS**} From 24577090bfa17203bef7161388904dc31726b548 Mon Sep 17 00:00:00 2001 From: Cesar de la Vega <664544+vegaro@users.noreply.github.com> Date: Mon, 22 Sep 2025 12:34:40 +0200 Subject: [PATCH 37/65] update PurchasesListener --- Subtester/Assets/Scripts/PurchasesListener.cs | 220 +++++++++++++----- 1 file changed, 168 insertions(+), 52 deletions(-) diff --git a/Subtester/Assets/Scripts/PurchasesListener.cs b/Subtester/Assets/Scripts/PurchasesListener.cs index 668ad26d..51c148b2 100644 --- a/Subtester/Assets/Scripts/PurchasesListener.cs +++ b/Subtester/Assets/Scripts/PurchasesListener.cs @@ -67,6 +67,8 @@ private void Start() CreateButton("Fetch & Redeem WinBack for Package", FetchAndRedeemWinBackForPackage); CreateButton("Get Storefront", GetStorefront); CreateButton("Present Paywall", PresentPaywallResult); + CreateButton("Present Paywall with Options", PresentPaywallWithOptions); + CreateButton("Present Paywall for Offering", PresentPaywallForOffering); var purchases = GetComponent(); purchases.SetLogLevel(Purchases.LogLevel.Verbose); @@ -199,64 +201,178 @@ private void CreatePurchasePackageForPlacementButtons() void PresentPaywallResult() { - var purchases = GetComponent(); Debug.Log("Subtester: launching paywall"); if (infoLabel != null) infoLabel.text = "Launching paywall..."; - purchases.PresentPaywall(result => + StartCoroutine(PresentPaywallCoroutine()); + } + + void PresentPaywallWithOptions() + { + Debug.Log("Subtester: launching paywall with options"); + if (infoLabel != null) infoLabel.text = "Launching paywall with options..."; + StartCoroutine(PresentPaywallWithOptionsCoroutine()); + } + + void PresentPaywallForOffering() + { + Debug.Log("Subtester: launching paywall for specific offering"); + if (infoLabel != null) infoLabel.text = "Launching paywall for offering..."; + StartCoroutine(PresentPaywallForOfferingCoroutine()); + } + + private System.Collections.IEnumerator PresentPaywallCoroutine() + { + var ui = GetComponent(); + if (ui == null) { - Debug.Log("Subtester: paywall result = " + result); - if (infoLabel != null) + ui = gameObject.AddComponent(); + } + + var task = ui.PresentPaywall(); + while (!task.IsCompleted) { yield return null; } + + var result = task.Result; + Debug.Log("Subtester: paywall result = " + result); + + if (infoLabel != null) + { + string status = GetPaywallResultStatus(result); + + if (result.Result == RevenueCat.UI.PaywallResultType.Purchased || + result.Result == RevenueCat.UI.PaywallResultType.Restored) { - string status = ""; - - switch (result) - { - case Purchases.PaywallResult.Purchased: - status = "PURCHASED - User completed a purchase"; - GetComponent().GetCustomerInfo((customerInfo, error) => { - if (error != null) - { - Debug.LogError("Subtester: Error refreshing customer info after purchase: " + error); - } - else - { - Debug.Log("Subtester: Refreshed customer info after purchase"); - DisplayCustomerInfo(customerInfo); - } - }); - break; - case Purchases.PaywallResult.Restored: - status = "RESTORED - User restored previous purchases"; - GetComponent().GetCustomerInfo((customerInfo, error) => { - if (error != null) - { - Debug.LogError("Subtester: Error refreshing customer info after restore: " + error); - } - else - { - Debug.Log("Subtester: Refreshed customer info after restore"); - DisplayCustomerInfo(customerInfo); - } - }); - break; - case Purchases.PaywallResult.Cancelled: - status = "CANCELLED - User dismissed the paywall"; - break; - case Purchases.PaywallResult.Error: - status = "ERROR - An error occurred during paywall"; - break; - case Purchases.PaywallResult.NotPresented: - status = "NOT PRESENTED - Paywall was not needed"; - break; - default: - status = $"UNKNOWN - Received: {result}"; - break; - } - - infoLabel.text = $"Paywall result: {status}"; - Debug.Log($"Subtester: {status}"); + GetComponent().GetCustomerInfo((customerInfo, error) => { + if (error != null) + { + Debug.LogError("Subtester: Error refreshing customer info after " + result.Result + ": " + error); + } + else + { + Debug.Log("Subtester: Refreshed customer info after " + result.Result); + DisplayCustomerInfo(customerInfo); + } + }); + } + + infoLabel.text = $"Paywall result: {status}"; + Debug.Log($"Subtester: {status}"); + } + } + + private System.Collections.IEnumerator PresentPaywallWithOptionsCoroutine() + { + var ui = GetComponent(); + if (ui == null) + { + ui = gameObject.AddComponent(); + } + + var options = new RevenueCat.UI.PaywallOptions + { + DisplayCloseButton = false + }; + + var task = ui.PresentPaywall(options); + while (!task.IsCompleted) { yield return null; } + + var result = task.Result; + Debug.Log("Subtester: paywall with options result = " + result); + + if (infoLabel != null) + { + infoLabel.text = $"Paywall with options result: {GetPaywallResultStatus(result)}"; + } + } + + private System.Collections.IEnumerator PresentPaywallForOfferingCoroutine() + { + var ui = GetComponent(); + if (ui == null) + { + ui = gameObject.AddComponent(); + } + + // First get available offerings to use one as an example + var purchases = GetComponent(); + var offeringsTask = new System.Threading.Tasks.TaskCompletionSource(); + + purchases.GetOfferings((offerings, error) => + { + if (error != null) + { + offeringsTask.SetException(new System.Exception(error.ToString())); + } + else + { + offeringsTask.SetResult(offerings); } }); + + while (!offeringsTask.Task.IsCompleted) { yield return null; } + + if (offeringsTask.Task.IsFaulted) + { + Debug.LogError("Subtester: Error getting offerings: " + offeringsTask.Task.Exception.GetBaseException().Message); + if (infoLabel != null) + { + infoLabel.text = "Error getting offerings: " + offeringsTask.Task.Exception.GetBaseException().Message; + } + yield break; + } + + var offerings = offeringsTask.Task.Result; + string offeringId = null; + + // Random offering ID from available offerings + if (offerings?.All?.Count > 0) + { + var allOfferingIds = offerings.All.Keys.ToList(); + var randomIndex = UnityEngine.Random.Range(0, allOfferingIds.Count); + offeringId = allOfferingIds[randomIndex]; + } + else if (offerings?.Current != null) + { + offeringId = offerings.Current.Identifier; + } + + // Create options with specific offering + var options = new RevenueCat.UI.PaywallOptions + { + OfferingIdentifier = offeringId ?? "default", + DisplayCloseButton = true + }; + + Debug.Log($"Subtester: Presenting paywall for offering: {options.OfferingIdentifier}"); + + var task = ui.PresentPaywall(options); + while (!task.IsCompleted) { yield return null; } + + var result = task.Result; + Debug.Log("Subtester: paywall for offering result = " + result); + + if (infoLabel != null) + { + infoLabel.text = $"Paywall for offering '{options.OfferingIdentifier}' result: {GetPaywallResultStatus(result)}"; + } + } + + private string GetPaywallResultStatus(RevenueCat.UI.PaywallResult result) + { + switch (result.Result) + { + case RevenueCat.UI.PaywallResultType.Purchased: + return "PURCHASED - User completed a purchase"; + case RevenueCat.UI.PaywallResultType.Restored: + return "RESTORED - User restored previous purchases"; + case RevenueCat.UI.PaywallResultType.Cancelled: + return "CANCELLED - User dismissed the paywall"; + case RevenueCat.UI.PaywallResultType.Error: + return "ERROR - An error occurred during paywall"; + case RevenueCat.UI.PaywallResultType.NotPresented: + return "NOT PRESENTED - Paywall was not needed"; + default: + return $"UNKNOWN - Received: {result}"; + } } private void CreateButton(string label, UnityAction action) From 44bc909ee829e7f0a27e4783263aebc4f25f102d Mon Sep 17 00:00:00 2001 From: Cesar de la Vega <664544+vegaro@users.noreply.github.com> Date: Mon, 22 Sep 2025 12:34:45 +0200 Subject: [PATCH 38/65] gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index c65d9f7a..bf451aeb 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,4 @@ fastlane/.env # CircleCI folders vendor/ .bundle/ +RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/build From eb27acf0fdefeb022aa4d7a5d4860e6f1604beb3 Mon Sep 17 00:00:00 2001 From: Cesar de la Vega <664544+vegaro@users.noreply.github.com> Date: Mon, 22 Sep 2025 12:35:20 +0200 Subject: [PATCH 39/65] add log to AndroidPaywallPresenter --- .../Scripts/Platforms/Android/AndroidPaywallPresenter.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs b/RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs index 03268917..cf33ce00 100644 --- a/RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs +++ b/RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs @@ -41,8 +41,10 @@ public Task PresentPaywallAsync(string gameObjectName, PaywallOpt try { var offering = options?.OfferingIdentifier; - Debug.Log($"[RevenueCatUI][Android] presentPaywall offering='{offering ?? ""}'"); - _plugin.CallStatic("presentPaywall", gameObjectName, offering); + var displayCloseButton = options?.DisplayCloseButton ?? false; + + Debug.Log($"[RevenueCatUI][Android] presentPaywall offering='{offering ?? ""}', displayCloseButton={displayCloseButton}"); + _plugin.CallStatic("presentPaywall", gameObjectName, offering, displayCloseButton); } catch (Exception e) { From 5da75f800704189a68447fbbb7d4df39fdace270 Mon Sep 17 00:00:00 2001 From: Cesar de la Vega <664544+vegaro@users.noreply.github.com> Date: Mon, 22 Sep 2025 12:35:40 +0200 Subject: [PATCH 40/65] add displayCloseButton --- .../ui/PaywallProxyActivity.java | 23 ++++++++++++++++--- .../purchasesunity/ui/RevenueCatUI.java | 13 +++++++---- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/src/main/java/com/revenuecat/purchasesunity/ui/PaywallProxyActivity.java b/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/src/main/java/com/revenuecat/purchasesunity/ui/PaywallProxyActivity.java index dd79d03a..ee92baa2 100644 --- a/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/src/main/java/com/revenuecat/purchasesunity/ui/PaywallProxyActivity.java +++ b/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/src/main/java/com/revenuecat/purchasesunity/ui/PaywallProxyActivity.java @@ -9,11 +9,13 @@ import com.revenuecat.purchases.ui.revenuecatui.activity.PaywallActivityLauncher; import com.revenuecat.purchases.ui.revenuecatui.activity.PaywallResult; +import com.revenuecat.purchases.PresentedOfferingContext; public class PaywallProxyActivity extends AppCompatActivity { public static final String EXTRA_GAME_OBJECT = "rc_proxy_game_object"; public static final String EXTRA_METHOD = "rc_proxy_method"; public static final String EXTRA_OFFERING_ID = "rc_offering_id"; + public static final String EXTRA_SHOULD_DISPLAY_DISMISS_BUTTON = "rc_should_display_dismiss_button"; private static final String TAG = "PurchasesUnity"; @@ -27,6 +29,8 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { final Intent source = getIntent(); gameObject = source.getStringExtra(EXTRA_GAME_OBJECT); method = source.getStringExtra(EXTRA_METHOD); + String offeringId = source.getStringExtra(EXTRA_OFFERING_ID); + boolean shouldDisplayDismissButton = source.getBooleanExtra(EXTRA_SHOULD_DISPLAY_DISMISS_BUTTON, false); if (gameObject == null || method == null) { Log.w(TAG, "Missing gameObject/method extras; finishing."); @@ -48,8 +52,22 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { ); Log.d(TAG, "Launching paywall with PaywallActivityLauncher"); - // If you ever need to pass an offering, wire it via intent extras and into the launcher - launcher.launch(null); + Log.d(TAG, "Options - offering: " + offeringId + ", dismissButton: " + shouldDisplayDismissButton); + + if (offeringId != null) { + Log.d(TAG, "Launching paywall with offering ID using deprecated method"); + launcher.launch(offeringId, + new PresentedOfferingContext(offeringId), // TODO: pass PresentedOfferingContext data + null, // fontProvider + shouldDisplayDismissButton); + } else { + Log.d(TAG, "Launching paywall with standard method"); + launcher.launch( + null, // offering (Offering object, not String) + null, // fontProvider + shouldDisplayDismissButton + ); + } } private void sendPaywallResult(PaywallResult result) { @@ -68,7 +86,6 @@ private void sendPaywallResult(PaywallResult result) { Log.d(TAG, "Sending PaywallResult: " + resultName); - // UnitySendMessage is safest from the UI thread. We're already on it, but guard anyway. runOnUiThread(() -> UnityBridge.sendMessage(gameObject, method, resultName)); } } \ No newline at end of file diff --git a/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/src/main/java/com/revenuecat/purchasesunity/ui/RevenueCatUI.java b/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/src/main/java/com/revenuecat/purchasesunity/ui/RevenueCatUI.java index 65e29f07..a7bdec59 100644 --- a/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/src/main/java/com/revenuecat/purchasesunity/ui/RevenueCatUI.java +++ b/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/src/main/java/com/revenuecat/purchasesunity/ui/RevenueCatUI.java @@ -2,6 +2,8 @@ import static com.revenuecat.purchasesunity.ui.PaywallProxyActivity.EXTRA_GAME_OBJECT; import static com.revenuecat.purchasesunity.ui.PaywallProxyActivity.EXTRA_METHOD; +import static com.revenuecat.purchasesunity.ui.PaywallProxyActivity.EXTRA_OFFERING_ID; +import static com.revenuecat.purchasesunity.ui.PaywallProxyActivity.EXTRA_SHOULD_DISPLAY_DISMISS_BUTTON; import android.app.Activity; import android.content.Intent; @@ -16,7 +18,7 @@ public interface PaywallCallbacks { void onPaywallResult(String result); } public static void registerPaywallCallbacks(PaywallCallbacks cb) { paywallCallbacks = cb; } public static void unregisterPaywallCallbacks() { paywallCallbacks = null; } - public static void presentPaywall(String gameObject, String offeringIdentifier) { + public static void presentPaywall(String gameObject, String offeringIdentifier, boolean displayCloseButton) { try { Activity activity = UnityBridge.currentActivityOrNull(); if (activity == null) { @@ -28,12 +30,15 @@ public static void presentPaywall(String gameObject, String offeringIdentifier) Intent intent = new Intent(activity, PaywallProxyActivity.class); intent.putExtra(EXTRA_GAME_OBJECT, gameObject); intent.putExtra(EXTRA_METHOD, "OnPaywallResultFromActivity"); - if (offeringIdentifier != null) { - intent.putExtra(PaywallProxyActivity.EXTRA_OFFERING_ID, offeringIdentifier); + + if (offeringIdentifier != null && !offeringIdentifier.isEmpty()) { + intent.putExtra(EXTRA_OFFERING_ID, offeringIdentifier); } + + intent.putExtra(EXTRA_SHOULD_DISPLAY_DISMISS_BUTTON, displayCloseButton); Log.d(TAG, "Launching PaywallProxyActivity for gameObject=" + gameObject + - ", offering=" + offeringIdentifier); + ", offering=" + offeringIdentifier + ", displayCloseButton=" + displayCloseButton); activity.startActivity(intent); } catch (Throwable t) { Log.e(TAG, "Error launching PaywallProxyActivity", t); From 56258da7d412358cf66ab5d8cc479a3e934edac7 Mon Sep 17 00:00:00 2001 From: Cesar de la Vega <664544+vegaro@users.noreply.github.com> Date: Mon, 22 Sep 2025 13:17:59 +0200 Subject: [PATCH 41/65] fix compilation of Subtester when exporting package --- Subtester/Assets/Scripts/PurchasesListener.cs | 1 + scripts/create-unity-package.sh | 50 ++++++++++++++----- 2 files changed, 39 insertions(+), 12 deletions(-) diff --git a/Subtester/Assets/Scripts/PurchasesListener.cs b/Subtester/Assets/Scripts/PurchasesListener.cs index 51c148b2..ac85c934 100644 --- a/Subtester/Assets/Scripts/PurchasesListener.cs +++ b/Subtester/Assets/Scripts/PurchasesListener.cs @@ -5,6 +5,7 @@ using UnityEngine; using UnityEngine.Events; using UnityEngine.UI; +using RevenueCat.UI; public class PurchasesListener : Purchases.UpdatedCustomerInfoListener { diff --git a/scripts/create-unity-package.sh b/scripts/create-unity-package.sh index 91007cbc..b1c741e0 100755 --- a/scripts/create-unity-package.sh +++ b/scripts/create-unity-package.sh @@ -49,6 +49,11 @@ if [ ! -z "$CI" ]; then verbose_echo "Using copy operation instead of symlink for CI compatibility" cp -r "$PWD/RevenueCat" "$PROJECT/Assets/" + # Also copy RevenueCatUI folder for compilation (not exported yet) + echo "🔧 Copying RevenueCatUI folder for compilation" + verbose_echo "Copying: $PWD/RevenueCatUI → $PROJECT/Assets/RevenueCatUI" + cp -r "$PWD/RevenueCatUI" "$PROJECT/Assets/" + # Verify copy was successful if [ -d "$PROJECT/Assets/RevenueCat" ]; then echo "✅ RevenueCat folder copied successfully" @@ -56,6 +61,13 @@ if [ ! -z "$CI" ]; then echo "❌ Failed to copy RevenueCat folder!" exit 1 fi + + if [ -d "$PROJECT/Assets/RevenueCatUI" ]; then + echo "✅ RevenueCatUI folder copied successfully" + else + echo "❌ Failed to copy RevenueCatUI folder!" + exit 1 + fi else # Local development (macOS) - use symlink for convenience verbose_echo "Creating symlink: $PWD/RevenueCat → $PROJECT/Assets/RevenueCat" @@ -63,14 +75,26 @@ else verbose_echo "Using symlink operation for local development" ln -s "$PWD/RevenueCat" "$PROJECT/Assets/" + # Also create symlink for RevenueCatUI folder for compilation (not exported yet) + verbose_echo "Creating symlink: $PWD/RevenueCatUI → $PROJECT/Assets/RevenueCatUI" + ln -s "$PWD/RevenueCatUI" "$PROJECT/Assets/" + # Verify symlink was created successfully if [ -L "$PROJECT/Assets/RevenueCat" ]; then - echo "✅ Symlink created successfully" + echo "✅ RevenueCat symlink created successfully" else - echo "❌ Failed to create symlink!" + echo "❌ Failed to create RevenueCat symlink!" rm -f $SYMBOLIC_LINK_PATH exit 1 fi + + if [ -L "$PROJECT/Assets/RevenueCatUI" ]; then + echo "✅ RevenueCatUI symlink created successfully" + else + echo "❌ Failed to create RevenueCatUI symlink!" + rm -f "$PROJECT/Assets/RevenueCatUI" + exit 1 + fi fi # Common verification for both approaches @@ -96,7 +120,7 @@ if [ -d "$PROJECT/Assets/RevenueCat" ]; then fi else echo "❌ Failed to access RevenueCat folder!" - rm -rf "$PROJECT/Assets/RevenueCat" 2>/dev/null + rm -rf "$PROJECT/Assets/RevenueCat" "$PROJECT/Assets/RevenueCatUI" 2>/dev/null exit 1 fi @@ -142,7 +166,7 @@ PLUGINS_FOLDER="$PWD/RevenueCat/Plugins" if ! [ -d "$PROJECT" ]; then echo "Run this script from the root folder of the repository (e.g. ./scripts/create-unity-package.sh)." - rm -rf "$PROJECT/Assets/RevenueCat" 2>/dev/null + rm -rf "$PROJECT/Assets/RevenueCat" "$PROJECT/Assets/RevenueCatUI" 2>/dev/null exit 1 fi @@ -153,7 +177,7 @@ if [ -z "$UNITY_BIN" ]; then echo " -u Path to Unity binary" echo " -v Enable verbose output" echo "Note: This script is optimized for Unity 6.2 (6000.2.x) but should work with Unity 2021.3+ versions" - rm -rf "$PROJECT/Assets/RevenueCat" 2>/dev/null + rm -rf "$PROJECT/Assets/RevenueCat" "$PROJECT/Assets/RevenueCatUI" 2>/dev/null exit 1 fi @@ -161,7 +185,7 @@ fi verbose_echo "Checking Unity binary at: $UNITY_BIN" if [ ! -x "$UNITY_BIN" ]; then echo "😞 Unity binary not found or not executable at: $UNITY_BIN" - rm -rf "$PROJECT/Assets/RevenueCat" 2>/dev/null + rm -rf "$PROJECT/Assets/RevenueCat" "$PROJECT/Assets/RevenueCatUI" 2>/dev/null exit 1 fi verbose_echo "Unity binary verified successfully" @@ -198,13 +222,13 @@ else curl -L "$EDM_URL" -o "$EDM_FILE" else echo "❌ Neither wget nor curl found. Please install one of them." - rm -rf "$PROJECT/Assets/RevenueCat" 2>/dev/null + rm -rf "$PROJECT/Assets/RevenueCat" "$PROJECT/Assets/RevenueCatUI" 2>/dev/null exit 1 fi if [ ! -f "$EDM_FILE" ]; then echo "❌ Failed to download External Dependency Manager" - rm -rf "$PROJECT/Assets/RevenueCat" 2>/dev/null + rm -rf "$PROJECT/Assets/RevenueCat" "$PROJECT/Assets/RevenueCatUI" 2>/dev/null exit 1 fi fi @@ -255,12 +279,14 @@ else if [ ! -f "$PACKAGE" ]; then echo " Package file not found: $PACKAGE" fi - verbose_echo "Cleaning up RevenueCat folder/symlink due to failure" - # Cleanup RevenueCat folder/symlink + verbose_echo "Cleaning up RevenueCat and RevenueCatUI folders/symlinks due to failure" + # Cleanup RevenueCat and RevenueCatUI folders/symlinks rm -rf "$PROJECT/Assets/RevenueCat" + rm -rf "$PROJECT/Assets/RevenueCatUI" exit 1 fi -# Cleanup RevenueCat folder/symlink -verbose_echo "Cleaning up RevenueCat folder/symlink after successful package creation" +# Cleanup RevenueCat and RevenueCatUI folders/symlinks +verbose_echo "Cleaning up RevenueCat and RevenueCatUI folders/symlinks after successful package creation" rm -rf "$PROJECT/Assets/RevenueCat" +rm -rf "$PROJECT/Assets/RevenueCatUI" From be4ef13449b5131f80238631268a8c796cb7b611 Mon Sep 17 00:00:00 2001 From: Cesar de la Vega <664544+vegaro@users.noreply.github.com> Date: Mon, 22 Sep 2025 14:29:15 +0200 Subject: [PATCH 42/65] use componentActivity --- .../Android/RevenueCatUI.androidlib/AndroidManifest.xml | 2 +- .../Plugins/Android/RevenueCatUI.androidlib/build.gradle | 2 +- .../revenuecat/purchasesunity/ui/PaywallProxyActivity.java | 4 ++-- RevenueCatUI/Plugins/Editor/RevenueCatUIDependencies.xml | 2 +- Subtester/Assets/Plugins/Android/mainTemplate.gradle | 5 +++-- 5 files changed, 8 insertions(+), 7 deletions(-) diff --git a/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/AndroidManifest.xml b/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/AndroidManifest.xml index de6a0905..3a19e189 100644 --- a/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/AndroidManifest.xml +++ b/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/AndroidManifest.xml @@ -6,7 +6,7 @@ diff --git a/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/build.gradle b/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/build.gradle index 33e5eaba..59b00ab1 100644 --- a/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/build.gradle +++ b/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/build.gradle @@ -2,9 +2,9 @@ apply plugin: 'com.android.library' dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'com.revenuecat.purchases:purchases-hybrid-common:17.7.0' implementation 'com.revenuecat.purchases:purchases-hybrid-common-ui:17.7.0' + implementation 'androidx.activity:activity:1.8.2' } android { diff --git a/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/src/main/java/com/revenuecat/purchasesunity/ui/PaywallProxyActivity.java b/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/src/main/java/com/revenuecat/purchasesunity/ui/PaywallProxyActivity.java index ee92baa2..1932d2ff 100644 --- a/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/src/main/java/com/revenuecat/purchasesunity/ui/PaywallProxyActivity.java +++ b/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/src/main/java/com/revenuecat/purchasesunity/ui/PaywallProxyActivity.java @@ -5,13 +5,13 @@ import android.util.Log; import androidx.annotation.Nullable; -import androidx.appcompat.app.AppCompatActivity; +import androidx.activity.ComponentActivity; import com.revenuecat.purchases.ui.revenuecatui.activity.PaywallActivityLauncher; import com.revenuecat.purchases.ui.revenuecatui.activity.PaywallResult; import com.revenuecat.purchases.PresentedOfferingContext; -public class PaywallProxyActivity extends AppCompatActivity { +public class PaywallProxyActivity extends ComponentActivity { public static final String EXTRA_GAME_OBJECT = "rc_proxy_game_object"; public static final String EXTRA_METHOD = "rc_proxy_method"; public static final String EXTRA_OFFERING_ID = "rc_offering_id"; diff --git a/RevenueCatUI/Plugins/Editor/RevenueCatUIDependencies.xml b/RevenueCatUI/Plugins/Editor/RevenueCatUIDependencies.xml index f41d2613..d3bc0b47 100644 --- a/RevenueCatUI/Plugins/Editor/RevenueCatUIDependencies.xml +++ b/RevenueCatUI/Plugins/Editor/RevenueCatUIDependencies.xml @@ -2,7 +2,7 @@ - + diff --git a/Subtester/Assets/Plugins/Android/mainTemplate.gradle b/Subtester/Assets/Plugins/Android/mainTemplate.gradle index afd5650c..c7f03dce 100644 --- a/Subtester/Assets/Plugins/Android/mainTemplate.gradle +++ b/Subtester/Assets/Plugins/Android/mainTemplate.gradle @@ -7,8 +7,8 @@ apply from: '../shared/keepUnitySymbols.gradle' dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) // Android Resolver Dependencies Start + implementation 'androidx.activity:activity:1.8.2' // Packages/com.revenuecat.purchases-ui-unity/Plugins/Editor/RevenueCatUIDependencies.xml:5 implementation 'androidx.annotation:annotation:[1.2.0]' // Packages/com.revenuecat.purchases-unity/Plugins/Editor/RevenueCatDependencies.xml:5 - implementation 'androidx.appcompat:appcompat:1.6.1' // Packages/com.revenuecat.purchases-ui-unity/Plugins/Editor/RevenueCatUIDependencies.xml:5 implementation 'com.revenuecat.purchases:purchases-hybrid-common:[17.7.0]' // Packages/com.revenuecat.purchases-unity/Plugins/Editor/RevenueCatDependencies.xml:4 implementation 'com.revenuecat.purchases:purchases-hybrid-common-ui:[17.7.0]' // Packages/com.revenuecat.purchases-ui-unity/Plugins/Editor/RevenueCatUIDependencies.xml:4 // Android Resolver Dependencies End @@ -16,7 +16,6 @@ dependencies { // Android Resolver Exclusions Start android { - namespace "com.unity3d.player" ndkPath "**NDKPATH**" ndkVersion "**NDKVERSION**" packaging { @@ -30,6 +29,8 @@ android { } // Android Resolver Exclusions End android { + namespace "com.unity3d.player" + compileSdk **APIVERSION** buildToolsVersion '**BUILDTOOLS**' From 649cff43b47424217cf7cdfcbb123f3c01a8cfe4 Mon Sep 17 00:00:00 2001 From: Cesar de la Vega <664544+vegaro@users.noreply.github.com> Date: Mon, 22 Sep 2025 14:38:27 +0200 Subject: [PATCH 43/65] presentPaywallIfNeeded --- .../ui/PaywallProxyActivity.java | 51 +++++++++++++++++-- .../purchasesunity/ui/RevenueCatUI.java | 34 +++++++++++-- 2 files changed, 77 insertions(+), 8 deletions(-) diff --git a/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/src/main/java/com/revenuecat/purchasesunity/ui/PaywallProxyActivity.java b/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/src/main/java/com/revenuecat/purchasesunity/ui/PaywallProxyActivity.java index 1932d2ff..c9e3f8c3 100644 --- a/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/src/main/java/com/revenuecat/purchasesunity/ui/PaywallProxyActivity.java +++ b/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/src/main/java/com/revenuecat/purchasesunity/ui/PaywallProxyActivity.java @@ -16,6 +16,7 @@ public class PaywallProxyActivity extends ComponentActivity { public static final String EXTRA_METHOD = "rc_proxy_method"; public static final String EXTRA_OFFERING_ID = "rc_offering_id"; public static final String EXTRA_SHOULD_DISPLAY_DISMISS_BUTTON = "rc_should_display_dismiss_button"; + public static final String EXTRA_REQUIRED_ENTITLEMENT_IDENTIFIER = "rc_required_entitlement_identifier"; private static final String TAG = "PurchasesUnity"; @@ -31,6 +32,7 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { method = source.getStringExtra(EXTRA_METHOD); String offeringId = source.getStringExtra(EXTRA_OFFERING_ID); boolean shouldDisplayDismissButton = source.getBooleanExtra(EXTRA_SHOULD_DISPLAY_DISMISS_BUTTON, false); + String requiredEntitlementIdentifier = source.getStringExtra(EXTRA_REQUIRED_ENTITLEMENT_IDENTIFIER); if (gameObject == null || method == null) { Log.w(TAG, "Missing gameObject/method extras; finishing."); @@ -51,11 +53,49 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { } ); + if (requiredEntitlementIdentifier != null) { + Log.d(TAG, "Using launchIfNeeded for entitlement '" + requiredEntitlementIdentifier + "'"); + launchPaywallIfNeeded(launcher, requiredEntitlementIdentifier, offeringId, shouldDisplayDismissButton); + } else { + Log.d(TAG, "No entitlement check required, presenting paywall directly"); + launchPaywall(launcher, offeringId, shouldDisplayDismissButton); + } + } + + private void launchPaywallIfNeeded(PaywallActivityLauncher launcher, String requiredEntitlementIdentifier, String offeringId, boolean shouldDisplayDismissButton) { + Log.d(TAG, "Launching paywall if needed with PaywallActivityLauncher"); + Log.d(TAG, "Options - entitlement: " + requiredEntitlementIdentifier + ", offering: " + offeringId + ", dismissButton: " + shouldDisplayDismissButton); + + if (offeringId != null) { + Log.d(TAG, "Using launchIfNeeded with offering ID"); + launcher.launchIfNeeded( + requiredEntitlementIdentifier, + offeringId, + new PresentedOfferingContext(offeringId), // TODO: pass PresentedOfferingContext data + null, // fontProvider + shouldDisplayDismissButton, + false, // edgeToEdge + paywallDisplayResult -> { + if (!paywallDisplayResult) { + Log.d(TAG, "PaywallDisplayCallback: paywall not needed"); + sendResult("NOT_PRESENTED"); + finish(); + } + // If paywallDisplayResult is true, the paywall will be shown and result will come through normal callback + } + ); + } else { + Log.w(TAG, "launchIfNeeded requires an offering ID, falling back to regular launch"); + launchPaywall(launcher, offeringId, shouldDisplayDismissButton); + } + } + + private void launchPaywall(PaywallActivityLauncher launcher, String offeringId, boolean shouldDisplayDismissButton) { Log.d(TAG, "Launching paywall with PaywallActivityLauncher"); Log.d(TAG, "Options - offering: " + offeringId + ", dismissButton: " + shouldDisplayDismissButton); if (offeringId != null) { - Log.d(TAG, "Launching paywall with offering ID using deprecated method"); + Log.d(TAG, "Launching paywall with offering ID"); launcher.launch(offeringId, new PresentedOfferingContext(offeringId), // TODO: pass PresentedOfferingContext data null, // fontProvider @@ -70,6 +110,11 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { } } + private void sendResult(String resultName) { + Log.d(TAG, "Sending result: " + resultName); + runOnUiThread(() -> UnityBridge.sendMessage(gameObject, method, resultName)); + } + private void sendPaywallResult(PaywallResult result) { final String resultName; if (result instanceof PaywallResult.Purchased) { @@ -84,8 +129,6 @@ private void sendPaywallResult(PaywallResult result) { resultName = "cancelled"; } - Log.d(TAG, "Sending PaywallResult: " + resultName); - - runOnUiThread(() -> UnityBridge.sendMessage(gameObject, method, resultName)); + sendResult(resultName); } } \ No newline at end of file diff --git a/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/src/main/java/com/revenuecat/purchasesunity/ui/RevenueCatUI.java b/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/src/main/java/com/revenuecat/purchasesunity/ui/RevenueCatUI.java index a7bdec59..11fb88ae 100644 --- a/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/src/main/java/com/revenuecat/purchasesunity/ui/RevenueCatUI.java +++ b/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/src/main/java/com/revenuecat/purchasesunity/ui/RevenueCatUI.java @@ -4,6 +4,7 @@ import static com.revenuecat.purchasesunity.ui.PaywallProxyActivity.EXTRA_METHOD; import static com.revenuecat.purchasesunity.ui.PaywallProxyActivity.EXTRA_OFFERING_ID; import static com.revenuecat.purchasesunity.ui.PaywallProxyActivity.EXTRA_SHOULD_DISPLAY_DISMISS_BUTTON; +import static com.revenuecat.purchasesunity.ui.PaywallProxyActivity.EXTRA_REQUIRED_ENTITLEMENT_IDENTIFIER; import android.app.Activity; import android.content.Intent; @@ -46,11 +47,36 @@ public static void presentPaywall(String gameObject, String offeringIdentifier, } } - public static void presentPaywallIfNeeded(String gameObject, String requiredEntitlementIdentifier, String offeringIdentifier) { + public static void presentPaywallIfNeeded(String gameObject, String requiredEntitlementIdentifier, String offeringIdentifier, boolean displayCloseButton) { Log.d(TAG, "presentPaywallIfNeeded(go=" + gameObject + ", entitlement=" + - requiredEntitlementIdentifier + ", offering=" + offeringIdentifier + ")"); - // Stubbed behavior (adjust when you add native logic) - UnityBridge.sendMessage(gameObject, "OnPaywallResultFromActivity", "NOT_PRESENTED|Stub: no native UI"); + requiredEntitlementIdentifier + ", offering=" + offeringIdentifier + ", displayCloseButton=" + displayCloseButton + ")"); + + try { + Activity activity = UnityBridge.currentActivityOrNull(); + if (activity == null) { + Log.e(TAG, "currentActivity is null; cannot launch paywall"); + UnityBridge.sendMessage(gameObject, "OnPaywallResultFromActivity", "ERROR|NoActivity"); + return; + } + + Intent intent = new Intent(activity, PaywallProxyActivity.class); + intent.putExtra(EXTRA_GAME_OBJECT, gameObject); + intent.putExtra(EXTRA_METHOD, "OnPaywallResultFromActivity"); + intent.putExtra(EXTRA_REQUIRED_ENTITLEMENT_IDENTIFIER, requiredEntitlementIdentifier); + + if (offeringIdentifier != null && !offeringIdentifier.isEmpty()) { + intent.putExtra(EXTRA_OFFERING_ID, offeringIdentifier); + } + + intent.putExtra(EXTRA_SHOULD_DISPLAY_DISMISS_BUTTON, displayCloseButton); + + Log.d(TAG, "Launching PaywallProxyActivity for presentPaywallIfNeeded gameObject=" + gameObject + + ", entitlement=" + requiredEntitlementIdentifier + ", offering=" + offeringIdentifier + ", displayCloseButton=" + displayCloseButton); + activity.startActivity(intent); + } catch (Throwable t) { + Log.e(TAG, "Error launching PaywallProxyActivity for presentPaywallIfNeeded", t); + UnityBridge.sendMessage(gameObject, "OnPaywallResultFromActivity", "ERROR|" + t.getClass().getSimpleName()); + } } public static boolean isSupported() { return true; } From e55f37507626675956b882fc013138020f4707fa Mon Sep 17 00:00:00 2001 From: Cesar de la Vega <664544+vegaro@users.noreply.github.com> Date: Mon, 22 Sep 2025 14:42:19 +0200 Subject: [PATCH 44/65] add close button to log --- .../Scripts/Platforms/Android/AndroidPaywallPresenter.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs b/RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs index cf33ce00..268917a7 100644 --- a/RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs +++ b/RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs @@ -67,8 +67,9 @@ public Task PresentPaywallIfNeededAsync(string gameObjectName, st try { var offering = options?.OfferingIdentifier; - Debug.Log($"[RevenueCatUI][Android] presentPaywallIfNeeded entitlement='{requiredEntitlementIdentifier}', offering='{offering ?? ""}'"); - _plugin.CallStatic("presentPaywallIfNeeded", gameObjectName, requiredEntitlementIdentifier, offering); + var displayCloseButton = options?.DisplayCloseButton ?? false; + Debug.Log($"[RevenueCatUI][Android] presentPaywallIfNeeded entitlement='{requiredEntitlementIdentifier}', offering='{offering ?? ""}', displayCloseButton={displayCloseButton}"); + _plugin.CallStatic("presentPaywallIfNeeded", gameObjectName, requiredEntitlementIdentifier, offering, displayCloseButton); } catch (Exception e) { From 26961f33912a36dd9e682e64e27f6152f3ead7df Mon Sep 17 00:00:00 2001 From: Cesar de la Vega <664544+vegaro@users.noreply.github.com> Date: Mon, 22 Sep 2025 14:42:46 +0200 Subject: [PATCH 45/65] add PresentPaywallIfNeeded to PurchasesListener --- Subtester/Assets/Scripts/PurchasesListener.cs | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/Subtester/Assets/Scripts/PurchasesListener.cs b/Subtester/Assets/Scripts/PurchasesListener.cs index ac85c934..02bbf230 100644 --- a/Subtester/Assets/Scripts/PurchasesListener.cs +++ b/Subtester/Assets/Scripts/PurchasesListener.cs @@ -70,6 +70,7 @@ private void Start() CreateButton("Present Paywall", PresentPaywallResult); CreateButton("Present Paywall with Options", PresentPaywallWithOptions); CreateButton("Present Paywall for Offering", PresentPaywallForOffering); + CreateButton("Present Paywall If Needed", PresentPaywallIfNeeded); var purchases = GetComponent(); purchases.SetLogLevel(Purchases.LogLevel.Verbose); @@ -221,6 +222,13 @@ void PresentPaywallForOffering() StartCoroutine(PresentPaywallForOfferingCoroutine()); } + void PresentPaywallIfNeeded() + { + Debug.Log("Subtester: launching paywall if needed for test entitlement"); + if (infoLabel != null) infoLabel.text = "Checking entitlement and launching paywall if needed..."; + StartCoroutine(PresentPaywallIfNeededCoroutine()); + } + private System.Collections.IEnumerator PresentPaywallCoroutine() { var ui = GetComponent(); @@ -357,6 +365,87 @@ private System.Collections.IEnumerator PresentPaywallForOfferingCoroutine() } } + private System.Collections.IEnumerator PresentPaywallIfNeededCoroutine() + { + var ui = GetComponent(); + if (ui == null) + { + ui = gameObject.AddComponent(); + } + + // First get available offerings to use one as an example + var purchases = GetComponent(); + var offeringsTask = new System.Threading.Tasks.TaskCompletionSource(); + + purchases.GetOfferings((offerings, error) => + { + if (error != null) + { + offeringsTask.SetException(new System.Exception(error.ToString())); + } + else + { + offeringsTask.SetResult(offerings); + } + }); + + while (!offeringsTask.Task.IsCompleted) { yield return null; } + + if (offeringsTask.Task.IsFaulted) + { + Debug.LogError("Subtester: Error getting offerings: " + offeringsTask.Task.Exception.GetBaseException().Message); + if (infoLabel != null) + { + infoLabel.text = "Error getting offerings: " + offeringsTask.Task.Exception.GetBaseException().Message; + } + yield break; + } + + var offerings = offeringsTask.Task.Result; + string offeringId = null; + + // Random offering ID from available offerings + if (offerings?.All?.Count > 0) + { + var allOfferingIds = offerings.All.Keys.ToList(); + var randomIndex = UnityEngine.Random.Range(0, allOfferingIds.Count); + offeringId = allOfferingIds[randomIndex]; + } + else if (offerings?.Current != null) + { + offeringId = offerings.Current.Identifier; + } + + // Create options for the test + var options = new RevenueCat.UI.PaywallOptions + { + OfferingIdentifier = offeringId ?? "default", + DisplayCloseButton = true // Test with close button enabled + }; + + // Test with a real entitlement - change this to test different scenarios + var testEntitlement = "pro_level_b"; // User should have this, so paywall should NOT be presented + + Debug.Log($"Subtester: Testing presentPaywallIfNeeded for entitlement: {testEntitlement}, offering: {options.OfferingIdentifier}"); + + var task = ui.PresentPaywallIfNeeded(testEntitlement, options); + while (!task.IsCompleted) { yield return null; } + + var result = task.Result; + Debug.Log("Subtester: paywall if needed result = " + result); + + if (infoLabel != null) + { + var status = GetPaywallResultStatus(result); + var message = $"PaywallIfNeeded for '{testEntitlement}' result: {status}"; + if (result.Result == RevenueCat.UI.PaywallResultType.NotPresented) + { + message += " (User already has entitlement)"; + } + infoLabel.text = message; + } + } + private string GetPaywallResultStatus(RevenueCat.UI.PaywallResult result) { switch (result.Result) From 07afd3dc482c54c13eb480876685dde9dd9d27c2 Mon Sep 17 00:00:00 2001 From: Cesar de la Vega <664544+vegaro@users.noreply.github.com> Date: Mon, 22 Sep 2025 14:43:30 +0200 Subject: [PATCH 46/65] update AndroidResolverDependencies --- Subtester/ProjectSettings/AndroidResolverDependencies.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Subtester/ProjectSettings/AndroidResolverDependencies.xml b/Subtester/ProjectSettings/AndroidResolverDependencies.xml index c1994495..dea0fc6c 100644 --- a/Subtester/ProjectSettings/AndroidResolverDependencies.xml +++ b/Subtester/ProjectSettings/AndroidResolverDependencies.xml @@ -1,7 +1,9 @@ + androidx.activity:activity:1.8.2 androidx.annotation:annotation:[1.2.0] com.revenuecat.purchases:purchases-hybrid-common:[17.7.0] + com.revenuecat.purchases:purchases-hybrid-common-ui:[17.7.0] @@ -17,6 +19,8 @@ + + \ No newline at end of file From dde2af53fe95a8214bcb51819d2465a46a6dcf1c Mon Sep 17 00:00:00 2001 From: Cesar de la Vega <664544+vegaro@users.noreply.github.com> Date: Fri, 26 Sep 2025 08:10:22 +0200 Subject: [PATCH 47/65] implements PaywallResultHandler --- .../ui/PaywallProxyActivity.java | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/src/main/java/com/revenuecat/purchasesunity/ui/PaywallProxyActivity.java b/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/src/main/java/com/revenuecat/purchasesunity/ui/PaywallProxyActivity.java index c9e3f8c3..e4e342a8 100644 --- a/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/src/main/java/com/revenuecat/purchasesunity/ui/PaywallProxyActivity.java +++ b/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/src/main/java/com/revenuecat/purchasesunity/ui/PaywallProxyActivity.java @@ -9,9 +9,10 @@ import com.revenuecat.purchases.ui.revenuecatui.activity.PaywallActivityLauncher; import com.revenuecat.purchases.ui.revenuecatui.activity.PaywallResult; +import com.revenuecat.purchases.ui.revenuecatui.activity.PaywallResultHandler; import com.revenuecat.purchases.PresentedOfferingContext; -public class PaywallProxyActivity extends ComponentActivity { +public class PaywallProxyActivity extends ComponentActivity implements PaywallResultHandler { public static final String EXTRA_GAME_OBJECT = "rc_proxy_game_object"; public static final String EXTRA_METHOD = "rc_proxy_method"; public static final String EXTRA_OFFERING_ID = "rc_offering_id"; @@ -22,6 +23,7 @@ public class PaywallProxyActivity extends ComponentActivity { private String gameObject; private String method; + private PaywallActivityLauncher launcher; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { @@ -40,18 +42,7 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { return; } - PaywallActivityLauncher launcher = new PaywallActivityLauncher( - this, - result -> { - try { - if (result != null) { - sendPaywallResult(result); - } - } finally { - finish(); - } - } - ); + launcher = new PaywallActivityLauncher(this, this); if (requiredEntitlementIdentifier != null) { Log.d(TAG, "Using launchIfNeeded for entitlement '" + requiredEntitlementIdentifier + "'"); @@ -110,6 +101,17 @@ private void launchPaywall(PaywallActivityLauncher launcher, String offeringId, } } + @Override + public void onActivityResult(PaywallResult result) { + try { + if (result != null) { + sendPaywallResult(result); + } + } finally { + finish(); + } + } + private void sendResult(String resultName) { Log.d(TAG, "Sending result: " + resultName); runOnUiThread(() -> UnityBridge.sendMessage(gameObject, method, resultName)); From 96d73f6fc329afa6f27487f26c9b81c23eb447d7 Mon Sep 17 00:00:00 2001 From: Cesar de la Vega <664544+vegaro@users.noreply.github.com> Date: Fri, 26 Sep 2025 08:42:54 +0200 Subject: [PATCH 48/65] rename to PaywallTrampolineActivity and made transparent --- .../AndroidManifest.xml | 4 ++-- ...ty.java => PaywallTrampolineActivity.java} | 2 +- .../purchasesunity/ui/RevenueCatUI.java | 22 +++++++++---------- RevenueCatUI/Scripts/RevenueCatUI.cs | 2 +- 4 files changed, 15 insertions(+), 15 deletions(-) rename RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/src/main/java/com/revenuecat/purchasesunity/ui/{PaywallProxyActivity.java => PaywallTrampolineActivity.java} (98%) diff --git a/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/AndroidManifest.xml b/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/AndroidManifest.xml index 3a19e189..cec1e46d 100644 --- a/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/AndroidManifest.xml +++ b/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/AndroidManifest.xml @@ -4,9 +4,9 @@ diff --git a/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/src/main/java/com/revenuecat/purchasesunity/ui/PaywallProxyActivity.java b/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/src/main/java/com/revenuecat/purchasesunity/ui/PaywallTrampolineActivity.java similarity index 98% rename from RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/src/main/java/com/revenuecat/purchasesunity/ui/PaywallProxyActivity.java rename to RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/src/main/java/com/revenuecat/purchasesunity/ui/PaywallTrampolineActivity.java index e4e342a8..ed30b395 100644 --- a/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/src/main/java/com/revenuecat/purchasesunity/ui/PaywallProxyActivity.java +++ b/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/src/main/java/com/revenuecat/purchasesunity/ui/PaywallTrampolineActivity.java @@ -12,7 +12,7 @@ import com.revenuecat.purchases.ui.revenuecatui.activity.PaywallResultHandler; import com.revenuecat.purchases.PresentedOfferingContext; -public class PaywallProxyActivity extends ComponentActivity implements PaywallResultHandler { +public class PaywallTrampolineActivity extends ComponentActivity implements PaywallResultHandler { public static final String EXTRA_GAME_OBJECT = "rc_proxy_game_object"; public static final String EXTRA_METHOD = "rc_proxy_method"; public static final String EXTRA_OFFERING_ID = "rc_offering_id"; diff --git a/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/src/main/java/com/revenuecat/purchasesunity/ui/RevenueCatUI.java b/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/src/main/java/com/revenuecat/purchasesunity/ui/RevenueCatUI.java index 11fb88ae..30b46e29 100644 --- a/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/src/main/java/com/revenuecat/purchasesunity/ui/RevenueCatUI.java +++ b/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/src/main/java/com/revenuecat/purchasesunity/ui/RevenueCatUI.java @@ -1,10 +1,10 @@ package com.revenuecat.purchasesunity.ui; -import static com.revenuecat.purchasesunity.ui.PaywallProxyActivity.EXTRA_GAME_OBJECT; -import static com.revenuecat.purchasesunity.ui.PaywallProxyActivity.EXTRA_METHOD; -import static com.revenuecat.purchasesunity.ui.PaywallProxyActivity.EXTRA_OFFERING_ID; -import static com.revenuecat.purchasesunity.ui.PaywallProxyActivity.EXTRA_SHOULD_DISPLAY_DISMISS_BUTTON; -import static com.revenuecat.purchasesunity.ui.PaywallProxyActivity.EXTRA_REQUIRED_ENTITLEMENT_IDENTIFIER; +import static com.revenuecat.purchasesunity.ui.PaywallTrampolineActivity.EXTRA_GAME_OBJECT; +import static com.revenuecat.purchasesunity.ui.PaywallTrampolineActivity.EXTRA_METHOD; +import static com.revenuecat.purchasesunity.ui.PaywallTrampolineActivity.EXTRA_OFFERING_ID; +import static com.revenuecat.purchasesunity.ui.PaywallTrampolineActivity.EXTRA_SHOULD_DISPLAY_DISMISS_BUTTON; +import static com.revenuecat.purchasesunity.ui.PaywallTrampolineActivity.EXTRA_REQUIRED_ENTITLEMENT_IDENTIFIER; import android.app.Activity; import android.content.Intent; @@ -28,7 +28,7 @@ public static void presentPaywall(String gameObject, String offeringIdentifier, return; } - Intent intent = new Intent(activity, PaywallProxyActivity.class); + Intent intent = new Intent(activity, PaywallTrampolineActivity.class); intent.putExtra(EXTRA_GAME_OBJECT, gameObject); intent.putExtra(EXTRA_METHOD, "OnPaywallResultFromActivity"); @@ -38,11 +38,11 @@ public static void presentPaywall(String gameObject, String offeringIdentifier, intent.putExtra(EXTRA_SHOULD_DISPLAY_DISMISS_BUTTON, displayCloseButton); - Log.d(TAG, "Launching PaywallProxyActivity for gameObject=" + gameObject + + Log.d(TAG, "Launching PaywallTrampolineActivity for gameObject=" + gameObject + ", offering=" + offeringIdentifier + ", displayCloseButton=" + displayCloseButton); activity.startActivity(intent); } catch (Throwable t) { - Log.e(TAG, "Error launching PaywallProxyActivity", t); + Log.e(TAG, "Error launching PaywallTrampolineActivity", t); UnityBridge.sendMessage(gameObject, "OnPaywallResultFromActivity", "ERROR|" + t.getClass().getSimpleName()); } } @@ -59,7 +59,7 @@ public static void presentPaywallIfNeeded(String gameObject, String requiredEnti return; } - Intent intent = new Intent(activity, PaywallProxyActivity.class); + Intent intent = new Intent(activity, PaywallTrampolineActivity.class); intent.putExtra(EXTRA_GAME_OBJECT, gameObject); intent.putExtra(EXTRA_METHOD, "OnPaywallResultFromActivity"); intent.putExtra(EXTRA_REQUIRED_ENTITLEMENT_IDENTIFIER, requiredEntitlementIdentifier); @@ -70,11 +70,11 @@ public static void presentPaywallIfNeeded(String gameObject, String requiredEnti intent.putExtra(EXTRA_SHOULD_DISPLAY_DISMISS_BUTTON, displayCloseButton); - Log.d(TAG, "Launching PaywallProxyActivity for presentPaywallIfNeeded gameObject=" + gameObject + + Log.d(TAG, "Launching PaywallTrampolineActivity for presentPaywallIfNeeded gameObject=" + gameObject + ", entitlement=" + requiredEntitlementIdentifier + ", offering=" + offeringIdentifier + ", displayCloseButton=" + displayCloseButton); activity.startActivity(intent); } catch (Throwable t) { - Log.e(TAG, "Error launching PaywallProxyActivity for presentPaywallIfNeeded", t); + Log.e(TAG, "Error launching PaywallTrampolineActivity for presentPaywallIfNeeded", t); UnityBridge.sendMessage(gameObject, "OnPaywallResultFromActivity", "ERROR|" + t.getClass().getSimpleName()); } } diff --git a/RevenueCatUI/Scripts/RevenueCatUI.cs b/RevenueCatUI/Scripts/RevenueCatUI.cs index 416d7ca5..2bb4e135 100644 --- a/RevenueCatUI/Scripts/RevenueCatUI.cs +++ b/RevenueCatUI/Scripts/RevenueCatUI.cs @@ -91,7 +91,7 @@ public bool IsSupported() } - // Called from PaywallProxyActivity via UnitySendMessage + // Called from PaywallTrampolineActivity via UnitySendMessage private void OnPaywallResultFromActivity(string resultName) { #if UNITY_ANDROID && !UNITY_EDITOR From cf1a839ed41a55571813aaf49678e4bb111262b5 Mon Sep 17 00:00:00 2001 From: Cesar de la Vega <664544+vegaro@users.noreply.github.com> Date: Fri, 26 Sep 2025 08:44:38 +0200 Subject: [PATCH 49/65] remove unneeded comment --- .../main/java/com/revenuecat/purchasesunity/ui/RevenueCatUI.java | 1 - 1 file changed, 1 deletion(-) diff --git a/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/src/main/java/com/revenuecat/purchasesunity/ui/RevenueCatUI.java b/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/src/main/java/com/revenuecat/purchasesunity/ui/RevenueCatUI.java index 30b46e29..2f66970d 100644 --- a/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/src/main/java/com/revenuecat/purchasesunity/ui/RevenueCatUI.java +++ b/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/src/main/java/com/revenuecat/purchasesunity/ui/RevenueCatUI.java @@ -81,7 +81,6 @@ public static void presentPaywallIfNeeded(String gameObject, String requiredEnti public static boolean isSupported() { return true; } - // Keeps your callback path intact if you use it internally (not used by SendMessage flow) private static void sendPaywallResult(String result) { try { PaywallCallbacks cb = paywallCallbacks; From 09651b4a79d1a3f7599fa60f2e346b413da5f1fa Mon Sep 17 00:00:00 2001 From: Cesar de la Vega <664544+vegaro@users.noreply.github.com> Date: Fri, 26 Sep 2025 08:55:01 +0200 Subject: [PATCH 50/65] TODOs on logs --- .../purchasesunity/ui/PaywallTrampolineActivity.java | 9 +++++++++ .../com/revenuecat/purchasesunity/ui/RevenueCatUI.java | 5 +++++ 2 files changed, 14 insertions(+) diff --git a/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/src/main/java/com/revenuecat/purchasesunity/ui/PaywallTrampolineActivity.java b/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/src/main/java/com/revenuecat/purchasesunity/ui/PaywallTrampolineActivity.java index ed30b395..2a98feca 100644 --- a/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/src/main/java/com/revenuecat/purchasesunity/ui/PaywallTrampolineActivity.java +++ b/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/src/main/java/com/revenuecat/purchasesunity/ui/PaywallTrampolineActivity.java @@ -45,19 +45,23 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { launcher = new PaywallActivityLauncher(this, this); if (requiredEntitlementIdentifier != null) { + // TODO: Remove debug log before shipping Log.d(TAG, "Using launchIfNeeded for entitlement '" + requiredEntitlementIdentifier + "'"); launchPaywallIfNeeded(launcher, requiredEntitlementIdentifier, offeringId, shouldDisplayDismissButton); } else { + // TODO: Remove debug log before shipping Log.d(TAG, "No entitlement check required, presenting paywall directly"); launchPaywall(launcher, offeringId, shouldDisplayDismissButton); } } private void launchPaywallIfNeeded(PaywallActivityLauncher launcher, String requiredEntitlementIdentifier, String offeringId, boolean shouldDisplayDismissButton) { + // TODO: Remove debug logs before shipping Log.d(TAG, "Launching paywall if needed with PaywallActivityLauncher"); Log.d(TAG, "Options - entitlement: " + requiredEntitlementIdentifier + ", offering: " + offeringId + ", dismissButton: " + shouldDisplayDismissButton); if (offeringId != null) { + // TODO: Remove debug log before shipping Log.d(TAG, "Using launchIfNeeded with offering ID"); launcher.launchIfNeeded( requiredEntitlementIdentifier, @@ -68,6 +72,7 @@ private void launchPaywallIfNeeded(PaywallActivityLauncher launcher, String requ false, // edgeToEdge paywallDisplayResult -> { if (!paywallDisplayResult) { + // TODO: Remove debug log before shipping Log.d(TAG, "PaywallDisplayCallback: paywall not needed"); sendResult("NOT_PRESENTED"); finish(); @@ -82,16 +87,19 @@ private void launchPaywallIfNeeded(PaywallActivityLauncher launcher, String requ } private void launchPaywall(PaywallActivityLauncher launcher, String offeringId, boolean shouldDisplayDismissButton) { + // TODO: Remove debug logs before shipping Log.d(TAG, "Launching paywall with PaywallActivityLauncher"); Log.d(TAG, "Options - offering: " + offeringId + ", dismissButton: " + shouldDisplayDismissButton); if (offeringId != null) { + // TODO: Remove debug log before shipping Log.d(TAG, "Launching paywall with offering ID"); launcher.launch(offeringId, new PresentedOfferingContext(offeringId), // TODO: pass PresentedOfferingContext data null, // fontProvider shouldDisplayDismissButton); } else { + // TODO: Remove debug log before shipping Log.d(TAG, "Launching paywall with standard method"); launcher.launch( null, // offering (Offering object, not String) @@ -113,6 +121,7 @@ public void onActivityResult(PaywallResult result) { } private void sendResult(String resultName) { + // TODO: Remove debug log before shipping Log.d(TAG, "Sending result: " + resultName); runOnUiThread(() -> UnityBridge.sendMessage(gameObject, method, resultName)); } diff --git a/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/src/main/java/com/revenuecat/purchasesunity/ui/RevenueCatUI.java b/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/src/main/java/com/revenuecat/purchasesunity/ui/RevenueCatUI.java index 2f66970d..8c8853c0 100644 --- a/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/src/main/java/com/revenuecat/purchasesunity/ui/RevenueCatUI.java +++ b/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/src/main/java/com/revenuecat/purchasesunity/ui/RevenueCatUI.java @@ -38,6 +38,7 @@ public static void presentPaywall(String gameObject, String offeringIdentifier, intent.putExtra(EXTRA_SHOULD_DISPLAY_DISMISS_BUTTON, displayCloseButton); + // TODO: Remove debug log before shipping Log.d(TAG, "Launching PaywallTrampolineActivity for gameObject=" + gameObject + ", offering=" + offeringIdentifier + ", displayCloseButton=" + displayCloseButton); activity.startActivity(intent); @@ -48,6 +49,7 @@ public static void presentPaywall(String gameObject, String offeringIdentifier, } public static void presentPaywallIfNeeded(String gameObject, String requiredEntitlementIdentifier, String offeringIdentifier, boolean displayCloseButton) { + // TODO: Remove debug log before shipping Log.d(TAG, "presentPaywallIfNeeded(go=" + gameObject + ", entitlement=" + requiredEntitlementIdentifier + ", offering=" + offeringIdentifier + ", displayCloseButton=" + displayCloseButton + ")"); @@ -70,6 +72,7 @@ public static void presentPaywallIfNeeded(String gameObject, String requiredEnti intent.putExtra(EXTRA_SHOULD_DISPLAY_DISMISS_BUTTON, displayCloseButton); + // TODO: Remove debug log before shipping Log.d(TAG, "Launching PaywallTrampolineActivity for presentPaywallIfNeeded gameObject=" + gameObject + ", entitlement=" + requiredEntitlementIdentifier + ", offering=" + offeringIdentifier + ", displayCloseButton=" + displayCloseButton); activity.startActivity(intent); @@ -85,9 +88,11 @@ private static void sendPaywallResult(String result) { try { PaywallCallbacks cb = paywallCallbacks; if (cb != null) { + // TODO: Remove debug log before shipping Log.d(TAG, "Forwarding result to registered callback: " + result); cb.onPaywallResult(result); } else { + // TODO: Review - keep this warning or remove before shipping? Log.w(TAG, "No callback registered to receive paywall result: " + result); } } catch (Throwable e) { From 80650578166f7419def7391f830f7edc6050bfc7 Mon Sep 17 00:00:00 2001 From: Cesar de la Vega <664544+vegaro@users.noreply.github.com> Date: Fri, 26 Sep 2025 09:51:30 +0200 Subject: [PATCH 51/65] remove UnityBridge --- .../Android/RevenueCatUI.androidlib.meta | 4 ++ .../ui/PaywallTrampolineActivity.java | 41 ++++++------------- .../purchasesunity/ui/RevenueCatUI.java | 32 +++++---------- .../purchasesunity/ui/UnityBridge.java | 28 ------------- .../Android/AndroidPaywallPresenter.cs | 12 +++++- RevenueCatUI/Scripts/RevenueCatUI.cs | 15 ------- 6 files changed, 36 insertions(+), 96 deletions(-) delete mode 100644 RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/src/main/java/com/revenuecat/purchasesunity/ui/UnityBridge.java diff --git a/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib.meta b/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib.meta index 2a3b1281..f4122b4c 100644 --- a/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib.meta +++ b/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib.meta @@ -24,6 +24,7 @@ PluginImporter: Exclude Editor: 0 Exclude Linux64: 0 Exclude OSXUniversal: 0 + Exclude WebGL: 0 Exclude Win: 0 Exclude Win64: 0 Exclude iOS: 0 @@ -41,6 +42,9 @@ PluginImporter: enabled: 1 settings: CPU: None + WebGL: + enabled: 1 + settings: {} Win: enabled: 1 settings: diff --git a/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/src/main/java/com/revenuecat/purchasesunity/ui/PaywallTrampolineActivity.java b/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/src/main/java/com/revenuecat/purchasesunity/ui/PaywallTrampolineActivity.java index 2a98feca..84e4cf26 100644 --- a/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/src/main/java/com/revenuecat/purchasesunity/ui/PaywallTrampolineActivity.java +++ b/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/src/main/java/com/revenuecat/purchasesunity/ui/PaywallTrampolineActivity.java @@ -13,16 +13,12 @@ import com.revenuecat.purchases.PresentedOfferingContext; public class PaywallTrampolineActivity extends ComponentActivity implements PaywallResultHandler { - public static final String EXTRA_GAME_OBJECT = "rc_proxy_game_object"; - public static final String EXTRA_METHOD = "rc_proxy_method"; public static final String EXTRA_OFFERING_ID = "rc_offering_id"; public static final String EXTRA_SHOULD_DISPLAY_DISMISS_BUTTON = "rc_should_display_dismiss_button"; public static final String EXTRA_REQUIRED_ENTITLEMENT_IDENTIFIER = "rc_required_entitlement_identifier"; private static final String TAG = "PurchasesUnity"; - private String gameObject; - private String method; private PaywallActivityLauncher launcher; @Override @@ -30,32 +26,24 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); final Intent source = getIntent(); - gameObject = source.getStringExtra(EXTRA_GAME_OBJECT); - method = source.getStringExtra(EXTRA_METHOD); String offeringId = source.getStringExtra(EXTRA_OFFERING_ID); boolean shouldDisplayDismissButton = source.getBooleanExtra(EXTRA_SHOULD_DISPLAY_DISMISS_BUTTON, false); String requiredEntitlementIdentifier = source.getStringExtra(EXTRA_REQUIRED_ENTITLEMENT_IDENTIFIER); - if (gameObject == null || method == null) { - Log.w(TAG, "Missing gameObject/method extras; finishing."); - finish(); - return; - } - launcher = new PaywallActivityLauncher(this, this); if (requiredEntitlementIdentifier != null) { // TODO: Remove debug log before shipping Log.d(TAG, "Using launchIfNeeded for entitlement '" + requiredEntitlementIdentifier + "'"); - launchPaywallIfNeeded(launcher, requiredEntitlementIdentifier, offeringId, shouldDisplayDismissButton); + launchPaywallIfNeeded(requiredEntitlementIdentifier, offeringId, shouldDisplayDismissButton); } else { // TODO: Remove debug log before shipping Log.d(TAG, "No entitlement check required, presenting paywall directly"); - launchPaywall(launcher, offeringId, shouldDisplayDismissButton); + launchPaywall(offeringId, shouldDisplayDismissButton); } } - private void launchPaywallIfNeeded(PaywallActivityLauncher launcher, String requiredEntitlementIdentifier, String offeringId, boolean shouldDisplayDismissButton) { + private void launchPaywallIfNeeded(String requiredEntitlementIdentifier, String offeringId, boolean shouldDisplayDismissButton) { // TODO: Remove debug logs before shipping Log.d(TAG, "Launching paywall if needed with PaywallActivityLauncher"); Log.d(TAG, "Options - entitlement: " + requiredEntitlementIdentifier + ", offering: " + offeringId + ", dismissButton: " + shouldDisplayDismissButton); @@ -74,7 +62,7 @@ private void launchPaywallIfNeeded(PaywallActivityLauncher launcher, String requ if (!paywallDisplayResult) { // TODO: Remove debug log before shipping Log.d(TAG, "PaywallDisplayCallback: paywall not needed"); - sendResult("NOT_PRESENTED"); + RevenueCatUI.sendPaywallResult("NOT_PRESENTED"); finish(); } // If paywallDisplayResult is true, the paywall will be shown and result will come through normal callback @@ -82,11 +70,11 @@ private void launchPaywallIfNeeded(PaywallActivityLauncher launcher, String requ ); } else { Log.w(TAG, "launchIfNeeded requires an offering ID, falling back to regular launch"); - launchPaywall(launcher, offeringId, shouldDisplayDismissButton); + launchPaywall(offeringId, shouldDisplayDismissButton); } } - private void launchPaywall(PaywallActivityLauncher launcher, String offeringId, boolean shouldDisplayDismissButton) { + private void launchPaywall(String offeringId, boolean shouldDisplayDismissButton) { // TODO: Remove debug logs before shipping Log.d(TAG, "Launching paywall with PaywallActivityLauncher"); Log.d(TAG, "Options - offering: " + offeringId + ", dismissButton: " + shouldDisplayDismissButton); @@ -120,26 +108,21 @@ public void onActivityResult(PaywallResult result) { } } - private void sendResult(String resultName) { - // TODO: Remove debug log before shipping - Log.d(TAG, "Sending result: " + resultName); - runOnUiThread(() -> UnityBridge.sendMessage(gameObject, method, resultName)); - } private void sendPaywallResult(PaywallResult result) { final String resultName; if (result instanceof PaywallResult.Purchased) { - resultName = "purchased"; + resultName = "PURCHASED"; } else if (result instanceof PaywallResult.Restored) { - resultName = "restored"; + resultName = "RESTORED"; } else if (result instanceof PaywallResult.Cancelled) { - resultName = "cancelled"; + resultName = "CANCELLED"; } else if (result instanceof PaywallResult.Error) { - resultName = "error"; + resultName = "ERROR"; } else { - resultName = "cancelled"; + resultName = "CANCELLED"; } - sendResult(resultName); + RevenueCatUI.sendPaywallResult(resultName); } } \ No newline at end of file diff --git a/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/src/main/java/com/revenuecat/purchasesunity/ui/RevenueCatUI.java b/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/src/main/java/com/revenuecat/purchasesunity/ui/RevenueCatUI.java index 8c8853c0..35e6ad90 100644 --- a/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/src/main/java/com/revenuecat/purchasesunity/ui/RevenueCatUI.java +++ b/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/src/main/java/com/revenuecat/purchasesunity/ui/RevenueCatUI.java @@ -1,7 +1,5 @@ package com.revenuecat.purchasesunity.ui; -import static com.revenuecat.purchasesunity.ui.PaywallTrampolineActivity.EXTRA_GAME_OBJECT; -import static com.revenuecat.purchasesunity.ui.PaywallTrampolineActivity.EXTRA_METHOD; import static com.revenuecat.purchasesunity.ui.PaywallTrampolineActivity.EXTRA_OFFERING_ID; import static com.revenuecat.purchasesunity.ui.PaywallTrampolineActivity.EXTRA_SHOULD_DISPLAY_DISMISS_BUTTON; import static com.revenuecat.purchasesunity.ui.PaywallTrampolineActivity.EXTRA_REQUIRED_ENTITLEMENT_IDENTIFIER; @@ -19,18 +17,14 @@ public interface PaywallCallbacks { void onPaywallResult(String result); } public static void registerPaywallCallbacks(PaywallCallbacks cb) { paywallCallbacks = cb; } public static void unregisterPaywallCallbacks() { paywallCallbacks = null; } - public static void presentPaywall(String gameObject, String offeringIdentifier, boolean displayCloseButton) { + public static void presentPaywall(Activity activity, String offeringIdentifier, boolean displayCloseButton) { try { - Activity activity = UnityBridge.currentActivityOrNull(); if (activity == null) { - Log.e(TAG, "currentActivity is null; cannot launch paywall"); - UnityBridge.sendMessage(gameObject, "OnPaywallResultFromActivity", "ERROR|NoActivity"); + Log.e(TAG, "Activity is null; cannot launch paywall"); return; } Intent intent = new Intent(activity, PaywallTrampolineActivity.class); - intent.putExtra(EXTRA_GAME_OBJECT, gameObject); - intent.putExtra(EXTRA_METHOD, "OnPaywallResultFromActivity"); if (offeringIdentifier != null && !offeringIdentifier.isEmpty()) { intent.putExtra(EXTRA_OFFERING_ID, offeringIdentifier); @@ -39,31 +33,26 @@ public static void presentPaywall(String gameObject, String offeringIdentifier, intent.putExtra(EXTRA_SHOULD_DISPLAY_DISMISS_BUTTON, displayCloseButton); // TODO: Remove debug log before shipping - Log.d(TAG, "Launching PaywallTrampolineActivity for gameObject=" + gameObject + - ", offering=" + offeringIdentifier + ", displayCloseButton=" + displayCloseButton); + Log.d(TAG, "Launching PaywallTrampolineActivity offering=" + offeringIdentifier + ", displayCloseButton=" + displayCloseButton); activity.startActivity(intent); } catch (Throwable t) { Log.e(TAG, "Error launching PaywallTrampolineActivity", t); - UnityBridge.sendMessage(gameObject, "OnPaywallResultFromActivity", "ERROR|" + t.getClass().getSimpleName()); + sendPaywallResult("ERROR"); } } - public static void presentPaywallIfNeeded(String gameObject, String requiredEntitlementIdentifier, String offeringIdentifier, boolean displayCloseButton) { + public static void presentPaywallIfNeeded(Activity activity, String requiredEntitlementIdentifier, String offeringIdentifier, boolean displayCloseButton) { // TODO: Remove debug log before shipping - Log.d(TAG, "presentPaywallIfNeeded(go=" + gameObject + ", entitlement=" + + Log.d(TAG, "presentPaywallIfNeeded(entitlement=" + requiredEntitlementIdentifier + ", offering=" + offeringIdentifier + ", displayCloseButton=" + displayCloseButton + ")"); try { - Activity activity = UnityBridge.currentActivityOrNull(); if (activity == null) { - Log.e(TAG, "currentActivity is null; cannot launch paywall"); - UnityBridge.sendMessage(gameObject, "OnPaywallResultFromActivity", "ERROR|NoActivity"); + Log.e(TAG, "Activity is null; cannot launch paywall"); return; } Intent intent = new Intent(activity, PaywallTrampolineActivity.class); - intent.putExtra(EXTRA_GAME_OBJECT, gameObject); - intent.putExtra(EXTRA_METHOD, "OnPaywallResultFromActivity"); intent.putExtra(EXTRA_REQUIRED_ENTITLEMENT_IDENTIFIER, requiredEntitlementIdentifier); if (offeringIdentifier != null && !offeringIdentifier.isEmpty()) { @@ -73,18 +62,17 @@ public static void presentPaywallIfNeeded(String gameObject, String requiredEnti intent.putExtra(EXTRA_SHOULD_DISPLAY_DISMISS_BUTTON, displayCloseButton); // TODO: Remove debug log before shipping - Log.d(TAG, "Launching PaywallTrampolineActivity for presentPaywallIfNeeded gameObject=" + gameObject + - ", entitlement=" + requiredEntitlementIdentifier + ", offering=" + offeringIdentifier + ", displayCloseButton=" + displayCloseButton); + Log.d(TAG, "Launching PaywallTrampolineActivity for presentPaywallIfNeeded entitlement=" + requiredEntitlementIdentifier + ", offering=" + offeringIdentifier + ", displayCloseButton=" + displayCloseButton); activity.startActivity(intent); } catch (Throwable t) { Log.e(TAG, "Error launching PaywallTrampolineActivity for presentPaywallIfNeeded", t); - UnityBridge.sendMessage(gameObject, "OnPaywallResultFromActivity", "ERROR|" + t.getClass().getSimpleName()); + sendPaywallResult("ERROR"); } } public static boolean isSupported() { return true; } - private static void sendPaywallResult(String result) { + public static void sendPaywallResult(String result) { try { PaywallCallbacks cb = paywallCallbacks; if (cb != null) { diff --git a/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/src/main/java/com/revenuecat/purchasesunity/ui/UnityBridge.java b/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/src/main/java/com/revenuecat/purchasesunity/ui/UnityBridge.java deleted file mode 100644 index 6f52099f..00000000 --- a/RevenueCatUI/Plugins/Android/RevenueCatUI.androidlib/src/main/java/com/revenuecat/purchasesunity/ui/UnityBridge.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.revenuecat.purchasesunity.ui; - -import android.app.Activity; -import android.util.Log; - -final class UnityBridge { - private static final String TAG = "UnityBridge"; - private UnityBridge() {} - - static Activity currentActivityOrNull() { - try { - Class up = Class.forName("com.unity3d.player.UnityPlayer"); - return (Activity) up.getField("currentActivity").get(null); - } catch (Throwable t) { - return null; - } - } - - static void sendMessage(String gameObject, String method, String message) { - try { - Class up = Class.forName("com.unity3d.player.UnityPlayer"); - up.getMethod("UnitySendMessage", String.class, String.class, String.class) - .invoke(null, gameObject, method, message); - } catch (Throwable t) { - Log.w(TAG, "UnitySendMessage failed (" + gameObject + "." + method + "): " + message, t); - } - } -} \ No newline at end of file diff --git a/RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs b/RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs index 268917a7..2862ecd2 100644 --- a/RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs +++ b/RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs @@ -44,7 +44,11 @@ public Task PresentPaywallAsync(string gameObjectName, PaywallOpt var displayCloseButton = options?.DisplayCloseButton ?? false; Debug.Log($"[RevenueCatUI][Android] presentPaywall offering='{offering ?? ""}', displayCloseButton={displayCloseButton}"); - _plugin.CallStatic("presentPaywall", gameObjectName, offering, displayCloseButton); + using (var unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer")) + { + var currentActivity = unityPlayer.GetStatic("currentActivity"); + _plugin.CallStatic("presentPaywall", new object[] { currentActivity, offering, displayCloseButton }); + } } catch (Exception e) { @@ -69,7 +73,11 @@ public Task PresentPaywallIfNeededAsync(string gameObjectName, st var offering = options?.OfferingIdentifier; var displayCloseButton = options?.DisplayCloseButton ?? false; Debug.Log($"[RevenueCatUI][Android] presentPaywallIfNeeded entitlement='{requiredEntitlementIdentifier}', offering='{offering ?? ""}', displayCloseButton={displayCloseButton}"); - _plugin.CallStatic("presentPaywallIfNeeded", gameObjectName, requiredEntitlementIdentifier, offering, displayCloseButton); + using (var unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer")) + { + var currentActivity = unityPlayer.GetStatic("currentActivity"); + _plugin.CallStatic("presentPaywallIfNeeded", new object[] { currentActivity, requiredEntitlementIdentifier, offering, displayCloseButton }); + } } catch (Exception e) { diff --git a/RevenueCatUI/Scripts/RevenueCatUI.cs b/RevenueCatUI/Scripts/RevenueCatUI.cs index 2bb4e135..972522a1 100644 --- a/RevenueCatUI/Scripts/RevenueCatUI.cs +++ b/RevenueCatUI/Scripts/RevenueCatUI.cs @@ -90,20 +90,5 @@ public bool IsSupported() } } - - // Called from PaywallTrampolineActivity via UnitySendMessage - private void OnPaywallResultFromActivity(string resultName) - { -#if UNITY_ANDROID && !UNITY_EDITOR - // Convert simple result name to format expected by AndroidPaywallPresenter - string formattedResult = resultName.ToUpper(); - - var presenter = PaywallPresenter.Instance; - if (presenter is Platforms.AndroidPaywallPresenter androidPresenter) - { - androidPresenter.OnPaywallResult(formattedResult); - } -#endif - } } } From 82643ac26dc6368250bc702ee6ac78367c5047cf Mon Sep 17 00:00:00 2001 From: Facundo Menzella Date: Fri, 26 Sep 2025 02:57:01 -0700 Subject: [PATCH 52/65] Paywalls iOS (#675) This is a new implementation for https://github.com/RevenueCat/purchases-unity/pull/671 This PR implements showing Paywalls for iOS, based on https://github.com/RevenueCat/purchases-unity/pull/649 --- RevenueCatUI/Plugins/iOS/RevenueCatUI.m | 169 +++++++++++++++++++++--- 1 file changed, 150 insertions(+), 19 deletions(-) diff --git a/RevenueCatUI/Plugins/iOS/RevenueCatUI.m b/RevenueCatUI/Plugins/iOS/RevenueCatUI.m index 5827e657..f67cdbe7 100644 --- a/RevenueCatUI/Plugins/iOS/RevenueCatUI.m +++ b/RevenueCatUI/Plugins/iOS/RevenueCatUI.m @@ -1,31 +1,162 @@ #import +#import +@import RevenueCat; +@import PurchasesHybridCommonUI; +#import -// Minimal native stubs for iOS bridging +typedef void (*RCUIPaywallResultCallback)(const char *result); -typedef void (*RCUIPaywallResultCallback)(const char* result); +static NSString *const kRCUIOptionRequiredEntitlementIdentifier = @"requiredEntitlementIdentifier"; +static NSString *const kRCUIOptionOfferingIdentifier = @"offeringIdentifier"; +static NSString *const kRCUIOptionDisplayCloseButton = @"displayCloseButton"; -void rcui_presentPaywall(const char* offeringIdentifier, bool displayCloseButton, RCUIPaywallResultCallback callback) { - NSLog(@"[RevenueCatUI][iOS] presentPaywall(offering=%@, closeButton=%@)", - offeringIdentifier ? [NSString stringWithUTF8String:offeringIdentifier] : @"", - displayCloseButton ? @"true" : @"false"); - if (callback) { - callback("CANCELLED|Stub: no native UI"); +static NSString *RCUIStringFromCString(const char *string) { + if (string == NULL) { + return nil; } + return [NSString stringWithUTF8String:string]; } -void rcui_presentPaywallIfNeeded(const char* requiredEntitlementIdentifier, const char* offeringIdentifier, bool displayCloseButton, RCUIPaywallResultCallback callback) { - NSLog(@"[RevenueCatUI][iOS] presentPaywallIfNeeded(entitlement=%@, offering=%@, closeButton=%@)", - requiredEntitlementIdentifier ? [NSString stringWithUTF8String:requiredEntitlementIdentifier] : @"", - offeringIdentifier ? [NSString stringWithUTF8String:offeringIdentifier] : @"", - displayCloseButton ? @"true" : @"false"); - if (callback) { - callback("NOT_PRESENTED|Stub: no native UI"); +static NSString *RCUINormalizedResultToken(NSString *resultName) { + if (resultName.length == 0) { + return @"ERROR"; } + + NSString *token = [resultName uppercaseString]; + + if ([token isEqualToString:@"PURCHASED"]) { + return @"PURCHASED"; + } + if ([token isEqualToString:@"RESTORED"]) { + return @"RESTORED"; + } + if ([token isEqualToString:@"CANCELLED"] || [token isEqualToString:@"USER_CANCELLED"]) { + return @"CANCELLED"; + } + if ([token isEqualToString:@"NOT_PRESENTED"]) { + return @"NOT_PRESENTED"; + } + if ([token isEqualToString:@"ERROR"] || [token isEqualToString:@"FAILED"]) { + return @"ERROR"; + } + + return token; +} + +static BOOL RCUIRuntimeSupportsPaywalls(void) { + if (@available(iOS 15.0, *)) { + return YES; + } + return NO; } -// No Customer Center in this stub +static void RCUIInvokeCallback(RCUIPaywallResultCallback callback, NSString *token, NSString *message) { + if (callback == NULL) { + return; + } + + const char *payload = (token ?: @"ERROR").UTF8String; + + dispatch_async(dispatch_get_main_queue(), ^{ + callback(payload); + }); +} + +static BOOL RCUIEnsureReady(RCUIPaywallResultCallback callback) { + if (!RCUIRuntimeSupportsPaywalls()) { + RCUIInvokeCallback(callback, @"NOT_PRESENTED", @"Requires iOS 15.0+"); + return NO; + } + + if (!RCPurchases.isConfigured) { + RCUIInvokeCallback(callback, @"ERROR", @"PurchasesNotConfigured"); + return NO; + } + + return YES; +} + +static NSMutableDictionary *RCUICreateOptionsDictionary(NSString *offeringIdentifier, BOOL displayCloseButton) { + NSMutableDictionary *options = [NSMutableDictionary new]; + options[kRCUIOptionDisplayCloseButton] = @(displayCloseButton); + + if (offeringIdentifier.length > 0) { + options[kRCUIOptionOfferingIdentifier] = offeringIdentifier; + } + + return options; +} + +static void RCUIPresentPaywallInternal(NSString *offeringIdentifier, BOOL displayCloseButton, RCUIPaywallResultCallback callback) { + dispatch_async(dispatch_get_main_queue(), ^{ + if (@available(iOS 15.0, *)) { + __block PaywallProxy *proxy = [[PaywallProxy alloc] init]; + + NSMutableDictionary *options = RCUICreateOptionsDictionary(offeringIdentifier, displayCloseButton); + + [proxy presentPaywallWithOptions:options + paywallResultHandler:^(NSString * _Nonnull resultName) { + NSString *token = RCUINormalizedResultToken(resultName); + RCUIInvokeCallback(callback, token, nil); + proxy = nil; + }]; + } else { + RCUIInvokeCallback(callback, @"NOT_PRESENTED", @"Requires iOS 15.0+"); + } + }); +} + +static void RCUIPresentPaywallIfNeededInternal(NSString *requiredEntitlementIdentifier, + NSString *offeringIdentifier, + BOOL displayCloseButton, + RCUIPaywallResultCallback callback) { + dispatch_async(dispatch_get_main_queue(), ^{ + if (@available(iOS 15.0, *)) { + __block PaywallProxy *proxy = [[PaywallProxy alloc] init]; + + NSMutableDictionary *options = RCUICreateOptionsDictionary(offeringIdentifier, displayCloseButton); + options[kRCUIOptionRequiredEntitlementIdentifier] = requiredEntitlementIdentifier; + + [proxy presentPaywallIfNeededWithOptions:options + paywallResultHandler:^(NSString * _Nonnull resultName) { + NSString *token = RCUINormalizedResultToken(resultName); + RCUIInvokeCallback(callback, token, nil); + proxy = nil; + }]; + } else { + RCUIInvokeCallback(callback, @"NOT_PRESENTED", @"Requires iOS 15.0+"); + } + }); +} + +void rcui_presentPaywall(const char *offeringIdentifier, bool displayCloseButton, RCUIPaywallResultCallback callback) { + if (!RCUIEnsureReady(callback)) { + return; + } + + NSString *offering = RCUIStringFromCString(offeringIdentifier); + RCUIPresentPaywallInternal(offering, displayCloseButton ? YES : NO, callback); +} + +void rcui_presentPaywallIfNeeded(const char *requiredEntitlementIdentifier, + const char *offeringIdentifier, + bool displayCloseButton, + RCUIPaywallResultCallback callback) { + if (!RCUIEnsureReady(callback)) { + return; + } + + NSString *entitlement = RCUIStringFromCString(requiredEntitlementIdentifier); + NSString *offering = RCUIStringFromCString(offeringIdentifier); + + if (entitlement.length == 0) { + RCUIPresentPaywallInternal(offering, displayCloseButton ? YES : NO, callback); + return; + } + + RCUIPresentPaywallIfNeededInternal(entitlement, offering, displayCloseButton ? YES : NO, callback); +} -bool rcui_isSupported() { - NSLog(@"[RevenueCatUI][iOS] isSupported() -> true (stub)"); - return true; +bool rcui_isSupported(void) { + return RCUIRuntimeSupportsPaywalls(); } From 36133ccd9ee84367cdd30a65a6c2eff55a3fe336 Mon Sep 17 00:00:00 2001 From: Facundo Menzella Date: Fri, 26 Sep 2025 16:04:21 +0200 Subject: [PATCH 53/65] Add UI static approach --- RevenueCatUI/Scripts/IPaywallPresenter.cs | 2 +- RevenueCatUI/Scripts/PaywallOptions.cs | 2 +- RevenueCatUI/Scripts/PaywallPresenter.cs | 2 +- RevenueCatUI/Scripts/PaywallResult.cs | 2 +- .../Android/AndroidPaywallPresenter.cs | 2 +- .../Platforms/iOS/IOSPaywallPresenter.cs | 2 +- RevenueCatUI/Scripts/RevenueCatUI.cs | 94 -------------- RevenueCatUI/Scripts/UI.cs | 120 ++++++++++++++++++ RevenueCatUI/Scripts/UI.cs.meta | 12 ++ RevenueCatUI/Scripts/UIBehaviour.cs | 43 +++++++ ...venueCatUI.cs.meta => UIBehaviour.cs.meta} | 0 .../revenuecat.purchases-unity-ui.asmdef | 3 +- 12 files changed, 182 insertions(+), 102 deletions(-) delete mode 100644 RevenueCatUI/Scripts/RevenueCatUI.cs create mode 100644 RevenueCatUI/Scripts/UI.cs create mode 100644 RevenueCatUI/Scripts/UI.cs.meta create mode 100644 RevenueCatUI/Scripts/UIBehaviour.cs rename RevenueCatUI/Scripts/{RevenueCatUI.cs.meta => UIBehaviour.cs.meta} (100%) diff --git a/RevenueCatUI/Scripts/IPaywallPresenter.cs b/RevenueCatUI/Scripts/IPaywallPresenter.cs index a43aba38..5e574859 100644 --- a/RevenueCatUI/Scripts/IPaywallPresenter.cs +++ b/RevenueCatUI/Scripts/IPaywallPresenter.cs @@ -1,6 +1,6 @@ using System.Threading.Tasks; -namespace RevenueCat.UI +namespace RevenueCat.UnityUI { /// /// Internal interface for presenting paywalls. diff --git a/RevenueCatUI/Scripts/PaywallOptions.cs b/RevenueCatUI/Scripts/PaywallOptions.cs index f1e01519..f2920476 100644 --- a/RevenueCatUI/Scripts/PaywallOptions.cs +++ b/RevenueCatUI/Scripts/PaywallOptions.cs @@ -1,6 +1,6 @@ using System; -namespace RevenueCat.UI +namespace RevenueCat.UnityUI { /// /// Options for configuring paywall presentation. diff --git a/RevenueCatUI/Scripts/PaywallPresenter.cs b/RevenueCatUI/Scripts/PaywallPresenter.cs index eb3cb68b..96be4f53 100644 --- a/RevenueCatUI/Scripts/PaywallPresenter.cs +++ b/RevenueCatUI/Scripts/PaywallPresenter.cs @@ -1,7 +1,7 @@ using System.Threading.Tasks; using UnityEngine; -namespace RevenueCat.UI +namespace RevenueCat.UnityUI { /// /// Platform-agnostic factory for paywall presenters. diff --git a/RevenueCatUI/Scripts/PaywallResult.cs b/RevenueCatUI/Scripts/PaywallResult.cs index 60505790..efe8715f 100644 --- a/RevenueCatUI/Scripts/PaywallResult.cs +++ b/RevenueCatUI/Scripts/PaywallResult.cs @@ -1,6 +1,6 @@ using System; -namespace RevenueCat.UI +namespace RevenueCat.UnityUI { /// /// Represents the result of a paywall presentation. diff --git a/RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs b/RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs index 2862ecd2..35234129 100644 --- a/RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs +++ b/RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs @@ -3,7 +3,7 @@ using System.Threading.Tasks; using UnityEngine; -namespace RevenueCat.UI.Platforms +namespace RevenueCat.UnityUI.Platforms { internal class AndroidPaywallPresenter : IPaywallPresenter { diff --git a/RevenueCatUI/Scripts/Platforms/iOS/IOSPaywallPresenter.cs b/RevenueCatUI/Scripts/Platforms/iOS/IOSPaywallPresenter.cs index 26b16802..3f7fc234 100644 --- a/RevenueCatUI/Scripts/Platforms/iOS/IOSPaywallPresenter.cs +++ b/RevenueCatUI/Scripts/Platforms/iOS/IOSPaywallPresenter.cs @@ -3,7 +3,7 @@ using System.Runtime.InteropServices; using System.Threading.Tasks; -namespace RevenueCat.UI.Platforms +namespace RevenueCat.UnityUI.Platforms { internal class IOSPaywallPresenter : IPaywallPresenter { diff --git a/RevenueCatUI/Scripts/RevenueCatUI.cs b/RevenueCatUI/Scripts/RevenueCatUI.cs deleted file mode 100644 index 972522a1..00000000 --- a/RevenueCatUI/Scripts/RevenueCatUI.cs +++ /dev/null @@ -1,94 +0,0 @@ -using System; -using System.Threading.Tasks; -using UnityEngine; - -namespace RevenueCat.UI -{ - /// - /// Main interface for RevenueCat UI components. - /// Provides methods to present paywalls. - /// Add this component to a GameObject to enable paywall functionality. - /// - public class RevenueCatUI : MonoBehaviour - { - - /// - /// Presents a paywall configured in the RevenueCat dashboard. - /// - /// Options for presenting the paywall - /// A PaywallResult indicating what happened during the paywall presentation - public async Task PresentPaywall(PaywallOptions options = null) - { - try - { - Debug.Log("[RevenueCatUI] Presenting paywall..."); - - var presenter = PaywallPresenter.Instance; - return await presenter.PresentPaywallAsync(gameObject.name, options ?? new PaywallOptions()); - } - catch (Exception e) - { - Debug.LogError($"[RevenueCatUI] Error presenting paywall: {e.Message}"); - return PaywallResult.Error; - } - } - - /// - /// Presents a paywall only if the user does not have the specified entitlement. - /// - /// Entitlement identifier to check before presenting - /// Options for presenting the paywall - /// A PaywallResult indicating what happened during the paywall presentation - public async Task PresentPaywallIfNeeded( - string requiredEntitlementIdentifier, - PaywallOptions options = null) - { - if (string.IsNullOrEmpty(requiredEntitlementIdentifier)) - { - Debug.LogError("[RevenueCatUI] Required entitlement identifier cannot be null or empty"); - return PaywallResult.Error; - } - - try - { - Debug.Log($"[RevenueCatUI] Presenting paywall if needed for entitlement: {requiredEntitlementIdentifier}"); - - var presenter = PaywallPresenter.Instance; - return await presenter.PresentPaywallIfNeededAsync(gameObject.name, requiredEntitlementIdentifier, options ?? new PaywallOptions()); - } - catch (Exception e) - { - Debug.LogError($"[RevenueCatUI] Error presenting paywall if needed: {e.Message}"); - return PaywallResult.Error; - } - } - - - - /// - /// Checks if the Paywall UI is available on the current platform. - /// Returns true on iOS/Android device builds when paywall is supported; - /// returns false on other platforms (Editor, Windows, macOS, WebGL, etc.). - /// - /// True if UI is supported on this platform, otherwise false. - public bool IsSupported() - { - try - { - var paywallPresenter = PaywallPresenter.Instance; - var paywall = paywallPresenter.IsSupported(); - if (Debug.isDebugBuild) - { - Debug.Log($"[RevenueCatUI] IsSupported -> Paywall={paywall}"); - } - return paywall; - } - catch - { - Debug.Log("[RevenueCatUI] IsSupported check threw; returning false"); - return false; - } - } - - } -} diff --git a/RevenueCatUI/Scripts/UI.cs b/RevenueCatUI/Scripts/UI.cs new file mode 100644 index 00000000..b05c7b13 --- /dev/null +++ b/RevenueCatUI/Scripts/UI.cs @@ -0,0 +1,120 @@ +using System; +using System.Threading.Tasks; +using UnityEngine; +using RevenueCat.UnityUI; + +namespace RevenueCat +{ + /// + /// Static entry point for presenting RevenueCat paywalls without requiring a MonoBehaviour component. + /// + public static class UI + { + /// + /// Presents a paywall configured in the RevenueCat dashboard using the default presenter. + /// + /// Optional presentation options. + /// describing the outcome. + public static Task PresentPaywallAsync(PaywallOptions options = null) + { + return PresentPaywallAsyncInternal(null, options); + } + + /// + /// Presents a paywall only if the user lacks the specified entitlement. + /// + /// Entitlement identifier to check before presenting. + /// Optional presentation options. + /// describing the outcome. + public static Task PresentPaywallIfNeededAsync( + string requiredEntitlementIdentifier, + PaywallOptions options = null) + { + if (string.IsNullOrEmpty(requiredEntitlementIdentifier)) + { + Debug.LogError("[RevenueCatUI] Required entitlement identifier cannot be null or empty"); + return Task.FromResult(PaywallResult.Error); + } + + return PresentPaywallIfNeededAsyncInternal(null, requiredEntitlementIdentifier, options); + } + + /// + /// Checks whether paywalls are supported on the current platform. + /// + /// True if supported, otherwise false. + public static bool IsSupported() + { + try + { + return PaywallPresenter.Instance.IsSupported(); + } + catch (Exception e) + { + Debug.LogError($"[RevenueCatUI] Error checking paywall support: {e.Message}"); + return false; + } + } + + internal static Task PresentPaywallAsync(string gameObjectName, PaywallOptions options = null) + { + return PresentPaywallAsyncInternal(gameObjectName, options); + } + + internal static Task PresentPaywallIfNeededAsync( + string gameObjectName, + string requiredEntitlementIdentifier, + PaywallOptions options = null) + { + if (string.IsNullOrEmpty(requiredEntitlementIdentifier)) + { + Debug.LogError("[RevenueCatUI] Required entitlement identifier cannot be null or empty"); + return Task.FromResult(PaywallResult.Error); + } + + return PresentPaywallIfNeededAsyncInternal(gameObjectName, requiredEntitlementIdentifier, options); + } + + private static Task PresentPaywallAsyncInternal(string gameObjectName, PaywallOptions options) + { + try + { + if (Debug.isDebugBuild) + { + Debug.Log("[RevenueCatUI] Presenting paywall..."); + } + + return PaywallPresenter.Instance.PresentPaywallAsync(gameObjectName, options ?? new PaywallOptions()); + } + catch (Exception e) + { + Debug.LogError($"[RevenueCatUI] Error presenting paywall: {e.Message}"); + return Task.FromResult(PaywallResult.Error); + } + } + + private static Task PresentPaywallIfNeededAsyncInternal( + string gameObjectName, + string requiredEntitlementIdentifier, + PaywallOptions options) + { + try + { + if (Debug.isDebugBuild) + { + Debug.Log($"[RevenueCatUI] Presenting paywall if needed for entitlement: {requiredEntitlementIdentifier}"); + } + + return PaywallPresenter.Instance.PresentPaywallIfNeededAsync( + gameObjectName, + requiredEntitlementIdentifier, + options ?? new PaywallOptions()); + } + catch (Exception e) + { + Debug.LogError($"[RevenueCatUI] Error presenting paywall if needed: {e.Message}"); + return Task.FromResult(PaywallResult.Error); + } + } + } +} diff --git a/RevenueCatUI/Scripts/UI.cs.meta b/RevenueCatUI/Scripts/UI.cs.meta new file mode 100644 index 00000000..2315a6c2 --- /dev/null +++ b/RevenueCatUI/Scripts/UI.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: a364a1333bcd4024936bff2e726ea79a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + diff --git a/RevenueCatUI/Scripts/UIBehaviour.cs b/RevenueCatUI/Scripts/UIBehaviour.cs new file mode 100644 index 00000000..6f4836b7 --- /dev/null +++ b/RevenueCatUI/Scripts/UIBehaviour.cs @@ -0,0 +1,43 @@ +using System.Threading.Tasks; +using UnityEngine; +using RevenueCat.UnityUI; + +namespace RevenueCat +{ + /// + /// MonoBehaviour helper that forwards to the static facade so paywalls can be driven from scenes. + /// + public class UIBehaviour : MonoBehaviour + { + /// + /// Presents a paywall configured in the RevenueCat dashboard. + /// + /// Options for presenting the paywall. + /// A describing the outcome. + public Task PresentPaywall(PaywallOptions options = null) + { + return UI.PresentPaywallAsync(gameObject?.name, options); + } + + /// + /// Presents a paywall only if the user does not have the specified entitlement. + /// + /// Entitlement identifier to check before presenting. + /// Options for presenting the paywall. + /// A describing the outcome. + public Task PresentPaywallIfNeeded(string requiredEntitlementIdentifier, PaywallOptions options = null) + { + return UI.PresentPaywallIfNeededAsync(gameObject?.name, requiredEntitlementIdentifier, options); + } + + /// + /// Checks if the Paywall UI is available on the current platform. + /// Returns true on iOS/Android device builds when paywall is supported; otherwise false. + /// + /// True if UI is supported on this platform, otherwise false. + public bool IsSupported() + { + return UI.IsSupported(); + } + } +} diff --git a/RevenueCatUI/Scripts/RevenueCatUI.cs.meta b/RevenueCatUI/Scripts/UIBehaviour.cs.meta similarity index 100% rename from RevenueCatUI/Scripts/RevenueCatUI.cs.meta rename to RevenueCatUI/Scripts/UIBehaviour.cs.meta diff --git a/RevenueCatUI/Scripts/revenuecat.purchases-unity-ui.asmdef b/RevenueCatUI/Scripts/revenuecat.purchases-unity-ui.asmdef index 40602dde..65c7e5aa 100644 --- a/RevenueCatUI/Scripts/revenuecat.purchases-unity-ui.asmdef +++ b/RevenueCatUI/Scripts/revenuecat.purchases-unity-ui.asmdef @@ -1,6 +1,6 @@ { "name": "revenuecat.purchases-unity-ui", - "rootNamespace": "RevenueCat.UI", + "rootNamespace": "RevenueCat.UnityUI", "references": [ "revenuecat.purchases-unity" ], @@ -15,4 +15,3 @@ "noEngineReferences": false } - From 02dae193df7306ccf6f28463638fb256125404de Mon Sep 17 00:00:00 2001 From: Facundo Menzella Date: Fri, 26 Sep 2025 16:15:14 +0200 Subject: [PATCH 54/65] rename namespace --- RevenueCatUI/Scripts/IPaywallPresenter.cs | 2 +- RevenueCatUI/Scripts/PaywallOptions.cs | 2 +- RevenueCatUI/Scripts/PaywallPresenter.cs | 2 +- RevenueCatUI/Scripts/PaywallResult.cs | 2 +- .../Android/AndroidPaywallPresenter.cs | 2 +- .../Platforms/iOS/IOSPaywallPresenter.cs | 2 +- RevenueCatUI/Scripts/UI.cs | 4 +- RevenueCatUI/Scripts/UIBehaviour.cs | 2 +- .../revenuecat.purchases-unity-ui.asmdef | 3 +- Subtester/Assets/Scripts/PurchasesListener.cs | 148 ++++++++++-------- 10 files changed, 96 insertions(+), 73 deletions(-) diff --git a/RevenueCatUI/Scripts/IPaywallPresenter.cs b/RevenueCatUI/Scripts/IPaywallPresenter.cs index 5e574859..894141e3 100644 --- a/RevenueCatUI/Scripts/IPaywallPresenter.cs +++ b/RevenueCatUI/Scripts/IPaywallPresenter.cs @@ -1,6 +1,6 @@ using System.Threading.Tasks; -namespace RevenueCat.UnityUI +namespace RevenueCat.UIInternal { /// /// Internal interface for presenting paywalls. diff --git a/RevenueCatUI/Scripts/PaywallOptions.cs b/RevenueCatUI/Scripts/PaywallOptions.cs index f2920476..51f290dd 100644 --- a/RevenueCatUI/Scripts/PaywallOptions.cs +++ b/RevenueCatUI/Scripts/PaywallOptions.cs @@ -1,6 +1,6 @@ using System; -namespace RevenueCat.UnityUI +namespace RevenueCat.UIInternal { /// /// Options for configuring paywall presentation. diff --git a/RevenueCatUI/Scripts/PaywallPresenter.cs b/RevenueCatUI/Scripts/PaywallPresenter.cs index 96be4f53..77d6907f 100644 --- a/RevenueCatUI/Scripts/PaywallPresenter.cs +++ b/RevenueCatUI/Scripts/PaywallPresenter.cs @@ -1,7 +1,7 @@ using System.Threading.Tasks; using UnityEngine; -namespace RevenueCat.UnityUI +namespace RevenueCat.UIInternal { /// /// Platform-agnostic factory for paywall presenters. diff --git a/RevenueCatUI/Scripts/PaywallResult.cs b/RevenueCatUI/Scripts/PaywallResult.cs index efe8715f..5bc9b4c1 100644 --- a/RevenueCatUI/Scripts/PaywallResult.cs +++ b/RevenueCatUI/Scripts/PaywallResult.cs @@ -1,6 +1,6 @@ using System; -namespace RevenueCat.UnityUI +namespace RevenueCat.UIInternal { /// /// Represents the result of a paywall presentation. diff --git a/RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs b/RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs index 35234129..2626a02b 100644 --- a/RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs +++ b/RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs @@ -3,7 +3,7 @@ using System.Threading.Tasks; using UnityEngine; -namespace RevenueCat.UnityUI.Platforms +namespace RevenueCat.UIInternal.Platforms { internal class AndroidPaywallPresenter : IPaywallPresenter { diff --git a/RevenueCatUI/Scripts/Platforms/iOS/IOSPaywallPresenter.cs b/RevenueCatUI/Scripts/Platforms/iOS/IOSPaywallPresenter.cs index 3f7fc234..8589c35a 100644 --- a/RevenueCatUI/Scripts/Platforms/iOS/IOSPaywallPresenter.cs +++ b/RevenueCatUI/Scripts/Platforms/iOS/IOSPaywallPresenter.cs @@ -3,7 +3,7 @@ using System.Runtime.InteropServices; using System.Threading.Tasks; -namespace RevenueCat.UnityUI.Platforms +namespace RevenueCat.UIInternal.Platforms { internal class IOSPaywallPresenter : IPaywallPresenter { diff --git a/RevenueCatUI/Scripts/UI.cs b/RevenueCatUI/Scripts/UI.cs index b05c7b13..a7bb9c75 100644 --- a/RevenueCatUI/Scripts/UI.cs +++ b/RevenueCatUI/Scripts/UI.cs @@ -1,7 +1,7 @@ using System; using System.Threading.Tasks; using UnityEngine; -using RevenueCat.UnityUI; +using RevenueCat.UIInternal; namespace RevenueCat { @@ -47,7 +47,7 @@ public static bool IsSupported() { try { - return PaywallPresenter.Instance.IsSupported(); + return PaywallPresenter.Instance.IsSupported(); } catch (Exception e) { diff --git a/RevenueCatUI/Scripts/UIBehaviour.cs b/RevenueCatUI/Scripts/UIBehaviour.cs index 6f4836b7..3c4b22f7 100644 --- a/RevenueCatUI/Scripts/UIBehaviour.cs +++ b/RevenueCatUI/Scripts/UIBehaviour.cs @@ -1,6 +1,6 @@ using System.Threading.Tasks; using UnityEngine; -using RevenueCat.UnityUI; +using RevenueCat.UIInternal; namespace RevenueCat { diff --git a/RevenueCatUI/Scripts/revenuecat.purchases-unity-ui.asmdef b/RevenueCatUI/Scripts/revenuecat.purchases-unity-ui.asmdef index 65c7e5aa..d4de9df4 100644 --- a/RevenueCatUI/Scripts/revenuecat.purchases-unity-ui.asmdef +++ b/RevenueCatUI/Scripts/revenuecat.purchases-unity-ui.asmdef @@ -1,6 +1,6 @@ { "name": "revenuecat.purchases-unity-ui", - "rootNamespace": "RevenueCat.UnityUI", + "rootNamespace": "RevenueCat.UIInternal", "references": [ "revenuecat.purchases-unity" ], @@ -14,4 +14,3 @@ "versionDefines": [], "noEngineReferences": false } - diff --git a/Subtester/Assets/Scripts/PurchasesListener.cs b/Subtester/Assets/Scripts/PurchasesListener.cs index 02bbf230..c1a43c3d 100644 --- a/Subtester/Assets/Scripts/PurchasesListener.cs +++ b/Subtester/Assets/Scripts/PurchasesListener.cs @@ -4,8 +4,9 @@ using UnityEditor; using UnityEngine; using UnityEngine.Events; -using UnityEngine.UI; -using RevenueCat.UI; +using UnityEngine.UI; +using RevenueCat; +using RevenueCat.UIPresentation; public class PurchasesListener : Purchases.UpdatedCustomerInfoListener { @@ -66,9 +67,10 @@ private void Start() CreateButton("Fetch & Redeem WinBack for Product", FetchAndRedeemWinBackForProduct); CreateButton("Purchase Package For WinBack Testing", PurchasePackageForWinBackTesting); CreateButton("Fetch & Redeem WinBack for Package", FetchAndRedeemWinBackForPackage); - CreateButton("Get Storefront", GetStorefront); - CreateButton("Present Paywall", PresentPaywallResult); - CreateButton("Present Paywall with Options", PresentPaywallWithOptions); + CreateButton("Get Storefront", GetStorefront); + CreateButton("Present Paywall", PresentPaywallResult); + CreateButton("Present Paywall (static)", PresentPaywallStaticResult); + CreateButton("Present Paywall with Options", PresentPaywallWithOptions); CreateButton("Present Paywall for Offering", PresentPaywallForOffering); CreateButton("Present Paywall If Needed", PresentPaywallIfNeeded); @@ -208,6 +210,13 @@ void PresentPaywallResult() StartCoroutine(PresentPaywallCoroutine()); } + void PresentPaywallStaticResult() + { + Debug.Log("Subtester: launching paywall using static API"); + if (infoLabel != null) infoLabel.text = "Launching paywall (static API)..."; + StartCoroutine(PresentPaywallStaticCoroutine()); + } + void PresentPaywallWithOptions() { Debug.Log("Subtester: launching paywall with options"); @@ -229,54 +238,69 @@ void PresentPaywallIfNeeded() StartCoroutine(PresentPaywallIfNeededCoroutine()); } - private System.Collections.IEnumerator PresentPaywallCoroutine() - { - var ui = GetComponent(); - if (ui == null) - { - ui = gameObject.AddComponent(); - } - - var task = ui.PresentPaywall(); - while (!task.IsCompleted) { yield return null; } - - var result = task.Result; - Debug.Log("Subtester: paywall result = " + result); - - if (infoLabel != null) - { - string status = GetPaywallResultStatus(result); - - if (result.Result == RevenueCat.UI.PaywallResultType.Purchased || - result.Result == RevenueCat.UI.PaywallResultType.Restored) - { - GetComponent().GetCustomerInfo((customerInfo, error) => { - if (error != null) - { - Debug.LogError("Subtester: Error refreshing customer info after " + result.Result + ": " + error); - } - else - { - Debug.Log("Subtester: Refreshed customer info after " + result.Result); - DisplayCustomerInfo(customerInfo); - } - }); - } - - infoLabel.text = $"Paywall result: {status}"; - Debug.Log($"Subtester: {status}"); - } - } - - private System.Collections.IEnumerator PresentPaywallWithOptionsCoroutine() - { - var ui = GetComponent(); + private System.Collections.IEnumerator PresentPaywallCoroutine() + { + var ui = GetComponent(); + if (ui == null) + { + ui = gameObject.AddComponent(); + } + + var task = ui.PresentPaywall(); + while (!task.IsCompleted) { yield return null; } + + var result = task.Result; + Debug.Log("Subtester: paywall result = " + result); + + if (infoLabel != null) + { + string status = GetPaywallResultStatus(result); + + if (result.Result == PaywallResultType.Purchased || + result.Result == PaywallResultType.Restored) + { + GetComponent().GetCustomerInfo((customerInfo, error) => { + if (error != null) + { + Debug.LogError("Subtester: Error refreshing customer info after " + result.Result + ": " + error); + } + else + { + Debug.Log("Subtester: Refreshed customer info after " + result.Result); + DisplayCustomerInfo(customerInfo); + } + }); + } + + infoLabel.text = $"Paywall result: {status}"; + Debug.Log($"Subtester: {status}"); + } + } + + private System.Collections.IEnumerator PresentPaywallStaticCoroutine() + { + var task = UI.PresentPaywallAsync(); + while (!task.IsCompleted) { yield return null; } + + var result = task.Result; + Debug.Log("Subtester: paywall (static) result = " + result); + + if (infoLabel != null) + { + string status = GetPaywallResultStatus(result); + infoLabel.text = status + " (static API)"; + } + } + + private System.Collections.IEnumerator PresentPaywallWithOptionsCoroutine() + { + var ui = GetComponent(); if (ui == null) { - ui = gameObject.AddComponent(); + ui = gameObject.AddComponent(); } - var options = new RevenueCat.UI.PaywallOptions + var options = new PaywallOptions { DisplayCloseButton = false }; @@ -295,10 +319,10 @@ private System.Collections.IEnumerator PresentPaywallWithOptionsCoroutine() private System.Collections.IEnumerator PresentPaywallForOfferingCoroutine() { - var ui = GetComponent(); + var ui = GetComponent(); if (ui == null) { - ui = gameObject.AddComponent(); + ui = gameObject.AddComponent(); } // First get available offerings to use one as an example @@ -345,7 +369,7 @@ private System.Collections.IEnumerator PresentPaywallForOfferingCoroutine() } // Create options with specific offering - var options = new RevenueCat.UI.PaywallOptions + var options = new PaywallOptions { OfferingIdentifier = offeringId ?? "default", DisplayCloseButton = true @@ -367,10 +391,10 @@ private System.Collections.IEnumerator PresentPaywallForOfferingCoroutine() private System.Collections.IEnumerator PresentPaywallIfNeededCoroutine() { - var ui = GetComponent(); + var ui = GetComponent(); if (ui == null) { - ui = gameObject.AddComponent(); + ui = gameObject.AddComponent(); } // First get available offerings to use one as an example @@ -417,7 +441,7 @@ private System.Collections.IEnumerator PresentPaywallIfNeededCoroutine() } // Create options for the test - var options = new RevenueCat.UI.PaywallOptions + var options = new PaywallOptions { OfferingIdentifier = offeringId ?? "default", DisplayCloseButton = true // Test with close button enabled @@ -438,7 +462,7 @@ private System.Collections.IEnumerator PresentPaywallIfNeededCoroutine() { var status = GetPaywallResultStatus(result); var message = $"PaywallIfNeeded for '{testEntitlement}' result: {status}"; - if (result.Result == RevenueCat.UI.PaywallResultType.NotPresented) + if (result.Result == PaywallResultType.NotPresented) { message += " (User already has entitlement)"; } @@ -446,19 +470,19 @@ private System.Collections.IEnumerator PresentPaywallIfNeededCoroutine() } } - private string GetPaywallResultStatus(RevenueCat.UI.PaywallResult result) + private string GetPaywallResultStatus(PaywallResult result) { switch (result.Result) { - case RevenueCat.UI.PaywallResultType.Purchased: + case PaywallResultType.Purchased: return "PURCHASED - User completed a purchase"; - case RevenueCat.UI.PaywallResultType.Restored: + case PaywallResultType.Restored: return "RESTORED - User restored previous purchases"; - case RevenueCat.UI.PaywallResultType.Cancelled: + case PaywallResultType.Cancelled: return "CANCELLED - User dismissed the paywall"; - case RevenueCat.UI.PaywallResultType.Error: + case PaywallResultType.Error: return "ERROR - An error occurred during paywall"; - case RevenueCat.UI.PaywallResultType.NotPresented: + case PaywallResultType.NotPresented: return "NOT PRESENTED - Paywall was not needed"; default: return $"UNKNOWN - Received: {result}"; From 79a708fb3c49f5faae4e16df5ec09dc864762bf7 Mon Sep 17 00:00:00 2001 From: Facundo Menzella Date: Fri, 3 Oct 2025 14:03:23 +0200 Subject: [PATCH 55/65] Remove gameobjedct --- RevenueCatUI/Scripts/IPaywallPresenter.cs | 6 ++-- RevenueCatUI/Scripts/PaywallPresenter.cs | 4 +-- .../Android/AndroidPaywallPresenter.cs | 4 +-- .../Platforms/iOS/IOSPaywallPresenter.cs | 4 +-- RevenueCatUI/Scripts/RevenueCatUI.cs | 4 +-- RevenueCatUI/Scripts/UI.cs | 28 +++---------------- RevenueCatUI/Scripts/UIBehaviour.cs | 4 +-- RevenueCatUI/Tests.meta | 8 ++++++ RevenueCatUI/Tests/ErrorHandlingTests.cs.meta | 2 ++ .../Tests/PaywallOptionsTests.cs.meta | 2 ++ .../Tests/PaywallPresenterTests.cs.meta | 2 ++ RevenueCatUI/Tests/PaywallResultTests.cs.meta | 2 ++ RevenueCatUI/Tests/UITests.cs.meta | 2 ++ ...uecat.purchases-unity-ui.Tests.asmdef.meta | 7 +++++ 14 files changed, 41 insertions(+), 38 deletions(-) create mode 100644 RevenueCatUI/Tests.meta create mode 100644 RevenueCatUI/Tests/ErrorHandlingTests.cs.meta create mode 100644 RevenueCatUI/Tests/PaywallOptionsTests.cs.meta create mode 100644 RevenueCatUI/Tests/PaywallPresenterTests.cs.meta create mode 100644 RevenueCatUI/Tests/PaywallResultTests.cs.meta create mode 100644 RevenueCatUI/Tests/UITests.cs.meta create mode 100644 RevenueCatUI/Tests/revenuecat.purchases-unity-ui.Tests.asmdef.meta diff --git a/RevenueCatUI/Scripts/IPaywallPresenter.cs b/RevenueCatUI/Scripts/IPaywallPresenter.cs index a43aba38..14bd8897 100644 --- a/RevenueCatUI/Scripts/IPaywallPresenter.cs +++ b/RevenueCatUI/Scripts/IPaywallPresenter.cs @@ -11,19 +11,17 @@ internal interface IPaywallPresenter /// /// Presents a paywall with the given options. /// - /// Name of GameObject to receive callbacks /// Paywall presentation options /// Result of the paywall presentation - Task PresentPaywallAsync(string gameObjectName, PaywallOptions options); + Task PresentPaywallAsync(PaywallOptions options); /// /// Presents a paywall only if the user does not have the specified entitlement. /// - /// Name of GameObject to receive callbacks /// Required entitlement identifier /// Paywall presentation options /// Result of the paywall presentation - Task PresentPaywallIfNeededAsync(string gameObjectName, string requiredEntitlementIdentifier, PaywallOptions options); + Task PresentPaywallIfNeededAsync(string requiredEntitlementIdentifier, PaywallOptions options); /// /// Whether paywall presentation is supported on this platform. diff --git a/RevenueCatUI/Scripts/PaywallPresenter.cs b/RevenueCatUI/Scripts/PaywallPresenter.cs index eb3cb68b..6498a926 100644 --- a/RevenueCatUI/Scripts/PaywallPresenter.cs +++ b/RevenueCatUI/Scripts/PaywallPresenter.cs @@ -43,13 +43,13 @@ private static IPaywallPresenter CreatePlatformPresenter() /// internal class UnsupportedPaywallPresenter : IPaywallPresenter { - public Task PresentPaywallAsync(string gameObjectName, PaywallOptions options) + public Task PresentPaywallAsync(PaywallOptions options) { Debug.LogWarning("[RevenueCatUI] Paywall presentation is not supported on this platform."); return Task.FromResult(PaywallResult.Error); } - public Task PresentPaywallIfNeededAsync(string gameObjectName, string requiredEntitlementIdentifier, PaywallOptions options) + public Task PresentPaywallIfNeededAsync(string requiredEntitlementIdentifier, PaywallOptions options) { Debug.LogWarning("[RevenueCatUI] Paywall presentation is not supported on this platform."); return Task.FromResult(PaywallResult.Error); diff --git a/RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs b/RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs index e6d30066..9a594efb 100644 --- a/RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs +++ b/RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs @@ -38,7 +38,7 @@ public bool IsSupported() catch { return false; } } - public Task PresentPaywallAsync(string gameObjectName, PaywallOptions options) + public Task PresentPaywallAsync(PaywallOptions options) { if (_plugin == null) { @@ -71,7 +71,7 @@ public Task PresentPaywallAsync(string gameObjectName, PaywallOpt return _current.Task; } - public Task PresentPaywallIfNeededAsync(string gameObjectName, string requiredEntitlementIdentifier, PaywallOptions options) + public Task PresentPaywallIfNeededAsync(string requiredEntitlementIdentifier, PaywallOptions options) { if (_plugin == null) { diff --git a/RevenueCatUI/Scripts/Platforms/iOS/IOSPaywallPresenter.cs b/RevenueCatUI/Scripts/Platforms/iOS/IOSPaywallPresenter.cs index 44ed09dc..b87403a2 100644 --- a/RevenueCatUI/Scripts/Platforms/iOS/IOSPaywallPresenter.cs +++ b/RevenueCatUI/Scripts/Platforms/iOS/IOSPaywallPresenter.cs @@ -17,7 +17,7 @@ internal class IOSPaywallPresenter : IPaywallPresenter public bool IsSupported() => rcui_isSupported(); - public Task PresentPaywallAsync(string gameObjectName, PaywallOptions options) + public Task PresentPaywallAsync(PaywallOptions options) { if (s_current != null && !s_current.Task.IsCompleted) { @@ -40,7 +40,7 @@ public Task PresentPaywallAsync(string gameObjectName, PaywallOpt return tcs.Task; } - public Task PresentPaywallIfNeededAsync(string gameObjectName, string requiredEntitlementIdentifier, PaywallOptions options) + public Task PresentPaywallIfNeededAsync(string requiredEntitlementIdentifier, PaywallOptions options) { if (s_current != null && !s_current.Task.IsCompleted) { diff --git a/RevenueCatUI/Scripts/RevenueCatUI.cs b/RevenueCatUI/Scripts/RevenueCatUI.cs index a7556ee5..da8ae84c 100644 --- a/RevenueCatUI/Scripts/RevenueCatUI.cs +++ b/RevenueCatUI/Scripts/RevenueCatUI.cs @@ -23,7 +23,7 @@ public static async Task PresentPaywall(PaywallOptions options = Debug.Log("[RevenueCatUI] Presenting paywall..."); var presenter = PaywallPresenter.Instance; - return await presenter.PresentPaywallAsync(null, options ?? new PaywallOptions()); + return await presenter.PresentPaywallAsync(options ?? new PaywallOptions()); } catch (Exception e) { @@ -53,7 +53,7 @@ public static async Task PresentPaywallIfNeeded( Debug.Log($"[RevenueCatUI] Presenting paywall if needed for entitlement: {requiredEntitlementIdentifier}"); var presenter = PaywallPresenter.Instance; - return await presenter.PresentPaywallIfNeededAsync(null, requiredEntitlementIdentifier, options ?? new PaywallOptions()); + return await presenter.PresentPaywallIfNeededAsync(requiredEntitlementIdentifier, options ?? new PaywallOptions()); } catch (Exception e) { diff --git a/RevenueCatUI/Scripts/UI.cs b/RevenueCatUI/Scripts/UI.cs index da4fa04f..68c4bc9a 100644 --- a/RevenueCatUI/Scripts/UI.cs +++ b/RevenueCatUI/Scripts/UI.cs @@ -17,7 +17,7 @@ public static class UIPresentation /// describing the outcome. public static Task PresentPaywallAsync(PaywallOptions options = null) { - return PresentPaywallAsyncInternal(null, options); + return PresentPaywallAsyncInternal(options); } /// @@ -36,7 +36,7 @@ public static Task PresentPaywallIfNeededAsync( return Task.FromResult(PaywallResult.Error); } - return PresentPaywallIfNeededAsyncInternal(null, requiredEntitlementIdentifier, options); + return PresentPaywallIfNeededAsyncInternal(requiredEntitlementIdentifier, options); } /// @@ -56,26 +56,8 @@ public static bool IsSupported() } } - internal static Task PresentPaywallAsync(string gameObjectName, PaywallOptions options = null) - { - return PresentPaywallAsyncInternal(gameObjectName, options); - } - - internal static Task PresentPaywallIfNeededAsync( - string gameObjectName, - string requiredEntitlementIdentifier, - PaywallOptions options = null) - { - if (string.IsNullOrEmpty(requiredEntitlementIdentifier)) - { - Debug.LogError("[RevenueCatUI] Required entitlement identifier cannot be null or empty"); - return Task.FromResult(PaywallResult.Error); - } - - return PresentPaywallIfNeededAsyncInternal(gameObjectName, requiredEntitlementIdentifier, options); - } - private static Task PresentPaywallAsyncInternal(string gameObjectName, PaywallOptions options) + private static Task PresentPaywallAsyncInternal(PaywallOptions options) { try { @@ -84,7 +66,7 @@ private static Task PresentPaywallAsyncInternal(string gameObject Debug.Log("[RevenueCatUI] Presenting paywall..."); } - return PaywallPresenter.Instance.PresentPaywallAsync(gameObjectName, options ?? new PaywallOptions()); + return PaywallPresenter.Instance.PresentPaywallAsync(options ?? new PaywallOptions()); } catch (Exception e) { @@ -94,7 +76,6 @@ private static Task PresentPaywallAsyncInternal(string gameObject } private static Task PresentPaywallIfNeededAsyncInternal( - string gameObjectName, string requiredEntitlementIdentifier, PaywallOptions options) { @@ -106,7 +87,6 @@ private static Task PresentPaywallIfNeededAsyncInternal( } return PaywallPresenter.Instance.PresentPaywallIfNeededAsync( - gameObjectName, requiredEntitlementIdentifier, options ?? new PaywallOptions()); } diff --git a/RevenueCatUI/Scripts/UIBehaviour.cs b/RevenueCatUI/Scripts/UIBehaviour.cs index 548530d2..cc1b9783 100644 --- a/RevenueCatUI/Scripts/UIBehaviour.cs +++ b/RevenueCatUI/Scripts/UIBehaviour.cs @@ -16,7 +16,7 @@ public class UIBehaviour : MonoBehaviour /// A describing the outcome. public Task PresentPaywall(PaywallOptions options = null) { - return UIPresentation.PresentPaywallAsync(gameObject?.name, options); + return UIPresentation.PresentPaywallAsync(options); } /// @@ -27,7 +27,7 @@ public Task PresentPaywall(PaywallOptions options = null) /// A describing the outcome. public Task PresentPaywallIfNeeded(string requiredEntitlementIdentifier, PaywallOptions options = null) { - return UIPresentation.PresentPaywallIfNeededAsync(gameObject?.name, requiredEntitlementIdentifier, options); + return UIPresentation.PresentPaywallIfNeededAsync(requiredEntitlementIdentifier, options); } /// diff --git a/RevenueCatUI/Tests.meta b/RevenueCatUI/Tests.meta new file mode 100644 index 00000000..5eb0ebd4 --- /dev/null +++ b/RevenueCatUI/Tests.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 2f1995c8609094b00956a30022d11c9e +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/RevenueCatUI/Tests/ErrorHandlingTests.cs.meta b/RevenueCatUI/Tests/ErrorHandlingTests.cs.meta new file mode 100644 index 00000000..f0fe04e6 --- /dev/null +++ b/RevenueCatUI/Tests/ErrorHandlingTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b3e204be7408a4c748d85169a6acb7db \ No newline at end of file diff --git a/RevenueCatUI/Tests/PaywallOptionsTests.cs.meta b/RevenueCatUI/Tests/PaywallOptionsTests.cs.meta new file mode 100644 index 00000000..2dceac2d --- /dev/null +++ b/RevenueCatUI/Tests/PaywallOptionsTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 327cacea9185042ddb380bbcb41d2ad7 \ No newline at end of file diff --git a/RevenueCatUI/Tests/PaywallPresenterTests.cs.meta b/RevenueCatUI/Tests/PaywallPresenterTests.cs.meta new file mode 100644 index 00000000..505e2fc9 --- /dev/null +++ b/RevenueCatUI/Tests/PaywallPresenterTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 8b3a7208757774456b7c9a060ff2033c \ No newline at end of file diff --git a/RevenueCatUI/Tests/PaywallResultTests.cs.meta b/RevenueCatUI/Tests/PaywallResultTests.cs.meta new file mode 100644 index 00000000..47226374 --- /dev/null +++ b/RevenueCatUI/Tests/PaywallResultTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 155bb60c88d104cc2b9449430e0dd240 \ No newline at end of file diff --git a/RevenueCatUI/Tests/UITests.cs.meta b/RevenueCatUI/Tests/UITests.cs.meta new file mode 100644 index 00000000..7933d48d --- /dev/null +++ b/RevenueCatUI/Tests/UITests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: ce66aa858a81544e98b02fd347f9426e \ No newline at end of file diff --git a/RevenueCatUI/Tests/revenuecat.purchases-unity-ui.Tests.asmdef.meta b/RevenueCatUI/Tests/revenuecat.purchases-unity-ui.Tests.asmdef.meta new file mode 100644 index 00000000..b81f1546 --- /dev/null +++ b/RevenueCatUI/Tests/revenuecat.purchases-unity-ui.Tests.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: c95837ba3963a40da8ff47c1a6b832b9 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: From 92d163117ac04f17e3e42a88f5ae8f12674982fc Mon Sep 17 00:00:00 2001 From: Facundo Menzella Date: Fri, 3 Oct 2025 14:14:36 +0200 Subject: [PATCH 56/65] revert RevenueCatUIDependencies.xml --- .../Editor/RevenueCatUIDependencies.xml | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/RevenueCatUI/Plugins/Editor/RevenueCatUIDependencies.xml b/RevenueCatUI/Plugins/Editor/RevenueCatUIDependencies.xml index b83d24ca..c2b6020d 100644 --- a/RevenueCatUI/Plugins/Editor/RevenueCatUIDependencies.xml +++ b/RevenueCatUI/Plugins/Editor/RevenueCatUIDependencies.xml @@ -1,11 +1,12 @@ - + - - - - - - - - - \ No newline at end of file + + // TODO: Automatically update this to the latest version + + + + + + + + \ No newline at end of file From 22640ce43f44eb4c847abf11ad388b3dabbe2b1f Mon Sep 17 00:00:00 2001 From: Facundo Menzella Date: Fri, 3 Oct 2025 14:17:05 +0200 Subject: [PATCH 57/65] delete RevenueCatUI/Tests.meta --- RevenueCatUI/Tests.meta | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 RevenueCatUI/Tests.meta diff --git a/RevenueCatUI/Tests.meta b/RevenueCatUI/Tests.meta deleted file mode 100644 index 5eb0ebd4..00000000 --- a/RevenueCatUI/Tests.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 2f1995c8609094b00956a30022d11c9e -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: From c953a713590038dc62556d23d68cfe22658a2baa Mon Sep 17 00:00:00 2001 From: Facundo Menzella Date: Fri, 3 Oct 2025 14:17:14 +0200 Subject: [PATCH 58/65] delete RevenueCatUI/Tests/UITests.cs.meta --- RevenueCatUI/Tests/UITests.cs.meta | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 RevenueCatUI/Tests/UITests.cs.meta diff --git a/RevenueCatUI/Tests/UITests.cs.meta b/RevenueCatUI/Tests/UITests.cs.meta deleted file mode 100644 index 7933d48d..00000000 --- a/RevenueCatUI/Tests/UITests.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: ce66aa858a81544e98b02fd347f9426e \ No newline at end of file From b5334a51cf05582673ecce53995252af47486603 Mon Sep 17 00:00:00 2001 From: Facundo Menzella Date: Fri, 3 Oct 2025 14:17:22 +0200 Subject: [PATCH 59/65] delete RevenueCatUI/Tests/revenuecat.purchases-unity-ui.Tests.asmdef.meta --- .../Tests/revenuecat.purchases-unity-ui.Tests.asmdef.meta | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 RevenueCatUI/Tests/revenuecat.purchases-unity-ui.Tests.asmdef.meta diff --git a/RevenueCatUI/Tests/revenuecat.purchases-unity-ui.Tests.asmdef.meta b/RevenueCatUI/Tests/revenuecat.purchases-unity-ui.Tests.asmdef.meta deleted file mode 100644 index b81f1546..00000000 --- a/RevenueCatUI/Tests/revenuecat.purchases-unity-ui.Tests.asmdef.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: c95837ba3963a40da8ff47c1a6b832b9 -AssemblyDefinitionImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: From 23b6ea67ba26a5b5780b9fa62e083dc48f550ed4 Mon Sep 17 00:00:00 2001 From: Facundo Menzella Date: Fri, 3 Oct 2025 14:17:46 +0200 Subject: [PATCH 60/65] delte tests --- RevenueCatUI/Tests/ErrorHandlingTests.cs.meta | 2 -- RevenueCatUI/Tests/PaywallOptionsTests.cs.meta | 2 -- RevenueCatUI/Tests/PaywallPresenterTests.cs.meta | 2 -- RevenueCatUI/Tests/PaywallResultTests.cs.meta | 2 -- 4 files changed, 8 deletions(-) delete mode 100644 RevenueCatUI/Tests/ErrorHandlingTests.cs.meta delete mode 100644 RevenueCatUI/Tests/PaywallOptionsTests.cs.meta delete mode 100644 RevenueCatUI/Tests/PaywallPresenterTests.cs.meta delete mode 100644 RevenueCatUI/Tests/PaywallResultTests.cs.meta diff --git a/RevenueCatUI/Tests/ErrorHandlingTests.cs.meta b/RevenueCatUI/Tests/ErrorHandlingTests.cs.meta deleted file mode 100644 index f0fe04e6..00000000 --- a/RevenueCatUI/Tests/ErrorHandlingTests.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: b3e204be7408a4c748d85169a6acb7db \ No newline at end of file diff --git a/RevenueCatUI/Tests/PaywallOptionsTests.cs.meta b/RevenueCatUI/Tests/PaywallOptionsTests.cs.meta deleted file mode 100644 index 2dceac2d..00000000 --- a/RevenueCatUI/Tests/PaywallOptionsTests.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 327cacea9185042ddb380bbcb41d2ad7 \ No newline at end of file diff --git a/RevenueCatUI/Tests/PaywallPresenterTests.cs.meta b/RevenueCatUI/Tests/PaywallPresenterTests.cs.meta deleted file mode 100644 index 505e2fc9..00000000 --- a/RevenueCatUI/Tests/PaywallPresenterTests.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 8b3a7208757774456b7c9a060ff2033c \ No newline at end of file diff --git a/RevenueCatUI/Tests/PaywallResultTests.cs.meta b/RevenueCatUI/Tests/PaywallResultTests.cs.meta deleted file mode 100644 index 47226374..00000000 --- a/RevenueCatUI/Tests/PaywallResultTests.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 155bb60c88d104cc2b9449430e0dd240 \ No newline at end of file From 692be3b154aa1a935f6b01e77eccc371a1a96742 Mon Sep 17 00:00:00 2001 From: Facundo Menzella Date: Fri, 3 Oct 2025 14:25:29 +0200 Subject: [PATCH 61/65] use new API --- RevenueCatUI/Scripts/RevenueCatUI.cs | 52 +++------------------------- 1 file changed, 5 insertions(+), 47 deletions(-) diff --git a/RevenueCatUI/Scripts/RevenueCatUI.cs b/RevenueCatUI/Scripts/RevenueCatUI.cs index da8ae84c..76c1d84d 100644 --- a/RevenueCatUI/Scripts/RevenueCatUI.cs +++ b/RevenueCatUI/Scripts/RevenueCatUI.cs @@ -16,20 +16,9 @@ public static class RevenueCatUI /// /// Options for presenting the paywall /// A PaywallResult indicating what happened during the paywall presentation - public static async Task PresentPaywall(PaywallOptions options = null) + public static Task PresentPaywall(PaywallOptions options = null) { - try - { - Debug.Log("[RevenueCatUI] Presenting paywall..."); - - var presenter = PaywallPresenter.Instance; - return await presenter.PresentPaywallAsync(options ?? new PaywallOptions()); - } - catch (Exception e) - { - Debug.LogError($"[RevenueCatUI] Error presenting paywall: {e.Message}"); - return PaywallResult.Error; - } + return RevenueCat.UIPresentation.PresentPaywallAsync(options); } /// @@ -38,28 +27,11 @@ public static async Task PresentPaywall(PaywallOptions options = /// Entitlement identifier to check before presenting /// Options for presenting the paywall /// A PaywallResult indicating what happened during the paywall presentation - public static async Task PresentPaywallIfNeeded( + public static Task PresentPaywallIfNeeded( string requiredEntitlementIdentifier, PaywallOptions options = null) { - if (string.IsNullOrEmpty(requiredEntitlementIdentifier)) - { - Debug.LogError("[RevenueCatUI] Required entitlement identifier cannot be null or empty"); - return PaywallResult.Error; - } - - try - { - Debug.Log($"[RevenueCatUI] Presenting paywall if needed for entitlement: {requiredEntitlementIdentifier}"); - - var presenter = PaywallPresenter.Instance; - return await presenter.PresentPaywallIfNeededAsync(requiredEntitlementIdentifier, options ?? new PaywallOptions()); - } - catch (Exception e) - { - Debug.LogError($"[RevenueCatUI] Error presenting paywall if needed: {e.Message}"); - return PaywallResult.Error; - } + return RevenueCat.UIPresentation.PresentPaywallIfNeededAsync(requiredEntitlementIdentifier, options); } @@ -72,21 +44,7 @@ public static async Task PresentPaywallIfNeeded( /// True if UI is supported on this platform, otherwise false. public static bool IsSupported() { - try - { - var paywallPresenter = PaywallPresenter.Instance; - var paywall = paywallPresenter.IsSupported(); - if (Debug.isDebugBuild) - { - Debug.Log($"[RevenueCatUI] IsSupported -> Paywall={paywall}"); - } - return paywall; - } - catch - { - Debug.Log("[RevenueCatUI] IsSupported check threw; returning false"); - return false; - } + return RevenueCat.UIPresentation.IsSupported(); } } From 2fe6caac5a16bb3a2ee09bb28bd3ead04a44ef11 Mon Sep 17 00:00:00 2001 From: Facundo Menzella Date: Fri, 3 Oct 2025 14:54:19 +0200 Subject: [PATCH 62/65] last refactor --- RevenueCatUI/Scripts/IPaywallPresenter.cs | 6 +- RevenueCatUI/Scripts/PaywallOptions.cs | 2 +- RevenueCatUI/Scripts/PaywallPresenter.cs | 10 +- RevenueCatUI/Scripts/PaywallResult.cs | 2 +- .../Android/AndroidPaywallPresenter.cs | 3 +- .../Platforms/iOS/IOSPaywallPresenter.cs | 3 +- RevenueCatUI/Scripts/RevenueCatUI.cs | 51 --------- RevenueCatUI/Scripts/RevenueCatUI.cs.meta | 2 - RevenueCatUI/Scripts/UI.cs | 108 +++++++++--------- RevenueCatUI/Scripts/UIBehaviour.cs | 9 +- .../revenuecat.purchases-unity-ui.asmdef | 2 +- Subtester/Assets/Scripts/PurchasesListener.cs | 33 +++--- 12 files changed, 86 insertions(+), 145 deletions(-) delete mode 100644 RevenueCatUI/Scripts/RevenueCatUI.cs delete mode 100644 RevenueCatUI/Scripts/RevenueCatUI.cs.meta diff --git a/RevenueCatUI/Scripts/IPaywallPresenter.cs b/RevenueCatUI/Scripts/IPaywallPresenter.cs index 14bd8897..7fac8d27 100644 --- a/RevenueCatUI/Scripts/IPaywallPresenter.cs +++ b/RevenueCatUI/Scripts/IPaywallPresenter.cs @@ -1,6 +1,6 @@ using System.Threading.Tasks; -namespace RevenueCat.UI +namespace RevenueCat.Internal.UI { /// /// Internal interface for presenting paywalls. @@ -13,7 +13,7 @@ internal interface IPaywallPresenter /// /// Paywall presentation options /// Result of the paywall presentation - Task PresentPaywallAsync(PaywallOptions options); + Task PresentPaywallAsync(RevenueCat.PaywallOptions options); /// /// Presents a paywall only if the user does not have the specified entitlement. @@ -21,7 +21,7 @@ internal interface IPaywallPresenter /// Required entitlement identifier /// Paywall presentation options /// Result of the paywall presentation - Task PresentPaywallIfNeededAsync(string requiredEntitlementIdentifier, PaywallOptions options); + Task PresentPaywallIfNeededAsync(string requiredEntitlementIdentifier, RevenueCat.PaywallOptions options); /// /// Whether paywall presentation is supported on this platform. diff --git a/RevenueCatUI/Scripts/PaywallOptions.cs b/RevenueCatUI/Scripts/PaywallOptions.cs index f1e01519..6a222abc 100644 --- a/RevenueCatUI/Scripts/PaywallOptions.cs +++ b/RevenueCatUI/Scripts/PaywallOptions.cs @@ -1,6 +1,6 @@ using System; -namespace RevenueCat.UI +namespace RevenueCat { /// /// Options for configuring paywall presentation. diff --git a/RevenueCatUI/Scripts/PaywallPresenter.cs b/RevenueCatUI/Scripts/PaywallPresenter.cs index 6498a926..1dc57de2 100644 --- a/RevenueCatUI/Scripts/PaywallPresenter.cs +++ b/RevenueCatUI/Scripts/PaywallPresenter.cs @@ -1,7 +1,7 @@ using System.Threading.Tasks; using UnityEngine; -namespace RevenueCat.UI +namespace RevenueCat.Internal.UI { /// /// Platform-agnostic factory for paywall presenters. @@ -43,16 +43,16 @@ private static IPaywallPresenter CreatePlatformPresenter() /// internal class UnsupportedPaywallPresenter : IPaywallPresenter { - public Task PresentPaywallAsync(PaywallOptions options) + public Task PresentPaywallAsync(RevenueCat.PaywallOptions options) { Debug.LogWarning("[RevenueCatUI] Paywall presentation is not supported on this platform."); - return Task.FromResult(PaywallResult.Error); + return Task.FromResult(RevenueCat.PaywallResult.Error); } - public Task PresentPaywallIfNeededAsync(string requiredEntitlementIdentifier, PaywallOptions options) + public Task PresentPaywallIfNeededAsync(string requiredEntitlementIdentifier, RevenueCat.PaywallOptions options) { Debug.LogWarning("[RevenueCatUI] Paywall presentation is not supported on this platform."); - return Task.FromResult(PaywallResult.Error); + return Task.FromResult(RevenueCat.PaywallResult.Error); } public bool IsSupported() diff --git a/RevenueCatUI/Scripts/PaywallResult.cs b/RevenueCatUI/Scripts/PaywallResult.cs index 60505790..ebc5bcb4 100644 --- a/RevenueCatUI/Scripts/PaywallResult.cs +++ b/RevenueCatUI/Scripts/PaywallResult.cs @@ -1,6 +1,6 @@ using System; -namespace RevenueCat.UI +namespace RevenueCat { /// /// Represents the result of a paywall presentation. diff --git a/RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs b/RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs index 9a594efb..77e8b1c0 100644 --- a/RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs +++ b/RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs @@ -3,8 +3,9 @@ using System.Threading.Tasks; using UnityEngine; using UnityEngine.Android; +using RevenueCat.Internal.UI; -namespace RevenueCat.UI.Platforms +namespace RevenueCat.Internal.UI.Platforms { internal class AndroidPaywallPresenter : IPaywallPresenter { diff --git a/RevenueCatUI/Scripts/Platforms/iOS/IOSPaywallPresenter.cs b/RevenueCatUI/Scripts/Platforms/iOS/IOSPaywallPresenter.cs index b87403a2..ebd6ab36 100644 --- a/RevenueCatUI/Scripts/Platforms/iOS/IOSPaywallPresenter.cs +++ b/RevenueCatUI/Scripts/Platforms/iOS/IOSPaywallPresenter.cs @@ -2,8 +2,9 @@ using System; using System.Runtime.InteropServices; using System.Threading.Tasks; +using RevenueCat.Internal.UI; -namespace RevenueCat.UI.Platforms +namespace RevenueCat.Internal.UI.Platforms { internal class IOSPaywallPresenter : IPaywallPresenter { diff --git a/RevenueCatUI/Scripts/RevenueCatUI.cs b/RevenueCatUI/Scripts/RevenueCatUI.cs deleted file mode 100644 index 76c1d84d..00000000 --- a/RevenueCatUI/Scripts/RevenueCatUI.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using System.Threading.Tasks; -using UnityEngine; - -namespace RevenueCat.UI -{ - /// - /// Main interface for RevenueCat UI components. - /// Provides static methods to present paywalls. - /// - public static class RevenueCatUI - { - - /// - /// Presents a paywall configured in the RevenueCat dashboard. - /// - /// Options for presenting the paywall - /// A PaywallResult indicating what happened during the paywall presentation - public static Task PresentPaywall(PaywallOptions options = null) - { - return RevenueCat.UIPresentation.PresentPaywallAsync(options); - } - - /// - /// Presents a paywall only if the user does not have the specified entitlement. - /// - /// Entitlement identifier to check before presenting - /// Options for presenting the paywall - /// A PaywallResult indicating what happened during the paywall presentation - public static Task PresentPaywallIfNeeded( - string requiredEntitlementIdentifier, - PaywallOptions options = null) - { - return RevenueCat.UIPresentation.PresentPaywallIfNeededAsync(requiredEntitlementIdentifier, options); - } - - - - /// - /// Checks if the Paywall UI is available on the current platform. - /// Returns true on iOS/Android device builds when paywall is supported; - /// returns false on other platforms (Editor, Windows, macOS, WebGL, etc.). - /// - /// True if UI is supported on this platform, otherwise false. - public static bool IsSupported() - { - return RevenueCat.UIPresentation.IsSupported(); - } - - } -} diff --git a/RevenueCatUI/Scripts/RevenueCatUI.cs.meta b/RevenueCatUI/Scripts/RevenueCatUI.cs.meta deleted file mode 100644 index 38ada05e..00000000 --- a/RevenueCatUI/Scripts/RevenueCatUI.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 048d583a8c7954f888c70579386b4865 \ No newline at end of file diff --git a/RevenueCatUI/Scripts/UI.cs b/RevenueCatUI/Scripts/UI.cs index 68c4bc9a..eb97f09c 100644 --- a/RevenueCatUI/Scripts/UI.cs +++ b/RevenueCatUI/Scripts/UI.cs @@ -1,100 +1,94 @@ using System; using System.Threading.Tasks; using UnityEngine; -using RevenueCat.UI; +using RevenueCat.Internal.UI; namespace RevenueCat { /// - /// Static entry point for presenting RevenueCat paywalls without requiring a MonoBehaviour component. + /// Main interface for RevenueCat UI components. + /// Provides static methods to present paywalls. /// - public static class UIPresentation + public static class UI { + /// - /// Presents a paywall configured in the RevenueCat dashboard using the default presenter. + /// Presents a paywall configured in the RevenueCat dashboard. /// - /// Optional presentation options. - /// describing the outcome. - public static Task PresentPaywallAsync(PaywallOptions options = null) + /// Options for presenting the paywall + /// A PaywallResult indicating what happened during the paywall presentation + public static async Task PresentPaywall(PaywallOptions options = null) { - return PresentPaywallAsyncInternal(options); + try + { + Debug.Log("[RevenueCat.UI] Presenting paywall..."); + + var presenter = PaywallPresenter.Instance; + return await presenter.PresentPaywallAsync(options ?? new PaywallOptions()); + } + catch (Exception e) + { + Debug.LogError($"[RevenueCat.UI] Error presenting paywall: {e.Message}"); + return PaywallResult.Error; + } } /// - /// Presents a paywall only if the user lacks the specified entitlement. + /// Presents a paywall only if the user does not have the specified entitlement. /// - /// Entitlement identifier to check before presenting. - /// Optional presentation options. - /// describing the outcome. - public static Task PresentPaywallIfNeededAsync( - string requiredEntitlementIdentifier, + /// Entitlement identifier to check before presenting + /// Options for presenting the paywall + /// A PaywallResult indicating what happened during the paywall presentation + public static async Task PresentPaywallIfNeeded( + string requiredEntitlementIdentifier, PaywallOptions options = null) { if (string.IsNullOrEmpty(requiredEntitlementIdentifier)) { - Debug.LogError("[RevenueCatUI] Required entitlement identifier cannot be null or empty"); - return Task.FromResult(PaywallResult.Error); + Debug.LogError("[RevenueCat.UI] Required entitlement identifier cannot be null or empty"); + return PaywallResult.Error; } - return PresentPaywallIfNeededAsyncInternal(requiredEntitlementIdentifier, options); - } - - /// - /// Checks whether paywalls are supported on the current platform. - /// - /// True if supported, otherwise false. - public static bool IsSupported() - { try { - return PaywallPresenter.Instance.IsSupported(); + Debug.Log($"[RevenueCat.UI] Presenting paywall if needed for entitlement: {requiredEntitlementIdentifier}"); + + var presenter = PaywallPresenter.Instance; + return await presenter.PresentPaywallIfNeededAsync(requiredEntitlementIdentifier, options ?? new PaywallOptions()); } catch (Exception e) { - Debug.LogError($"[RevenueCatUI] Error checking paywall support: {e.Message}"); - return false; + Debug.LogError($"[RevenueCat.UI] Error presenting paywall if needed: {e.Message}"); + return PaywallResult.Error; } } + - private static Task PresentPaywallAsyncInternal(PaywallOptions options) + /// + /// Checks if the Paywall UI is available on the current platform. + /// Returns true on iOS/Android device builds when paywall is supported; + /// returns false on other platforms (Editor, Windows, macOS, WebGL, etc.). + /// + /// True if UI is supported on this platform, otherwise false. + public static bool IsSupported() { try { + var paywallPresenter = PaywallPresenter.Instance; + var paywall = paywallPresenter.IsSupported(); if (Debug.isDebugBuild) { - Debug.Log("[RevenueCatUI] Presenting paywall..."); + Debug.Log($"[RevenueCat.UI] IsSupported -> Paywall={paywall}"); } - - return PaywallPresenter.Instance.PresentPaywallAsync(options ?? new PaywallOptions()); + return paywall; } - catch (Exception e) + catch { - Debug.LogError($"[RevenueCatUI] Error presenting paywall: {e.Message}"); - return Task.FromResult(PaywallResult.Error); + Debug.Log("[RevenueCat.UI] IsSupported check threw; returning false"); + return false; } } - private static Task PresentPaywallIfNeededAsyncInternal( - string requiredEntitlementIdentifier, - PaywallOptions options) - { - try - { - if (Debug.isDebugBuild) - { - Debug.Log($"[RevenueCatUI] Presenting paywall if needed for entitlement: {requiredEntitlementIdentifier}"); - } - - return PaywallPresenter.Instance.PresentPaywallIfNeededAsync( - requiredEntitlementIdentifier, - options ?? new PaywallOptions()); - } - catch (Exception e) - { - Debug.LogError($"[RevenueCatUI] Error presenting paywall if needed: {e.Message}"); - return Task.FromResult(PaywallResult.Error); - } - } } -} +} diff --git a/RevenueCatUI/Scripts/UIBehaviour.cs b/RevenueCatUI/Scripts/UIBehaviour.cs index cc1b9783..f1ff0c13 100644 --- a/RevenueCatUI/Scripts/UIBehaviour.cs +++ b/RevenueCatUI/Scripts/UIBehaviour.cs @@ -1,11 +1,10 @@ using System.Threading.Tasks; using UnityEngine; -using RevenueCat.UI; namespace RevenueCat { /// - /// MonoBehaviour helper that forwards to the static facade so paywalls can be driven from scenes. + /// MonoBehaviour helper that forwards to the static UI API so paywalls can be driven from scenes. /// public class UIBehaviour : MonoBehaviour { @@ -16,7 +15,7 @@ public class UIBehaviour : MonoBehaviour /// A describing the outcome. public Task PresentPaywall(PaywallOptions options = null) { - return UIPresentation.PresentPaywallAsync(options); + return UI.PresentPaywall(options); } /// @@ -27,7 +26,7 @@ public Task PresentPaywall(PaywallOptions options = null) /// A describing the outcome. public Task PresentPaywallIfNeeded(string requiredEntitlementIdentifier, PaywallOptions options = null) { - return UIPresentation.PresentPaywallIfNeededAsync(requiredEntitlementIdentifier, options); + return UI.PresentPaywallIfNeeded(requiredEntitlementIdentifier, options); } /// @@ -37,7 +36,7 @@ public Task PresentPaywallIfNeeded(string requiredEntitlementIden /// True if UI is supported on this platform, otherwise false. public bool IsSupported() { - return UIPresentation.IsSupported(); + return UI.IsSupported(); } } } diff --git a/RevenueCatUI/Scripts/revenuecat.purchases-unity-ui.asmdef b/RevenueCatUI/Scripts/revenuecat.purchases-unity-ui.asmdef index 40602dde..b427cf74 100644 --- a/RevenueCatUI/Scripts/revenuecat.purchases-unity-ui.asmdef +++ b/RevenueCatUI/Scripts/revenuecat.purchases-unity-ui.asmdef @@ -1,6 +1,6 @@ { "name": "revenuecat.purchases-unity-ui", - "rootNamespace": "RevenueCat.UI", + "rootNamespace": "RevenueCat", "references": [ "revenuecat.purchases-unity" ], diff --git a/Subtester/Assets/Scripts/PurchasesListener.cs b/Subtester/Assets/Scripts/PurchasesListener.cs index 32ed4799..12619ab0 100644 --- a/Subtester/Assets/Scripts/PurchasesListener.cs +++ b/Subtester/Assets/Scripts/PurchasesListener.cs @@ -5,7 +5,6 @@ using UnityEngine; using UnityEngine.Events; using UnityEngine.UI; -using RevenueCat.UI; public class PurchasesListener : Purchases.UpdatedCustomerInfoListener { @@ -231,7 +230,7 @@ void PresentPaywallIfNeeded() private System.Collections.IEnumerator PresentPaywallCoroutine() { - var task = RevenueCat.UI.RevenueCatUI.PresentPaywall(); + var task = RevenueCat.UI.PresentPaywall(); while (!task.IsCompleted) { yield return null; } var result = task.Result; @@ -241,8 +240,8 @@ private System.Collections.IEnumerator PresentPaywallCoroutine() { string status = GetPaywallResultStatus(result); - if (result.Result == RevenueCat.UI.PaywallResultType.Purchased || - result.Result == RevenueCat.UI.PaywallResultType.Restored) + if (result.Result == RevenueCat.PaywallResultType.Purchased || + result.Result == RevenueCat.PaywallResultType.Restored) { GetComponent().GetCustomerInfo((customerInfo, error) => { if (error != null) @@ -264,12 +263,12 @@ private System.Collections.IEnumerator PresentPaywallCoroutine() private System.Collections.IEnumerator PresentPaywallWithOptionsCoroutine() { - var options = new RevenueCat.UI.PaywallOptions + var options = new RevenueCat.PaywallOptions { DisplayCloseButton = false }; - var task = RevenueCat.UI.RevenueCatUI.PresentPaywall(options); + var task = RevenueCat.UI.PresentPaywall(options); while (!task.IsCompleted) { yield return null; } var result = task.Result; @@ -327,7 +326,7 @@ private System.Collections.IEnumerator PresentPaywallForOfferingCoroutine() } // Create options with specific offering - var options = new RevenueCat.UI.PaywallOptions + var options = new RevenueCat.PaywallOptions { OfferingIdentifier = offeringId, DisplayCloseButton = true @@ -335,7 +334,7 @@ private System.Collections.IEnumerator PresentPaywallForOfferingCoroutine() Debug.Log($"Subtester: Presenting paywall for offering: {options.OfferingIdentifier}"); - var task = RevenueCat.UI.RevenueCatUI.PresentPaywall(options); + var task = RevenueCat.UI.PresentPaywall(options); while (!task.IsCompleted) { yield return null; } var result = task.Result; @@ -393,7 +392,7 @@ private System.Collections.IEnumerator PresentPaywallIfNeededCoroutine() } // Create options for the test - var options = new RevenueCat.UI.PaywallOptions + var options = new RevenueCat.PaywallOptions { OfferingIdentifier = offeringId, DisplayCloseButton = true // Test with close button enabled @@ -404,7 +403,7 @@ private System.Collections.IEnumerator PresentPaywallIfNeededCoroutine() Debug.Log($"Subtester: Testing presentPaywallIfNeeded for entitlement: {testEntitlement}, offering: {options.OfferingIdentifier}"); - var task = RevenueCat.UI.RevenueCatUI.PresentPaywallIfNeeded(testEntitlement, options); + var task = RevenueCat.UI.PresentPaywallIfNeeded(testEntitlement, options); while (!task.IsCompleted) { yield return null; } var result = task.Result; @@ -414,7 +413,7 @@ private System.Collections.IEnumerator PresentPaywallIfNeededCoroutine() { var status = GetPaywallResultStatus(result); var message = $"PaywallIfNeeded for '{testEntitlement}' result: {status}"; - if (result.Result == RevenueCat.UI.PaywallResultType.NotPresented) + if (result.Result == RevenueCat.PaywallResultType.NotPresented) { message += " (User already has entitlement)"; } @@ -422,19 +421,19 @@ private System.Collections.IEnumerator PresentPaywallIfNeededCoroutine() } } - private string GetPaywallResultStatus(RevenueCat.UI.PaywallResult result) + private string GetPaywallResultStatus(RevenueCat.PaywallResult result) { switch (result.Result) { - case RevenueCat.UI.PaywallResultType.Purchased: + case RevenueCat.PaywallResultType.Purchased: return "PURCHASED - User completed a purchase"; - case RevenueCat.UI.PaywallResultType.Restored: + case RevenueCat.PaywallResultType.Restored: return "RESTORED - User restored previous purchases"; - case RevenueCat.UI.PaywallResultType.Cancelled: + case RevenueCat.PaywallResultType.Cancelled: return "CANCELLED - User dismissed the paywall"; - case RevenueCat.UI.PaywallResultType.Error: + case RevenueCat.PaywallResultType.Error: return "ERROR - An error occurred during paywall"; - case RevenueCat.UI.PaywallResultType.NotPresented: + case RevenueCat.PaywallResultType.NotPresented: return "NOT PRESENTED - Paywall was not needed"; default: return $"UNKNOWN - Received: {result}"; From 9d079061f682df3e5fd628171fb75f9eb2e0cbfa Mon Sep 17 00:00:00 2001 From: Facundo Menzella Date: Fri, 3 Oct 2025 14:59:08 +0200 Subject: [PATCH 63/65] add async --- RevenueCatUI/Scripts/UIBehaviour.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/RevenueCatUI/Scripts/UIBehaviour.cs b/RevenueCatUI/Scripts/UIBehaviour.cs index f1ff0c13..87d3bb7b 100644 --- a/RevenueCatUI/Scripts/UIBehaviour.cs +++ b/RevenueCatUI/Scripts/UIBehaviour.cs @@ -13,9 +13,9 @@ public class UIBehaviour : MonoBehaviour /// /// Options for presenting the paywall. /// A describing the outcome. - public Task PresentPaywall(PaywallOptions options = null) + public async Task PresentPaywall(PaywallOptions options = null) { - return UI.PresentPaywall(options); + return await UI.PresentPaywall(options); } /// @@ -24,9 +24,9 @@ public Task PresentPaywall(PaywallOptions options = null) /// Entitlement identifier to check before presenting. /// Options for presenting the paywall. /// A describing the outcome. - public Task PresentPaywallIfNeeded(string requiredEntitlementIdentifier, PaywallOptions options = null) + public async Task PresentPaywallIfNeeded(string requiredEntitlementIdentifier, PaywallOptions options = null) { - return UI.PresentPaywallIfNeeded(requiredEntitlementIdentifier, options); + return await UI.PresentPaywallIfNeeded(requiredEntitlementIdentifier, options); } /// From d733dda12487cf27eab6ff3165bd7ed4e10f46f8 Mon Sep 17 00:00:00 2001 From: Facundo Menzella Date: Fri, 3 Oct 2025 15:25:28 +0200 Subject: [PATCH 64/65] rename final names ultimate --- RevenueCatUI/Scripts/IPaywallPresenter.cs | 6 ++-- RevenueCatUI/Scripts/PaywallOptions.cs | 2 +- RevenueCatUI/Scripts/PaywallPresenter.cs | 10 +++--- RevenueCatUI/Scripts/PaywallResult.cs | 2 +- .../{UIBehaviour.cs => PaywallsBehaviour.cs} | 12 +++---- .../Scripts/PaywallsBehaviour.cs.meta | 2 ++ .../Android/AndroidPaywallPresenter.cs | 4 +-- .../Platforms/iOS/IOSPaywallPresenter.cs | 4 +-- RevenueCatUI/Scripts/UI.cs | 12 +++---- RevenueCatUI/Scripts/UIBehaviour.cs.meta | 12 ------- .../revenuecat.purchases-unity-ui.asmdef | 2 +- Subtester/Assets/Scripts/PurchasesListener.cs | 32 +++++++++---------- 12 files changed, 45 insertions(+), 55 deletions(-) rename RevenueCatUI/Scripts/{UIBehaviour.cs => PaywallsBehaviour.cs} (77%) create mode 100644 RevenueCatUI/Scripts/PaywallsBehaviour.cs.meta delete mode 100644 RevenueCatUI/Scripts/UIBehaviour.cs.meta diff --git a/RevenueCatUI/Scripts/IPaywallPresenter.cs b/RevenueCatUI/Scripts/IPaywallPresenter.cs index 7fac8d27..4e2c626b 100644 --- a/RevenueCatUI/Scripts/IPaywallPresenter.cs +++ b/RevenueCatUI/Scripts/IPaywallPresenter.cs @@ -1,6 +1,6 @@ using System.Threading.Tasks; -namespace RevenueCat.Internal.UI +namespace RevenueCatUI.Internal { /// /// Internal interface for presenting paywalls. @@ -13,7 +13,7 @@ internal interface IPaywallPresenter /// /// Paywall presentation options /// Result of the paywall presentation - Task PresentPaywallAsync(RevenueCat.PaywallOptions options); + Task PresentPaywallAsync(RevenueCatUI.PaywallOptions options); /// /// Presents a paywall only if the user does not have the specified entitlement. @@ -21,7 +21,7 @@ internal interface IPaywallPresenter /// Required entitlement identifier /// Paywall presentation options /// Result of the paywall presentation - Task PresentPaywallIfNeededAsync(string requiredEntitlementIdentifier, RevenueCat.PaywallOptions options); + Task PresentPaywallIfNeededAsync(string requiredEntitlementIdentifier, RevenueCatUI.PaywallOptions options); /// /// Whether paywall presentation is supported on this platform. diff --git a/RevenueCatUI/Scripts/PaywallOptions.cs b/RevenueCatUI/Scripts/PaywallOptions.cs index 6a222abc..f4a7d01c 100644 --- a/RevenueCatUI/Scripts/PaywallOptions.cs +++ b/RevenueCatUI/Scripts/PaywallOptions.cs @@ -1,6 +1,6 @@ using System; -namespace RevenueCat +namespace RevenueCatUI { /// /// Options for configuring paywall presentation. diff --git a/RevenueCatUI/Scripts/PaywallPresenter.cs b/RevenueCatUI/Scripts/PaywallPresenter.cs index 1dc57de2..41c4b4b7 100644 --- a/RevenueCatUI/Scripts/PaywallPresenter.cs +++ b/RevenueCatUI/Scripts/PaywallPresenter.cs @@ -1,7 +1,7 @@ using System.Threading.Tasks; using UnityEngine; -namespace RevenueCat.Internal.UI +namespace RevenueCatUI.Internal { /// /// Platform-agnostic factory for paywall presenters. @@ -43,16 +43,16 @@ private static IPaywallPresenter CreatePlatformPresenter() /// internal class UnsupportedPaywallPresenter : IPaywallPresenter { - public Task PresentPaywallAsync(RevenueCat.PaywallOptions options) + public Task PresentPaywallAsync(RevenueCatUI.PaywallOptions options) { Debug.LogWarning("[RevenueCatUI] Paywall presentation is not supported on this platform."); - return Task.FromResult(RevenueCat.PaywallResult.Error); + return Task.FromResult(RevenueCatUI.PaywallResult.Error); } - public Task PresentPaywallIfNeededAsync(string requiredEntitlementIdentifier, RevenueCat.PaywallOptions options) + public Task PresentPaywallIfNeededAsync(string requiredEntitlementIdentifier, RevenueCatUI.PaywallOptions options) { Debug.LogWarning("[RevenueCatUI] Paywall presentation is not supported on this platform."); - return Task.FromResult(RevenueCat.PaywallResult.Error); + return Task.FromResult(RevenueCatUI.PaywallResult.Error); } public bool IsSupported() diff --git a/RevenueCatUI/Scripts/PaywallResult.cs b/RevenueCatUI/Scripts/PaywallResult.cs index ebc5bcb4..dc2e8db5 100644 --- a/RevenueCatUI/Scripts/PaywallResult.cs +++ b/RevenueCatUI/Scripts/PaywallResult.cs @@ -1,6 +1,6 @@ using System; -namespace RevenueCat +namespace RevenueCatUI { /// /// Represents the result of a paywall presentation. diff --git a/RevenueCatUI/Scripts/UIBehaviour.cs b/RevenueCatUI/Scripts/PaywallsBehaviour.cs similarity index 77% rename from RevenueCatUI/Scripts/UIBehaviour.cs rename to RevenueCatUI/Scripts/PaywallsBehaviour.cs index 87d3bb7b..f2e1d173 100644 --- a/RevenueCatUI/Scripts/UIBehaviour.cs +++ b/RevenueCatUI/Scripts/PaywallsBehaviour.cs @@ -1,12 +1,12 @@ using System.Threading.Tasks; using UnityEngine; -namespace RevenueCat +namespace RevenueCatUI { /// - /// MonoBehaviour helper that forwards to the static UI API so paywalls can be driven from scenes. + /// MonoBehaviour helper that forwards to the static PaywallsPresenter API so paywalls can be driven from scenes. /// - public class UIBehaviour : MonoBehaviour + public class PaywallsBehaviour : MonoBehaviour { /// /// Presents a paywall configured in the RevenueCat dashboard. @@ -15,7 +15,7 @@ public class UIBehaviour : MonoBehaviour /// A describing the outcome. public async Task PresentPaywall(PaywallOptions options = null) { - return await UI.PresentPaywall(options); + return await PaywallsPresenter.Present(options); } /// @@ -26,7 +26,7 @@ public async Task PresentPaywall(PaywallOptions options = null) /// A describing the outcome. public async Task PresentPaywallIfNeeded(string requiredEntitlementIdentifier, PaywallOptions options = null) { - return await UI.PresentPaywallIfNeeded(requiredEntitlementIdentifier, options); + return await PaywallsPresenter.PresentIfNeeded(requiredEntitlementIdentifier, options); } /// @@ -36,7 +36,7 @@ public async Task PresentPaywallIfNeeded(string requiredEntitleme /// True if UI is supported on this platform, otherwise false. public bool IsSupported() { - return UI.IsSupported(); + return PaywallsPresenter.IsSupported(); } } } diff --git a/RevenueCatUI/Scripts/PaywallsBehaviour.cs.meta b/RevenueCatUI/Scripts/PaywallsBehaviour.cs.meta new file mode 100644 index 00000000..981870c2 --- /dev/null +++ b/RevenueCatUI/Scripts/PaywallsBehaviour.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 8851586298f4940e5aa3c16b6b1c00de \ No newline at end of file diff --git a/RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs b/RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs index 77e8b1c0..67576b12 100644 --- a/RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs +++ b/RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs @@ -3,9 +3,9 @@ using System.Threading.Tasks; using UnityEngine; using UnityEngine.Android; -using RevenueCat.Internal.UI; +using RevenueCatUI.Internal; -namespace RevenueCat.Internal.UI.Platforms +namespace RevenueCatUI.Internal.Platforms { internal class AndroidPaywallPresenter : IPaywallPresenter { diff --git a/RevenueCatUI/Scripts/Platforms/iOS/IOSPaywallPresenter.cs b/RevenueCatUI/Scripts/Platforms/iOS/IOSPaywallPresenter.cs index ebd6ab36..8449accc 100644 --- a/RevenueCatUI/Scripts/Platforms/iOS/IOSPaywallPresenter.cs +++ b/RevenueCatUI/Scripts/Platforms/iOS/IOSPaywallPresenter.cs @@ -2,9 +2,9 @@ using System; using System.Runtime.InteropServices; using System.Threading.Tasks; -using RevenueCat.Internal.UI; +using RevenueCatUI.Internal; -namespace RevenueCat.Internal.UI.Platforms +namespace RevenueCatUI.Internal.Platforms { internal class IOSPaywallPresenter : IPaywallPresenter { diff --git a/RevenueCatUI/Scripts/UI.cs b/RevenueCatUI/Scripts/UI.cs index eb97f09c..be44df38 100644 --- a/RevenueCatUI/Scripts/UI.cs +++ b/RevenueCatUI/Scripts/UI.cs @@ -1,15 +1,15 @@ using System; using System.Threading.Tasks; using UnityEngine; -using RevenueCat.Internal.UI; +using RevenueCatUI.Internal; -namespace RevenueCat +namespace RevenueCatUI { /// - /// Main interface for RevenueCat UI components. + /// Main interface for RevenueCatUI paywall presentation. /// Provides static methods to present paywalls. /// - public static class UI + public static class PaywallsPresenter { /// @@ -17,7 +17,7 @@ public static class UI /// /// Options for presenting the paywall /// A PaywallResult indicating what happened during the paywall presentation - public static async Task PresentPaywall(PaywallOptions options = null) + public static async Task Present(PaywallOptions options = null) { try { @@ -39,7 +39,7 @@ public static async Task PresentPaywall(PaywallOptions options = /// Entitlement identifier to check before presenting /// Options for presenting the paywall /// A PaywallResult indicating what happened during the paywall presentation - public static async Task PresentPaywallIfNeeded( + public static async Task PresentIfNeeded( string requiredEntitlementIdentifier, PaywallOptions options = null) { diff --git a/RevenueCatUI/Scripts/UIBehaviour.cs.meta b/RevenueCatUI/Scripts/UIBehaviour.cs.meta deleted file mode 100644 index 7a23e805..00000000 --- a/RevenueCatUI/Scripts/UIBehaviour.cs.meta +++ /dev/null @@ -1,12 +0,0 @@ -fileFormatVersion: 2 -guid: 2204d27dd3f02469dbaf46f478d096e8 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: - diff --git a/RevenueCatUI/Scripts/revenuecat.purchases-unity-ui.asmdef b/RevenueCatUI/Scripts/revenuecat.purchases-unity-ui.asmdef index b427cf74..35183459 100644 --- a/RevenueCatUI/Scripts/revenuecat.purchases-unity-ui.asmdef +++ b/RevenueCatUI/Scripts/revenuecat.purchases-unity-ui.asmdef @@ -1,6 +1,6 @@ { "name": "revenuecat.purchases-unity-ui", - "rootNamespace": "RevenueCat", + "rootNamespace": "RevenueCatUI", "references": [ "revenuecat.purchases-unity" ], diff --git a/Subtester/Assets/Scripts/PurchasesListener.cs b/Subtester/Assets/Scripts/PurchasesListener.cs index 12619ab0..dd7b9b95 100644 --- a/Subtester/Assets/Scripts/PurchasesListener.cs +++ b/Subtester/Assets/Scripts/PurchasesListener.cs @@ -230,7 +230,7 @@ void PresentPaywallIfNeeded() private System.Collections.IEnumerator PresentPaywallCoroutine() { - var task = RevenueCat.UI.PresentPaywall(); + var task = RevenueCatUI.PaywallsPresenter.Present(); while (!task.IsCompleted) { yield return null; } var result = task.Result; @@ -240,8 +240,8 @@ private System.Collections.IEnumerator PresentPaywallCoroutine() { string status = GetPaywallResultStatus(result); - if (result.Result == RevenueCat.PaywallResultType.Purchased || - result.Result == RevenueCat.PaywallResultType.Restored) + if (result.Result == RevenueCatUI.PaywallResultType.Purchased || + result.Result == RevenueCatUI.PaywallResultType.Restored) { GetComponent().GetCustomerInfo((customerInfo, error) => { if (error != null) @@ -263,12 +263,12 @@ private System.Collections.IEnumerator PresentPaywallCoroutine() private System.Collections.IEnumerator PresentPaywallWithOptionsCoroutine() { - var options = new RevenueCat.PaywallOptions + var options = new RevenueCatUI.PaywallOptions { DisplayCloseButton = false }; - var task = RevenueCat.UI.PresentPaywall(options); + var task = RevenueCatUI.PaywallsPresenter.Present(options); while (!task.IsCompleted) { yield return null; } var result = task.Result; @@ -326,7 +326,7 @@ private System.Collections.IEnumerator PresentPaywallForOfferingCoroutine() } // Create options with specific offering - var options = new RevenueCat.PaywallOptions + var options = new RevenueCatUI.PaywallOptions { OfferingIdentifier = offeringId, DisplayCloseButton = true @@ -334,7 +334,7 @@ private System.Collections.IEnumerator PresentPaywallForOfferingCoroutine() Debug.Log($"Subtester: Presenting paywall for offering: {options.OfferingIdentifier}"); - var task = RevenueCat.UI.PresentPaywall(options); + var task = RevenueCatUI.PaywallsPresenter.Present(options); while (!task.IsCompleted) { yield return null; } var result = task.Result; @@ -392,7 +392,7 @@ private System.Collections.IEnumerator PresentPaywallIfNeededCoroutine() } // Create options for the test - var options = new RevenueCat.PaywallOptions + var options = new RevenueCatUI.PaywallOptions { OfferingIdentifier = offeringId, DisplayCloseButton = true // Test with close button enabled @@ -403,7 +403,7 @@ private System.Collections.IEnumerator PresentPaywallIfNeededCoroutine() Debug.Log($"Subtester: Testing presentPaywallIfNeeded for entitlement: {testEntitlement}, offering: {options.OfferingIdentifier}"); - var task = RevenueCat.UI.PresentPaywallIfNeeded(testEntitlement, options); + var task = RevenueCatUI.PaywallsPresenter.PresentIfNeeded(testEntitlement, options); while (!task.IsCompleted) { yield return null; } var result = task.Result; @@ -413,7 +413,7 @@ private System.Collections.IEnumerator PresentPaywallIfNeededCoroutine() { var status = GetPaywallResultStatus(result); var message = $"PaywallIfNeeded for '{testEntitlement}' result: {status}"; - if (result.Result == RevenueCat.PaywallResultType.NotPresented) + if (result.Result == RevenueCatUI.PaywallResultType.NotPresented) { message += " (User already has entitlement)"; } @@ -421,19 +421,19 @@ private System.Collections.IEnumerator PresentPaywallIfNeededCoroutine() } } - private string GetPaywallResultStatus(RevenueCat.PaywallResult result) + private string GetPaywallResultStatus(RevenueCatUI.PaywallResult result) { switch (result.Result) { - case RevenueCat.PaywallResultType.Purchased: + case RevenueCatUI.PaywallResultType.Purchased: return "PURCHASED - User completed a purchase"; - case RevenueCat.PaywallResultType.Restored: + case RevenueCatUI.PaywallResultType.Restored: return "RESTORED - User restored previous purchases"; - case RevenueCat.PaywallResultType.Cancelled: + case RevenueCatUI.PaywallResultType.Cancelled: return "CANCELLED - User dismissed the paywall"; - case RevenueCat.PaywallResultType.Error: + case RevenueCatUI.PaywallResultType.Error: return "ERROR - An error occurred during paywall"; - case RevenueCat.PaywallResultType.NotPresented: + case RevenueCatUI.PaywallResultType.NotPresented: return "NOT PRESENTED - Paywall was not needed"; default: return $"UNKNOWN - Received: {result}"; From 8e087560336184fdfb90402e54dee2b378dcedd7 Mon Sep 17 00:00:00 2001 From: Facundo Menzella Date: Fri, 3 Oct 2025 15:51:20 +0200 Subject: [PATCH 65/65] nits --- .../Platforms/Android/AndroidPaywallPresenter.cs | 2 +- .../Scripts/Platforms/iOS/IOSPaywallPresenter.cs | 2 +- RevenueCatUI/Scripts/UI.cs | 14 +++++++------- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs b/RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs index 67576b12..9f866e97 100644 --- a/RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs +++ b/RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs @@ -5,7 +5,7 @@ using UnityEngine.Android; using RevenueCatUI.Internal; -namespace RevenueCatUI.Internal.Platforms +namespace RevenueCatUI.Platforms { internal class AndroidPaywallPresenter : IPaywallPresenter { diff --git a/RevenueCatUI/Scripts/Platforms/iOS/IOSPaywallPresenter.cs b/RevenueCatUI/Scripts/Platforms/iOS/IOSPaywallPresenter.cs index 8449accc..287df3f5 100644 --- a/RevenueCatUI/Scripts/Platforms/iOS/IOSPaywallPresenter.cs +++ b/RevenueCatUI/Scripts/Platforms/iOS/IOSPaywallPresenter.cs @@ -4,7 +4,7 @@ using System.Threading.Tasks; using RevenueCatUI.Internal; -namespace RevenueCatUI.Internal.Platforms +namespace RevenueCatUI.Platforms { internal class IOSPaywallPresenter : IPaywallPresenter { diff --git a/RevenueCatUI/Scripts/UI.cs b/RevenueCatUI/Scripts/UI.cs index be44df38..e5fd8424 100644 --- a/RevenueCatUI/Scripts/UI.cs +++ b/RevenueCatUI/Scripts/UI.cs @@ -21,14 +21,14 @@ public static async Task Present(PaywallOptions options = null) { try { - Debug.Log("[RevenueCat.UI] Presenting paywall..."); + Debug.Log("[RevenueCatUI] Presenting paywall..."); var presenter = PaywallPresenter.Instance; return await presenter.PresentPaywallAsync(options ?? new PaywallOptions()); } catch (Exception e) { - Debug.LogError($"[RevenueCat.UI] Error presenting paywall: {e.Message}"); + Debug.LogError($"[RevenueCatUI] Error presenting paywall: {e.Message}"); return PaywallResult.Error; } } @@ -45,20 +45,20 @@ public static async Task PresentIfNeeded( { if (string.IsNullOrEmpty(requiredEntitlementIdentifier)) { - Debug.LogError("[RevenueCat.UI] Required entitlement identifier cannot be null or empty"); + Debug.LogError("[RevenueCatUI] Required entitlement identifier cannot be null or empty"); return PaywallResult.Error; } try { - Debug.Log($"[RevenueCat.UI] Presenting paywall if needed for entitlement: {requiredEntitlementIdentifier}"); + Debug.Log($"[RevenueCatUI] Presenting paywall if needed for entitlement: {requiredEntitlementIdentifier}"); var presenter = PaywallPresenter.Instance; return await presenter.PresentPaywallIfNeededAsync(requiredEntitlementIdentifier, options ?? new PaywallOptions()); } catch (Exception e) { - Debug.LogError($"[RevenueCat.UI] Error presenting paywall if needed: {e.Message}"); + Debug.LogError($"[RevenueCatUI] Error presenting paywall if needed: {e.Message}"); return PaywallResult.Error; } } @@ -79,13 +79,13 @@ public static bool IsSupported() var paywall = paywallPresenter.IsSupported(); if (Debug.isDebugBuild) { - Debug.Log($"[RevenueCat.UI] IsSupported -> Paywall={paywall}"); + Debug.Log($"[RevenueCatUI] IsSupported -> Paywall={paywall}"); } return paywall; } catch { - Debug.Log("[RevenueCat.UI] IsSupported check threw; returning false"); + Debug.Log("[RevenueCatUI] IsSupported check threw; returning false"); return false; } }