From c94756732e5e6b3c0ccb7a005dbbb6189a3dd12a Mon Sep 17 00:00:00 2001 From: Ian Rumac Date: Thu, 21 Nov 2024 15:02:01 +0100 Subject: [PATCH 1/3] Add shimmer time tracking events --- CHANGELOG.md | 5 ++ .../sdk/analytics/internal/TrackingLogic.kt | 4 ++ .../trackable/TrackableSuperwallEvent.kt | 35 ++++++++++++++ .../sdk/analytics/superwall/SuperwallEvent.kt | 12 +++++ .../analytics/superwall/SuperwallEvents.kt | 2 + .../sdk/dependencies/DependencyContainer.kt | 2 +- .../sdk/dependencies/FactoryProtocols.kt | 2 +- .../superwall/sdk/models/paywall/Paywall.kt | 4 ++ .../sdk/paywall/presentation/PaywallInfo.kt | 12 +++++ .../superwall/sdk/paywall/vc/PaywallView.kt | 47 +++++++++++++++++-- 10 files changed, 119 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 730a9876..d9415672 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ The changelog for `Superwall`. Also see the [releases](https://github.com/superwall/Superwall-Android/releases) on GitHub. +## 1.5.0-beta.2 + +## Enhancements +- Adds `shimmerView_start` and `shimmerView_complete` events to track the loading of the shimmer animation. + ## 1.5.0-beta.1 ## Enhancements diff --git a/superwall/src/main/java/com/superwall/sdk/analytics/internal/TrackingLogic.kt b/superwall/src/main/java/com/superwall/sdk/analytics/internal/TrackingLogic.kt index 50c2b307..d57190c5 100644 --- a/superwall/src/main/java/com/superwall/sdk/analytics/internal/TrackingLogic.kt +++ b/superwall/src/main/java/com/superwall/sdk/analytics/internal/TrackingLogic.kt @@ -107,6 +107,10 @@ sealed class TrackingLogic { } } + if (event is InternalSuperwallEvent.ShimmerLoad) { + return !disableVerboseEvents + } + (event as? InternalSuperwallEvent.PaywallProductsLoad)?.let { return when (it.state) { is InternalSuperwallEvent.PaywallProductsLoad.State.Start, diff --git a/superwall/src/main/java/com/superwall/sdk/analytics/internal/trackable/TrackableSuperwallEvent.kt b/superwall/src/main/java/com/superwall/sdk/analytics/internal/trackable/TrackableSuperwallEvent.kt index e9a61fe4..0f547c73 100644 --- a/superwall/src/main/java/com/superwall/sdk/analytics/internal/trackable/TrackableSuperwallEvent.kt +++ b/superwall/src/main/java/com/superwall/sdk/analytics/internal/trackable/TrackableSuperwallEvent.kt @@ -843,6 +843,41 @@ sealed class InternalSuperwallEvent( override val canImplicitlyTriggerPaywall: Boolean = true } + data class ShimmerLoad( + val state: State, + val paywallId: String, + val visibleDuration: Double?, + val delay: Double, + val preloadingEnabled: Boolean, + ) : InternalSuperwallEvent( + if (state == + State.Started + ) { + SuperwallEvent.ShimmerViewStart + } else { + SuperwallEvent.ShimmerViewComplete(visibleDuration ?: 0.0) + }, + ) { + enum class State { + Started, + Complete, + } + + override val audienceFilterParams: Map = emptyMap() + + override val rawName: String + get() = superwallEvent.rawName + + override suspend fun getSuperwallParameters(): Map = + mapOf( + "paywall_id" to paywallId, + "preloading_enabled" to preloadingEnabled, + "visible_duration" to visibleDuration, + ).filter { it.value != null }.toMap() as Map + + override val canImplicitlyTriggerPaywall: Boolean = false + } + object ConfirmAllAssignments : InternalSuperwallEvent(SuperwallEvent.ConfirmAllAssignments) { override val audienceFilterParams: Map = emptyMap() diff --git a/superwall/src/main/java/com/superwall/sdk/analytics/superwall/SuperwallEvent.kt b/superwall/src/main/java/com/superwall/sdk/analytics/superwall/SuperwallEvent.kt index a7573118..ec2be1f5 100644 --- a/superwall/src/main/java/com/superwall/sdk/analytics/superwall/SuperwallEvent.kt +++ b/superwall/src/main/java/com/superwall/sdk/analytics/superwall/SuperwallEvent.kt @@ -416,6 +416,18 @@ sealed class SuperwallEvent { override val rawName: String = SuperwallEvents.CustomPlacement.rawName } + data object ShimmerViewStart : SuperwallEvent() { + override val rawName: String + get() = SuperwallEvents.ShimmerViewStart.rawName + } + + data class ShimmerViewComplete( + val duration: Double, + ) : SuperwallEvent() { + override val rawName: String + get() = SuperwallEvents.ShimmerViewComplete.rawName + } + internal object ErrorThrown : SuperwallEvent(), IsInternalEvent { override val rawName: String get() = "error_thrown" diff --git a/superwall/src/main/java/com/superwall/sdk/analytics/superwall/SuperwallEvents.kt b/superwall/src/main/java/com/superwall/sdk/analytics/superwall/SuperwallEvents.kt index be7cd3d6..5a6dcead 100644 --- a/superwall/src/main/java/com/superwall/sdk/analytics/superwall/SuperwallEvents.kt +++ b/superwall/src/main/java/com/superwall/sdk/analytics/superwall/SuperwallEvents.kt @@ -48,6 +48,8 @@ enum class SuperwallEvents( RestoreFail("restore_fail"), RestoreComplete("restore_complete"), CustomPlacement("custom_placement"), + ShimmerViewStart("shimmerView_start"), + ShimmerViewComplete("shimmerView_complete"), ConfigAttributes("config_attributes"), ConfirmAllAssignments("confirm_all_assignments"), ConfigFail("config_fail"), diff --git a/superwall/src/main/java/com/superwall/sdk/dependencies/DependencyContainer.kt b/superwall/src/main/java/com/superwall/sdk/dependencies/DependencyContainer.kt index 9ca9929f..8a10e2a3 100644 --- a/superwall/src/main/java/com/superwall/sdk/dependencies/DependencyContainer.kt +++ b/superwall/src/main/java/com/superwall/sdk/dependencies/DependencyContainer.kt @@ -643,7 +643,7 @@ class DependencyContainer( override fun makeTransactionVerifier(): GoogleBillingWrapper = googleBillingWrapper - override suspend fun makeSuperwallOptions(): SuperwallOptions = configManager.options + override fun makeSuperwallOptions(): SuperwallOptions = configManager.options override suspend fun makeTriggers(): Set = configManager.triggersByEventName.keys diff --git a/superwall/src/main/java/com/superwall/sdk/dependencies/FactoryProtocols.kt b/superwall/src/main/java/com/superwall/sdk/dependencies/FactoryProtocols.kt index 66cd8749..a1af19f0 100644 --- a/superwall/src/main/java/com/superwall/sdk/dependencies/FactoryProtocols.kt +++ b/superwall/src/main/java/com/superwall/sdk/dependencies/FactoryProtocols.kt @@ -184,7 +184,7 @@ interface StoreTransactionFactory { } interface OptionsFactory { - suspend fun makeSuperwallOptions(): SuperwallOptions + fun makeSuperwallOptions(): SuperwallOptions } interface TriggerFactory { diff --git a/superwall/src/main/java/com/superwall/sdk/models/paywall/Paywall.kt b/superwall/src/main/java/com/superwall/sdk/models/paywall/Paywall.kt index 9ffc9c33..d306eeeb 100644 --- a/superwall/src/main/java/com/superwall/sdk/models/paywall/Paywall.kt +++ b/superwall/src/main/java/com/superwall/sdk/models/paywall/Paywall.kt @@ -85,6 +85,8 @@ data class Paywall( var webviewLoadingInfo: LoadingInfo = LoadingInfo(), @kotlinx.serialization.Transient() var productsLoadingInfo: LoadingInfo = LoadingInfo(), + @kotlinx.serialization.Transient() + var shimmerLoadingInfo: LoadingInfo = LoadingInfo(), var productVariables: List? = null, var swProductVariablesTemplate: List? = null, var paywalljsVersion: String? = null, @@ -209,6 +211,8 @@ data class Paywall( productsLoadStartTime = productsLoadingInfo.startAt, productsLoadFailTime = productsLoadingInfo.failAt, productsLoadCompleteTime = productsLoadingInfo.endAt, + shimmerLoadStartTime = shimmerLoadingInfo.startAt, + shimmerLoadCompleteTime = shimmerLoadingInfo.endAt, experiment = experiment, paywalljsVersion = paywalljsVersion, isFreeTrialAvailable = isFreeTrialAvailable, diff --git a/superwall/src/main/java/com/superwall/sdk/paywall/presentation/PaywallInfo.kt b/superwall/src/main/java/com/superwall/sdk/paywall/presentation/PaywallInfo.kt index 17320763..90b11793 100644 --- a/superwall/src/main/java/com/superwall/sdk/paywall/presentation/PaywallInfo.kt +++ b/superwall/src/main/java/com/superwall/sdk/paywall/presentation/PaywallInfo.kt @@ -52,6 +52,8 @@ data class PaywallInfo( val productsLoadStartTime: String?, val productsLoadCompleteTime: String?, val productsLoadFailTime: String?, + val shimmerLoadStartTime: String?, + val shimmerLoadCompleteTime: String?, val productsLoadDuration: Double?, val paywalljsVersion: String?, val isFreeTrialAvailable: Boolean, @@ -83,6 +85,8 @@ data class PaywallInfo( productsLoadStartTime: Date?, productsLoadFailTime: Date?, productsLoadCompleteTime: Date?, + shimmerLoadStartTime: Date?, + shimmerLoadCompleteTime: Date?, experiment: Experiment? = null, paywalljsVersion: String? = null, isFreeTrialAvailable: Boolean, @@ -167,6 +171,14 @@ data class PaywallInfo( (endTime.time / 1000 - startTime.time / 1000).toDouble() } }, + shimmerLoadStartTime = + webViewLoadStartTime?.let { + DateFormatterUtil.format(it) + } ?: "", + shimmerLoadCompleteTime = + webViewLoadStartTime?.let { + DateFormatterUtil.format(it) + } ?: "", localNotifications = localNotifications, computedPropertyRequests = computedPropertyRequests, closeReason = closeReason, diff --git a/superwall/src/main/java/com/superwall/sdk/paywall/vc/PaywallView.kt b/superwall/src/main/java/com/superwall/sdk/paywall/vc/PaywallView.kt index beffd9d0..93463a17 100644 --- a/superwall/src/main/java/com/superwall/sdk/paywall/vc/PaywallView.kt +++ b/superwall/src/main/java/com/superwall/sdk/paywall/vc/PaywallView.kt @@ -19,6 +19,7 @@ import com.superwall.sdk.analytics.internal.trackable.InternalSuperwallEvent import com.superwall.sdk.analytics.superwall.SuperwallEvents import com.superwall.sdk.config.models.OnDeviceCaching import com.superwall.sdk.config.options.PaywallOptions +import com.superwall.sdk.dependencies.OptionsFactory import com.superwall.sdk.dependencies.TriggerFactory import com.superwall.sdk.game.GameControllerDelegate import com.superwall.sdk.game.GameControllerEvent @@ -66,6 +67,8 @@ import java.lang.ref.WeakReference import java.net.MalformedURLException import java.net.URI import java.util.Date +import kotlin.time.Duration.Companion.milliseconds +import kotlin.time.DurationUnit class PaywallView( context: Context, @@ -94,7 +97,9 @@ class PaywallView( } } - interface Factory : TriggerFactory + interface Factory : + TriggerFactory, + OptionsFactory //region Public properties // MUST be set prior to presentation @@ -312,7 +317,8 @@ class PaywallView( if (loadingState is PaywallLoadingState.Ready) { webView.messageHandler.handle(PaywallMessage.TemplateParamsAndUserAttributes) } - + paywall.shimmerLoadingInfo.startAt = Date() + trackShimmerStart() presentationWillPrepare = false } @@ -566,6 +572,20 @@ class PaywallView( } } + private fun trackShimmerStart() { + val trackedEvent = + InternalSuperwallEvent.ShimmerLoad( + state = InternalSuperwallEvent.ShimmerLoad.State.Started, + paywallId = paywall.identifier, + visibleDuration = null, + preloadingEnabled = factory.makeSuperwallOptions().paywalls.shouldPreload, + delay = paywall.presentation.delay.toDouble(), + ) + ioScope.launch { + Superwall.instance.track(trackedEvent) + } + } + private fun showShimmerView() { shimmerView?.let { mainScope.launch { @@ -580,6 +600,27 @@ class PaywallView( it.hideShimmer() } } + val visible = paywall.shimmerLoadingInfo.startAt + val now = Date() + paywall.shimmerLoadingInfo.endAt = now + ioScope.launch { + val trackedEvent = + InternalSuperwallEvent.ShimmerLoad( + state = InternalSuperwallEvent.ShimmerLoad.State.Complete, + paywallId = paywall.identifier, + visibleDuration = + if (visible != null) { + (now.time - visible.time).milliseconds.toDouble( + DurationUnit.MILLISECONDS, + ) + } else { + 0.0 + }, + delay = paywall.presentation.delay.toDouble(), + preloadingEnabled = factory.makeSuperwallOptions().paywalls.shouldPreload, + ) + Superwall.instance.track(trackedEvent) + } } fun showRefreshButtonAfterTimeout(isVisible: Boolean) { @@ -711,7 +752,6 @@ class PaywallView( } webView.scrollEnabled = paywall.isScrollEnabled ?: true - mainScope.launch { if (paywall.onDeviceCache is OnDeviceCaching.Enabled) { webView.settings.cacheMode = WebSettings.LOAD_CACHE_ELSE_NETWORK @@ -724,7 +764,6 @@ class PaywallView( webView.loadUrl(url.value) } } - loadingState = PaywallLoadingState.LoadingURL() } } From 8e730fd2811fa88e43831eb58211da14f7e4c16b Mon Sep 17 00:00:00 2001 From: Ian Rumac Date: Fri, 29 Nov 2024 15:33:08 +0100 Subject: [PATCH 2/3] Update hasFreeTrial and CEL logging, bump version --- CHANGELOG.md | 1 + superwall/build.gradle.kts | 2 +- .../sdk/dependencies/DependencyContainer.kt | 1 + .../sdk/models/config/FeatureFlags.kt | 3 ++- .../CombinedExpressionEvaluator.kt | 21 +++++++++++-------- .../abstractions/product/RawStoreProduct.kt | 2 +- 6 files changed, 18 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d9415672..3298f3e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ The changelog for `Superwall`. Also see the [releases](https://github.com/superw ## Enhancements - Adds `shimmerView_start` and `shimmerView_complete` events to track the loading of the shimmer animation. +- Makes `hasFreeTrial` match iOS SDK behavior by returning `true` for both free trials and non-free introductory offers ## 1.5.0-beta.1 diff --git a/superwall/build.gradle.kts b/superwall/build.gradle.kts index 52d9679f..4c24ea09 100644 --- a/superwall/build.gradle.kts +++ b/superwall/build.gradle.kts @@ -23,7 +23,7 @@ plugins { id("signing") } -version = "1.5.0-beta.1" +version = "1.5.0-beta.2" android { compileSdk = 34 diff --git a/superwall/src/main/java/com/superwall/sdk/dependencies/DependencyContainer.kt b/superwall/src/main/java/com/superwall/sdk/dependencies/DependencyContainer.kt index 8a10e2a3..5dc49901 100644 --- a/superwall/src/main/java/com/superwall/sdk/dependencies/DependencyContainer.kt +++ b/superwall/src/main/java/com/superwall/sdk/dependencies/DependencyContainer.kt @@ -170,6 +170,7 @@ class DependencyContainer( track = { Superwall.instance.track(it) }, + shouldTraceResults = makeFeatureFlags()?.enableCELLogging ?: false, ) } diff --git a/superwall/src/main/java/com/superwall/sdk/models/config/FeatureFlags.kt b/superwall/src/main/java/com/superwall/sdk/models/config/FeatureFlags.kt index 3c108e35..1063bc7c 100644 --- a/superwall/src/main/java/com/superwall/sdk/models/config/FeatureFlags.kt +++ b/superwall/src/main/java/com/superwall/sdk/models/config/FeatureFlags.kt @@ -13,12 +13,13 @@ data class RawFeatureFlag( @Serializable data class FeatureFlags( - @SerialName("enable_config_refresh") var enableConfigRefresh: Boolean = false, + @SerialName("enable_config_refresh_v2") var enableConfigRefresh: Boolean = false, @SerialName("enable_session_events") var enableSessionEvents: Boolean, @SerialName("enable_postback") var enablePostback: Boolean, @SerialName("enable_userid_seed") var enableUserIdSeed: Boolean, @SerialName("disable_verbose_events") var disableVerboseEvents: Boolean, @SerialName("enable_multiple_paywall_urls") var enableMultiplePaywallUrls: Boolean, + @SerialName("enable_cel_logging") var enableCELLogging: Boolean, ) fun List.value( diff --git a/superwall/src/main/java/com/superwall/sdk/paywall/presentation/rule_logic/expression_evaluator/CombinedExpressionEvaluator.kt b/superwall/src/main/java/com/superwall/sdk/paywall/presentation/rule_logic/expression_evaluator/CombinedExpressionEvaluator.kt index a6568da2..81096640 100644 --- a/superwall/src/main/java/com/superwall/sdk/paywall/presentation/rule_logic/expression_evaluator/CombinedExpressionEvaluator.kt +++ b/superwall/src/main/java/com/superwall/sdk/paywall/presentation/rule_logic/expression_evaluator/CombinedExpressionEvaluator.kt @@ -23,6 +23,7 @@ internal class CombinedExpressionEvaluator( private val storage: LocalStorage, private val factory: RuleAttributesFactory, private val evaluator: JavascriptEvaluator, + private val shouldTraceResults: Boolean, private val superscriptEvaluator: SuperscriptEvaluator, private val track: suspend (InternalSuperwallEvent.ExpressionResult) -> Unit, ) : ExpressionEvaluating { @@ -48,15 +49,17 @@ internal class CombinedExpressionEvaluator( } catch (e: Exception) { TriggerRuleOutcome.noMatch(UnmatchedRule.Source.EXPRESSION, rule.experiment.id) } - track( - InternalSuperwallEvent.ExpressionResult( - liquidExpression = rule.expression, - celExpression = rule.expressionCEL, - celExpressionResult = if (celEvaluation is TriggerRuleOutcome.Match) true else false, - jsExpression = rule.expressionJs, - jsExpressionResult = if (result is TriggerRuleOutcome.Match) true else false, - ), - ) + if (shouldTraceResults) { + track( + InternalSuperwallEvent.ExpressionResult( + liquidExpression = rule.expression, + celExpression = rule.expressionCEL, + celExpressionResult = if (celEvaluation is TriggerRuleOutcome.Match) true else false, + jsExpression = rule.expressionJs, + jsExpressionResult = if (result is TriggerRuleOutcome.Match) true else false, + ), + ) + } return result } diff --git a/superwall/src/main/java/com/superwall/sdk/store/abstractions/product/RawStoreProduct.kt b/superwall/src/main/java/com/superwall/sdk/store/abstractions/product/RawStoreProduct.kt index b6f5e7ae..8e714a81 100644 --- a/superwall/src/main/java/com/superwall/sdk/store/abstractions/product/RawStoreProduct.kt +++ b/superwall/src/main/java/com/superwall/sdk/store/abstractions/product/RawStoreProduct.kt @@ -233,7 +233,7 @@ class RawStoreProduct( // Check for free trial phase in pricing phases, excluding the base pricing selectedOffer.pricingPhases.pricingPhaseList .dropLast(1) - .any { it.priceAmountMicros == 0L } + .isNotEmpty() } override val localizedTrialPeriodPrice by lazy { From 330799f2340e30cc9634b1d568c4740babb82a36 Mon Sep 17 00:00:00 2001 From: Ian Rumac Date: Fri, 29 Nov 2024 16:07:40 +0100 Subject: [PATCH 3/3] Fix config feature flag issue --- .../src/main/java/com/superwall/sdk/models/config/Config.kt | 5 ++++- .../java/com/superwall/sdk/products/ProductFetcherTest.kt | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/superwall/src/main/java/com/superwall/sdk/models/config/Config.kt b/superwall/src/main/java/com/superwall/sdk/models/config/Config.kt index e590747a..b829106f 100644 --- a/superwall/src/main/java/com/superwall/sdk/models/config/Config.kt +++ b/superwall/src/main/java/com/superwall/sdk/models/config/Config.kt @@ -41,7 +41,7 @@ data class Config( rawFeatureFlags.find { it.key == "enable_multiple_paywall_urls" }?.enabled ?: false, enableConfigRefresh = - rawFeatureFlags.find { it.key == "enable_config_refresh" }?.enabled + rawFeatureFlags.find { it.key == "enable_config_refresh_v2" }?.enabled ?: false, enableSessionEvents = rawFeatureFlags.find { it.key == "enable_session_events" }?.enabled @@ -55,6 +55,9 @@ data class Config( disableVerboseEvents = rawFeatureFlags.find { it.key == "disable_verbose_events" }?.enabled ?: false, + enableCELLogging = + rawFeatureFlags.find { it.key == "enable_cel_logging" }?.enabled + ?: false, ) companion object { diff --git a/superwall/src/test/java/com/superwall/sdk/products/ProductFetcherTest.kt b/superwall/src/test/java/com/superwall/sdk/products/ProductFetcherTest.kt index 103e0df6..90f04003 100644 --- a/superwall/src/test/java/com/superwall/sdk/products/ProductFetcherTest.kt +++ b/superwall/src/test/java/com/superwall/sdk/products/ProductFetcherTest.kt @@ -410,7 +410,7 @@ class ProductFetcherInstrumentedTest { offerType = OfferType.Offer(id = "paid-offer"), ), ) - assert(!storeProduct.hasFreeTrial) + assert(storeProduct.hasFreeTrial) assert(storeProduct.productIdentifier == "com.ui_tests.quarterly2") assert(storeProduct.fullIdentifier == "com.ui_tests.quarterly2:test-4:paid-offer") assert(storeProduct.currencyCode == "USD") @@ -513,7 +513,7 @@ class ProductFetcherInstrumentedTest { offerType = OfferType.Auto, ), ) - assert(!storeProduct.hasFreeTrial) + assert(storeProduct.hasFreeTrial) assert(storeProduct.productIdentifier == "com.ui_tests.quarterly2") assert(storeProduct.fullIdentifier == "com.ui_tests.quarterly2:test-3:sw-auto") assert(storeProduct.currencyCode == "USD")