diff --git a/react-native-purchases-ui/android/src/main/java/com/revenuecat/purchases/react/ui/BasePaywallViewManager.kt b/react-native-purchases-ui/android/src/main/java/com/revenuecat/purchases/react/ui/BasePaywallViewManager.kt index 4633a935..6751bbf7 100644 --- a/react-native-purchases-ui/android/src/main/java/com/revenuecat/purchases/react/ui/BasePaywallViewManager.kt +++ b/react-native-purchases-ui/android/src/main/java/com/revenuecat/purchases/react/ui/BasePaywallViewManager.kt @@ -6,9 +6,18 @@ import com.facebook.react.bridge.WritableNativeMap import com.facebook.react.common.MapBuilder import com.facebook.react.uimanager.SimpleViewManager import com.facebook.react.uimanager.ThemedReactContext +import com.facebook.react.uimanager.UIManagerHelper import com.facebook.react.uimanager.annotations.ReactProp -import com.facebook.react.uimanager.events.RCTEventEmitter +import com.facebook.react.uimanager.events.Event import com.revenuecat.purchases.hybridcommon.ui.PaywallListenerWrapper +import com.revenuecat.purchases.react.ui.events.OnDismissEvent +import com.revenuecat.purchases.react.ui.events.OnPurchaseCancelledEvent +import com.revenuecat.purchases.react.ui.events.OnPurchaseCompletedEvent +import com.revenuecat.purchases.react.ui.events.OnPurchaseErrorEvent +import com.revenuecat.purchases.react.ui.events.OnPurchaseStartedEvent +import com.revenuecat.purchases.react.ui.events.OnRestoreCompletedEvent +import com.revenuecat.purchases.react.ui.events.OnRestoreErrorEvent +import com.revenuecat.purchases.react.ui.events.OnRestoreStartedEvent import com.revenuecat.purchases.ui.revenuecatui.ExperimentalPreviewRevenueCatUIPurchasesAPI import com.revenuecat.purchases.ui.revenuecatui.fonts.CustomFontProvider @@ -27,14 +36,14 @@ internal abstract class BasePaywallViewManager : SimpleViewManager( override fun getExportedCustomDirectEventTypeConstants(): Map? { return MapBuilder.builder() - .putEvent(PaywallEvent.ON_PURCHASE_STARTED) - .putEvent(PaywallEvent.ON_PURCHASE_COMPLETED) - .putEvent(PaywallEvent.ON_PURCHASE_ERROR) - .putEvent(PaywallEvent.ON_PURCHASE_CANCELLED) - .putEvent(PaywallEvent.ON_RESTORE_STARTED) - .putEvent(PaywallEvent.ON_RESTORE_COMPLETED) - .putEvent(PaywallEvent.ON_RESTORE_ERROR) - .putEvent(PaywallEvent.ON_DISMISS) + .putEvent(PaywallEventName.ON_PURCHASE_STARTED) + .putEvent(PaywallEventName.ON_PURCHASE_COMPLETED) + .putEvent(PaywallEventName.ON_PURCHASE_ERROR) + .putEvent(PaywallEventName.ON_PURCHASE_CANCELLED) + .putEvent(PaywallEventName.ON_RESTORE_STARTED) + .putEvent(PaywallEventName.ON_RESTORE_COMPLETED) + .putEvent(PaywallEventName.ON_RESTORE_ERROR) + .putEvent(PaywallEventName.ON_DISMISS) .build() } @@ -51,7 +60,8 @@ internal abstract class BasePaywallViewManager : SimpleViewManager( } private fun setOfferingIdProp(view: T, props: ReadableMap?) { - val offeringIdentifier = props?.getDynamic(OPTION_OFFERING)?.asMap()?.getString(OFFERING_IDENTIFIER) + val offeringIdentifier = + props?.getDynamic(OPTION_OFFERING)?.asMap()?.getString(OFFERING_IDENTIFIER) offeringIdentifier?.let { setOfferingId(view, it) } @@ -72,67 +82,87 @@ internal abstract class BasePaywallViewManager : SimpleViewManager( } } - // TODO: RCTEventEmitter is deprecated, and RCTModernEventEmitter should be used instead - // but documentation is not clear on how to use it so keeping this for now internal fun createPaywallListenerWrapper( themedReactContext: ThemedReactContext, view: View ) = object : PaywallListenerWrapper() { - override fun onPurchaseStarted(rcPackage: Map) { - val payload = mapOf( - PaywallEventKey.PACKAGE to rcPackage, + val event = OnPurchaseStartedEvent( + surfaceId = view.surfaceId, + viewTag = view.id, + rcPackage ) - emitEvent(themedReactContext, view.id, PaywallEvent.ON_PURCHASE_STARTED, payload) + emitEvent(themedReactContext, view.id, event) } override fun onPurchaseCompleted( customerInfo: Map, storeTransaction: Map ) { - val payload = mapOf( - PaywallEventKey.CUSTOMER_INFO to customerInfo, - PaywallEventKey.STORE_TRANSACTION to storeTransaction + val event = OnPurchaseCompletedEvent( + surfaceId = view.surfaceId, + viewTag = view.id, + customerInfo, + storeTransaction ) - emitEvent(themedReactContext, view.id, PaywallEvent.ON_PURCHASE_COMPLETED, payload) + emitEvent(themedReactContext, view.id, event) } override fun onPurchaseError(error: Map) { - val payload = mapOf(PaywallEventKey.ERROR to error) - emitEvent(themedReactContext, view.id, PaywallEvent.ON_PURCHASE_ERROR, payload) + val event = OnPurchaseErrorEvent( + surfaceId = view.surfaceId, + viewTag = view.id, + error + ) + emitEvent(themedReactContext, view.id, event) } override fun onPurchaseCancelled() { - emitEvent(themedReactContext, view.id, PaywallEvent.ON_PURCHASE_CANCELLED) + val event = OnPurchaseCancelledEvent( + surfaceId = view.surfaceId, + viewTag = view.id, + ) + emitEvent(themedReactContext, view.id, event) } override fun onRestoreStarted() { - emitEvent(themedReactContext, view.id, PaywallEvent.ON_RESTORE_STARTED) + val event = OnRestoreStartedEvent( + surfaceId = view.surfaceId, + viewTag = view.id, + ) + emitEvent(themedReactContext, view.id, event) } override fun onRestoreCompleted(customerInfo: Map) { - val payload = mapOf(PaywallEventKey.CUSTOMER_INFO to customerInfo) - emitEvent(themedReactContext, view.id, PaywallEvent.ON_RESTORE_COMPLETED, payload) + val event = OnRestoreCompletedEvent( + surfaceId = view.surfaceId, + viewTag = view.id, + customerInfo, + ) + emitEvent(themedReactContext, view.id, event) } override fun onRestoreError(error: Map) { - val payload = mapOf(PaywallEventKey.ERROR to error) - emitEvent(themedReactContext, view.id, PaywallEvent.ON_RESTORE_ERROR, payload) + val event = OnRestoreErrorEvent( + surfaceId = view.surfaceId, + viewTag = view.id, + error, + ) + emitEvent(themedReactContext, view.id, event) } } internal fun getDismissHandler( themedReactContext: ThemedReactContext, - view: T - ): (() -> Unit) { - return { - emitEvent(themedReactContext, view.id, PaywallEvent.ON_DISMISS) - } + view: View, + ): (() -> Unit) = { + val event = OnDismissEvent(view.surfaceId, view.id) + emitEvent(themedReactContext, view.id, event) } private fun MapBuilder.Builder.putEvent( - paywallEvent: PaywallEvent + paywallEvent: PaywallEventName ): MapBuilder.Builder { val registrationName = MapBuilder.of("registrationName", paywallEvent.eventName) return this.put(paywallEvent.eventName, registrationName) @@ -148,28 +178,9 @@ internal abstract class BasePaywallViewManager : SimpleViewManager( private fun emitEvent( context: ThemedReactContext, viewId: Int, - event: PaywallEvent, - payload: Map>, - ) { - val convertedPayload = WritableNativeMap().apply { - payload.forEach { (key, value) -> - putMap(key.key, RNPurchasesConverters.convertMapToWriteableMap(value)) - } - } - emitEvent(context, viewId, event, convertedPayload) - } - - @Suppress("DEPRECATION") - private fun emitEvent( - context: ThemedReactContext, - viewId: Int, - event: PaywallEvent, - payload: WritableNativeMap? = null + event: Event<*>, ) { - context.getJSModule(RCTEventEmitter::class.java).receiveEvent( - viewId, - event.eventName, - payload - ) + val eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(context, viewId) + eventDispatcher?.dispatchEvent(event) } } diff --git a/react-native-purchases-ui/android/src/main/java/com/revenuecat/purchases/react/ui/PaywallEvent.kt b/react-native-purchases-ui/android/src/main/java/com/revenuecat/purchases/react/ui/PaywallEventName.kt similarity index 90% rename from react-native-purchases-ui/android/src/main/java/com/revenuecat/purchases/react/ui/PaywallEvent.kt rename to react-native-purchases-ui/android/src/main/java/com/revenuecat/purchases/react/ui/PaywallEventName.kt index 77bddf6b..9fcc15c9 100644 --- a/react-native-purchases-ui/android/src/main/java/com/revenuecat/purchases/react/ui/PaywallEvent.kt +++ b/react-native-purchases-ui/android/src/main/java/com/revenuecat/purchases/react/ui/PaywallEventName.kt @@ -1,6 +1,6 @@ package com.revenuecat.purchases.react.ui -internal enum class PaywallEvent(val eventName: String) { +internal enum class PaywallEventName(val eventName: String) { ON_PURCHASE_STARTED("onPurchaseStarted"), ON_PURCHASE_COMPLETED("onPurchaseCompleted"), ON_PURCHASE_ERROR("onPurchaseError"), diff --git a/react-native-purchases-ui/android/src/main/java/com/revenuecat/purchases/react/ui/PaywallFooterViewManager.kt b/react-native-purchases-ui/android/src/main/java/com/revenuecat/purchases/react/ui/PaywallFooterViewManager.kt index dd2d9cd7..f76b813a 100644 --- a/react-native-purchases-ui/android/src/main/java/com/revenuecat/purchases/react/ui/PaywallFooterViewManager.kt +++ b/react-native-purchases-ui/android/src/main/java/com/revenuecat/purchases/react/ui/PaywallFooterViewManager.kt @@ -55,9 +55,9 @@ internal class PaywallFooterViewManager : BasePaywallViewManager + view.setPaywallListener(createPaywallListenerWrapper(themedReactContext, view)) + view.setDismissHandler(getDismissHandler(themedReactContext, view)) } } diff --git a/react-native-purchases-ui/android/src/main/java/com/revenuecat/purchases/react/ui/PaywallViewManager.kt b/react-native-purchases-ui/android/src/main/java/com/revenuecat/purchases/react/ui/PaywallViewManager.kt index 261057ef..2ca767d2 100644 --- a/react-native-purchases-ui/android/src/main/java/com/revenuecat/purchases/react/ui/PaywallViewManager.kt +++ b/react-native-purchases-ui/android/src/main/java/com/revenuecat/purchases/react/ui/PaywallViewManager.kt @@ -5,6 +5,7 @@ import com.revenuecat.purchases.ui.revenuecatui.ExperimentalPreviewRevenueCatUIP import com.revenuecat.purchases.ui.revenuecatui.fonts.CustomFontProvider import com.revenuecat.purchases.ui.revenuecatui.views.PaywallView + @OptIn(ExperimentalPreviewRevenueCatUIPurchasesAPI::class) internal class PaywallViewManager : BasePaywallViewManager() { @@ -17,9 +18,9 @@ internal class PaywallViewManager : BasePaywallViewManager() { } override fun createViewInstance(themedReactContext: ThemedReactContext): PaywallView { - return PaywallView(themedReactContext).also { - it.setPaywallListener(createPaywallListenerWrapper(themedReactContext, it)) - it.setDismissHandler(getDismissHandler(themedReactContext, it)) + return PaywallView(themedReactContext).also { view -> + view.setPaywallListener(createPaywallListenerWrapper(themedReactContext, view)) + view.setDismissHandler(getDismissHandler(themedReactContext, view)) } } diff --git a/react-native-purchases-ui/android/src/main/java/com/revenuecat/purchases/react/ui/ViewExtensions.kt b/react-native-purchases-ui/android/src/main/java/com/revenuecat/purchases/react/ui/ViewExtensions.kt new file mode 100644 index 00000000..d1ded1e2 --- /dev/null +++ b/react-native-purchases-ui/android/src/main/java/com/revenuecat/purchases/react/ui/ViewExtensions.kt @@ -0,0 +1,7 @@ +package com.revenuecat.purchases.react.ui + +import android.view.View +import com.facebook.react.uimanager.UIManagerHelper + +internal val View.surfaceId + get() = UIManagerHelper.getSurfaceId(this) diff --git a/react-native-purchases-ui/android/src/main/java/com/revenuecat/purchases/react/ui/events/OnDismissEvent.kt b/react-native-purchases-ui/android/src/main/java/com/revenuecat/purchases/react/ui/events/OnDismissEvent.kt new file mode 100644 index 00000000..d31e6e47 --- /dev/null +++ b/react-native-purchases-ui/android/src/main/java/com/revenuecat/purchases/react/ui/events/OnDismissEvent.kt @@ -0,0 +1,13 @@ +package com.revenuecat.purchases.react.ui.events + +import com.revenuecat.purchases.react.ui.PaywallEventKey +import com.revenuecat.purchases.react.ui.PaywallEventName + +internal class OnDismissEvent( + surfaceId: Int, + viewTag: Int, +) : PaywallEvent(surfaceId, viewTag) { + override fun getPaywallEventName() = PaywallEventName.ON_DISMISS + + override fun getPayload(): Map> = emptyMap() +} diff --git a/react-native-purchases-ui/android/src/main/java/com/revenuecat/purchases/react/ui/events/OnPurchaseCancelledEvent.kt b/react-native-purchases-ui/android/src/main/java/com/revenuecat/purchases/react/ui/events/OnPurchaseCancelledEvent.kt new file mode 100644 index 00000000..a8f687a1 --- /dev/null +++ b/react-native-purchases-ui/android/src/main/java/com/revenuecat/purchases/react/ui/events/OnPurchaseCancelledEvent.kt @@ -0,0 +1,13 @@ +package com.revenuecat.purchases.react.ui.events + +import com.revenuecat.purchases.react.ui.PaywallEventKey +import com.revenuecat.purchases.react.ui.PaywallEventName + +internal class OnPurchaseCancelledEvent( + surfaceId: Int, + viewTag: Int, +) : PaywallEvent(surfaceId, viewTag) { + override fun getPaywallEventName() = PaywallEventName.ON_PURCHASE_CANCELLED + + override fun getPayload(): Map> = emptyMap() +} diff --git a/react-native-purchases-ui/android/src/main/java/com/revenuecat/purchases/react/ui/events/OnPurchaseCompletedEvent.kt b/react-native-purchases-ui/android/src/main/java/com/revenuecat/purchases/react/ui/events/OnPurchaseCompletedEvent.kt new file mode 100644 index 00000000..b9826405 --- /dev/null +++ b/react-native-purchases-ui/android/src/main/java/com/revenuecat/purchases/react/ui/events/OnPurchaseCompletedEvent.kt @@ -0,0 +1,18 @@ +package com.revenuecat.purchases.react.ui.events + +import com.revenuecat.purchases.react.ui.PaywallEventKey +import com.revenuecat.purchases.react.ui.PaywallEventName + +internal class OnPurchaseCompletedEvent( + surfaceId: Int, + viewTag: Int, + private val customerInfo: Map, + private val storeTransaction: Map +) : PaywallEvent(surfaceId, viewTag) { + override fun getPaywallEventName() = PaywallEventName.ON_PURCHASE_COMPLETED + + override fun getPayload() = mapOf( + PaywallEventKey.CUSTOMER_INFO to customerInfo, + PaywallEventKey.STORE_TRANSACTION to storeTransaction + ) +} diff --git a/react-native-purchases-ui/android/src/main/java/com/revenuecat/purchases/react/ui/events/OnPurchaseErrorEvent.kt b/react-native-purchases-ui/android/src/main/java/com/revenuecat/purchases/react/ui/events/OnPurchaseErrorEvent.kt new file mode 100644 index 00000000..3993fc4f --- /dev/null +++ b/react-native-purchases-ui/android/src/main/java/com/revenuecat/purchases/react/ui/events/OnPurchaseErrorEvent.kt @@ -0,0 +1,14 @@ +package com.revenuecat.purchases.react.ui.events + +import com.revenuecat.purchases.react.ui.PaywallEventKey +import com.revenuecat.purchases.react.ui.PaywallEventName + +internal class OnPurchaseErrorEvent( + surfaceId: Int, + viewTag: Int, + private val error: Map +) : PaywallEvent(surfaceId, viewTag) { + override fun getPaywallEventName() = PaywallEventName.ON_PURCHASE_ERROR + + override fun getPayload() = mapOf(PaywallEventKey.ERROR to error) +} diff --git a/react-native-purchases-ui/android/src/main/java/com/revenuecat/purchases/react/ui/events/OnPurchaseStartedEvent.kt b/react-native-purchases-ui/android/src/main/java/com/revenuecat/purchases/react/ui/events/OnPurchaseStartedEvent.kt new file mode 100644 index 00000000..c594bbef --- /dev/null +++ b/react-native-purchases-ui/android/src/main/java/com/revenuecat/purchases/react/ui/events/OnPurchaseStartedEvent.kt @@ -0,0 +1,16 @@ +package com.revenuecat.purchases.react.ui.events + +import com.revenuecat.purchases.react.ui.PaywallEventKey +import com.revenuecat.purchases.react.ui.PaywallEventName + +internal class OnPurchaseStartedEvent( + surfaceId: Int, + viewTag: Int, + private val packageMap: Map +) : PaywallEvent(surfaceId, viewTag) { + override fun getPaywallEventName() = PaywallEventName.ON_PURCHASE_STARTED + + override fun getPayload() = mapOf( + PaywallEventKey.PACKAGE to packageMap, + ) +} diff --git a/react-native-purchases-ui/android/src/main/java/com/revenuecat/purchases/react/ui/events/OnRestoreCompletedEvent.kt b/react-native-purchases-ui/android/src/main/java/com/revenuecat/purchases/react/ui/events/OnRestoreCompletedEvent.kt new file mode 100644 index 00000000..b170d184 --- /dev/null +++ b/react-native-purchases-ui/android/src/main/java/com/revenuecat/purchases/react/ui/events/OnRestoreCompletedEvent.kt @@ -0,0 +1,14 @@ +package com.revenuecat.purchases.react.ui.events + +import com.revenuecat.purchases.react.ui.PaywallEventKey +import com.revenuecat.purchases.react.ui.PaywallEventName + +internal class OnRestoreCompletedEvent( + surfaceId: Int, + viewTag: Int, + private val customerInfo: Map, +) : PaywallEvent(surfaceId, viewTag) { + override fun getPaywallEventName() = PaywallEventName.ON_RESTORE_COMPLETED + + override fun getPayload() = mapOf(PaywallEventKey.CUSTOMER_INFO to customerInfo) +} diff --git a/react-native-purchases-ui/android/src/main/java/com/revenuecat/purchases/react/ui/events/OnRestoreErrorEvent.kt b/react-native-purchases-ui/android/src/main/java/com/revenuecat/purchases/react/ui/events/OnRestoreErrorEvent.kt new file mode 100644 index 00000000..55ae1064 --- /dev/null +++ b/react-native-purchases-ui/android/src/main/java/com/revenuecat/purchases/react/ui/events/OnRestoreErrorEvent.kt @@ -0,0 +1,14 @@ +package com.revenuecat.purchases.react.ui.events + +import com.revenuecat.purchases.react.ui.PaywallEventKey +import com.revenuecat.purchases.react.ui.PaywallEventName + +internal class OnRestoreErrorEvent( + surfaceId: Int, + viewTag: Int, + private val error: Map +) : PaywallEvent(surfaceId, viewTag) { + override fun getPaywallEventName() = PaywallEventName.ON_RESTORE_ERROR + + override fun getPayload() = mapOf(PaywallEventKey.ERROR to error) +} diff --git a/react-native-purchases-ui/android/src/main/java/com/revenuecat/purchases/react/ui/events/OnRestoreStartedEvent.kt b/react-native-purchases-ui/android/src/main/java/com/revenuecat/purchases/react/ui/events/OnRestoreStartedEvent.kt new file mode 100644 index 00000000..86dcec34 --- /dev/null +++ b/react-native-purchases-ui/android/src/main/java/com/revenuecat/purchases/react/ui/events/OnRestoreStartedEvent.kt @@ -0,0 +1,13 @@ +package com.revenuecat.purchases.react.ui.events + +import com.revenuecat.purchases.react.ui.PaywallEventKey +import com.revenuecat.purchases.react.ui.PaywallEventName + +internal class OnRestoreStartedEvent( + surfaceId: Int, + viewTag: Int, +) : PaywallEvent(surfaceId, viewTag) { + override fun getPaywallEventName() = PaywallEventName.ON_RESTORE_STARTED + + override fun getPayload(): Map> = emptyMap() +} diff --git a/react-native-purchases-ui/android/src/main/java/com/revenuecat/purchases/react/ui/events/PaywallEvent.kt b/react-native-purchases-ui/android/src/main/java/com/revenuecat/purchases/react/ui/events/PaywallEvent.kt new file mode 100644 index 00000000..1f5ee300 --- /dev/null +++ b/react-native-purchases-ui/android/src/main/java/com/revenuecat/purchases/react/ui/events/PaywallEvent.kt @@ -0,0 +1,34 @@ +package com.revenuecat.purchases.react.ui.events + +import com.facebook.react.bridge.WritableMap +import com.facebook.react.bridge.WritableNativeMap +import com.facebook.react.uimanager.events.Event +import com.revenuecat.purchases.react.ui.PaywallEventKey +import com.revenuecat.purchases.react.ui.PaywallEventName +import com.revenuecat.purchases.react.ui.RNPurchasesConverters + +internal abstract class PaywallEvent( + surfaceId: Int, + viewTag: Int, +) : Event>(surfaceId, viewTag) { + + abstract fun getPaywallEventName(): PaywallEventName + + abstract fun getPayload(): Map> + + override fun getEventName(): String { + return getPaywallEventName().eventName + } + + override fun getEventData(): WritableMap { + val convertedPayload = getPayload().let { payload -> + WritableNativeMap().apply { + payload.forEach { (key, value) -> + putMap(key.key, RNPurchasesConverters.convertMapToWriteableMap(value)) + } + } + } + + return convertedPayload + } +}