Skip to content

Commit 8a39fc8

Browse files
committed
Added wide events for switch flow
1 parent b7a2d89 commit 8a39fc8

File tree

5 files changed

+424
-2
lines changed

5 files changed

+424
-2
lines changed

subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/RealSubscriptions.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,9 @@ interface PrivacyProFeature {
255255
@Toggle.DefaultValue(defaultValue = DefaultFeatureValue.TRUE)
256256
fun sendAuthTokenRefreshWideEvent(): Toggle
257257

258+
@Toggle.DefaultValue(defaultValue = DefaultFeatureValue.TRUE)
259+
fun sendSubscriptionSwitchWideEvent(): Toggle
260+
258261
@Toggle.DefaultValue(defaultValue = DefaultFeatureValue.TRUE)
259262
fun useSubscriptionSupport(): Toggle
260263

subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/SubscriptionsManager.kt

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ import com.duckduckgo.subscriptions.impl.services.ValidateTokenResponse
7676
import com.duckduckgo.subscriptions.impl.services.toEntitlements
7777
import com.duckduckgo.subscriptions.impl.wideevents.AuthTokenRefreshWideEvent
7878
import com.duckduckgo.subscriptions.impl.wideevents.SubscriptionPurchaseWideEvent
79+
import com.duckduckgo.subscriptions.impl.wideevents.SubscriptionSwitchWideEvent
7980
import com.squareup.anvil.annotations.ContributesBinding
8081
import com.squareup.moshi.JsonDataException
8182
import com.squareup.moshi.JsonEncodingException
@@ -295,6 +296,7 @@ class RealSubscriptionsManager @Inject constructor(
295296
private val backgroundTokenRefresh: BackgroundTokenRefresh,
296297
private val subscriptionPurchaseWideEvent: SubscriptionPurchaseWideEvent,
297298
private val tokenRefreshWideEvent: AuthTokenRefreshWideEvent,
299+
private val subscriptionSwitchWideEvent: SubscriptionSwitchWideEvent,
298300
) : SubscriptionsManager {
299301
private val adapter = Moshi.Builder().build().adapter(ResponseError::class.java)
300302

@@ -371,10 +373,12 @@ class RealSubscriptionsManager @Inject constructor(
371373
when (it) {
372374
is PurchaseState.Purchased -> {
373375
subscriptionPurchaseWideEvent.onBillingFlowPurchaseSuccess()
376+
subscriptionSwitchWideEvent.onPlayBillingSwitchSuccess()
374377
checkPurchase(it.packageName, it.purchaseToken)
375378
}
376379
is PurchaseState.Canceled -> {
377380
_currentPurchaseState.emit(CurrentPurchase.Canceled)
381+
subscriptionSwitchWideEvent.onUserCancelled()
378382
if (removeExpiredSubscriptionOnCancelledPurchase) {
379383
if (subscriptionStatus().isExpired()) {
380384
signOut()
@@ -391,6 +395,7 @@ class RealSubscriptionsManager @Inject constructor(
391395
}
392396
}
393397

398+
394399
override suspend fun canSupportEncryption(): Boolean = authRepository.canSupportEncryption()
395400

396401
override suspend fun isFreeTrialEligible(): Boolean {
@@ -499,21 +504,34 @@ class RealSubscriptionsManager @Inject constructor(
499504
try {
500505
if (!isSignedIn()) {
501506
logcat { "Subs: Cannot switch plan - user not signed in" }
507+
subscriptionSwitchWideEvent.onValidationFailure("User not signed in")
502508
_currentPurchaseState.emit(CurrentPurchase.Failure("User not signed in for switch"))
503509
return@withContext
504510
}
505511

506512
val currentSubscription = authRepository.getSubscription()
507513
if (currentSubscription == null || !currentSubscription.isActive()) {
508-
logcat { "Subs: Cannot switch plan - no active subscription found" }
514+
subscriptionSwitchWideEvent.onValidationFailure("No active subscription found")
509515
_currentPurchaseState.emit(CurrentPurchase.Failure("No active subscription found for switch"))
510516
return@withContext
511517
}
512518

519+
// Start wide event tracking
520+
val isUpgrade = currentSubscription.productId in listOf(MONTHLY_PLAN_US, MONTHLY_PLAN_ROW)
521+
val switchType = if (isUpgrade) "upgrade" else "downgrade"
522+
subscriptionSwitchWideEvent.onSwitchFlowStarted(
523+
fromPlan = currentSubscription.productId,
524+
toPlan = planId,
525+
switchType = switchType,
526+
)
527+
528+
subscriptionSwitchWideEvent.onCurrentSubscriptionValidated()
529+
513530
val currentPurchaseToken = playBillingManager.getLatestPurchaseToken()
514531

515532
if (currentPurchaseToken == null) {
516533
logcat { "Subs: Cannot switch plan - no current purchase token found" }
534+
subscriptionSwitchWideEvent.onSwitchFailed("No current purchase token found")
517535
_currentPurchaseState.emit(CurrentPurchase.Failure("No current purchase token found for switch"))
518536
return@withContext
519537
}
@@ -522,6 +540,7 @@ class RealSubscriptionsManager @Inject constructor(
522540
val account = authRepository.getAccount()
523541
if (account == null) {
524542
logcat { "Subs: Cannot switch plan - no account found" }
543+
subscriptionSwitchWideEvent.onSwitchFailed("No account found")
525544
_currentPurchaseState.emit(CurrentPurchase.Failure("No account found for switch"))
526545
return@withContext
527546
}
@@ -531,10 +550,13 @@ class RealSubscriptionsManager @Inject constructor(
531550
val targetOffer = availableOffers.find { it.planId == planId && it.offerId == offerId }
532551
if (targetOffer == null) {
533552
logcat { "Subs: Cannot switch plan - target plan not found: $planId" }
553+
subscriptionSwitchWideEvent.onTargetPlanRetrievalFailure()
534554
_currentPurchaseState.emit(CurrentPurchase.Failure("Target plan not found: $planId for switch"))
535555
return@withContext
536556
}
537557

558+
subscriptionSwitchWideEvent.onTargetPlanRetrieved()
559+
538560
// Launch Google Play billing flow for subscription update
539561
logcat { "Subs: Launching subscription update flow for plan: $planId" }
540562

@@ -549,8 +571,11 @@ class RealSubscriptionsManager @Inject constructor(
549571
replacementMode = replacementMode,
550572
)
551573
}
574+
575+
subscriptionSwitchWideEvent.onBillingFlowInitSuccess()
552576
} catch (e: Exception) {
553577
logcat(ERROR) { "Subs: Failed to switch subscription plan: ${e.asLog()}" }
578+
subscriptionSwitchWideEvent.onBillingFlowInitFailure(e.message ?: "Unknown error")
554579
_currentPurchaseState.emit(CurrentPurchase.Failure("Failed to switch subscription plan: ${e.message}"))
555580
}
556581
}
@@ -693,8 +718,11 @@ class RealSubscriptionsManager @Inject constructor(
693718
emitEntitlementsValues()
694719
_currentPurchaseState.emit(CurrentPurchase.Success)
695720
authRepository.registerLocalPurchasedAt()
721+
722+
subscriptionSwitchWideEvent.onSwitchConfirmed()
696723
subscriptionPurchaseWideEvent.onPurchaseConfirmationSuccess()
697724
} else {
725+
subscriptionSwitchWideEvent.onSwitchConfirmationFailure("Subscription not active after switch")
698726
handlePurchaseFailed()
699727
}
700728

@@ -890,6 +918,7 @@ class RealSubscriptionsManager @Inject constructor(
890918
)
891919

892920
subscriptionPurchaseWideEvent.onSubscriptionUpdated(oldStatus = oldStatus, newStatus = subscription.status.toStatus())
921+
subscriptionSwitchWideEvent.onSubscriptionUpdated(oldStatus = oldStatus, newStatus = subscription.status.toStatus())
893922

894923
_subscriptionStatus.emit(subscription.status.toStatus())
895924
}
@@ -1390,7 +1419,6 @@ data class PricingPhase(
13901419
val priceCurrency: Currency,
13911420
val formattedPrice: String,
13921421
val billingPeriod: String,
1393-
13941422
) {
13951423
internal fun getBillingPeriodInDays(): Int? {
13961424
return try {

subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/switch_plan/SwitchPlanBottomSheetDialog.kt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import com.duckduckgo.subscriptions.impl.SubscriptionsManager
3535
import com.duckduckgo.subscriptions.impl.billing.SubscriptionReplacementMode
3636
import com.duckduckgo.subscriptions.impl.databinding.BottomSheetSwitchPlanBinding
3737
import com.duckduckgo.subscriptions.impl.ui.SubscriptionSettingsViewModel.SwitchPlanType
38+
import com.duckduckgo.subscriptions.impl.wideevents.SubscriptionSwitchWideEvent
3839
import com.google.android.material.bottomsheet.BottomSheetBehavior
3940
import com.google.android.material.bottomsheet.BottomSheetDialog
4041
import com.google.android.material.shape.CornerFamily
@@ -54,6 +55,7 @@ class SwitchPlanBottomSheetDialog @AssistedInject constructor(
5455
@Assisted private val onSwitchSuccess: () -> Unit,
5556
private val subscriptionsManager: SubscriptionsManager,
5657
private val dispatcherProvider: DispatcherProvider,
58+
private val subscriptionSwitchWideEvent: SubscriptionSwitchWideEvent,
5759
) : BottomSheetDialog(context) {
5860

5961
private val binding: BottomSheetSwitchPlanBinding = BottomSheetSwitchPlanBinding.inflate(LayoutInflater.from(context))
@@ -90,6 +92,11 @@ class SwitchPlanBottomSheetDialog @AssistedInject constructor(
9092
override fun onAttachedToWindow() {
9193
super.onAttachedToWindow()
9294

95+
// Track that user confirmation dialog was shown
96+
lifecycleOwner.lifecycleScope.launch(dispatcherProvider.io()) {
97+
subscriptionSwitchWideEvent.onUserConfirmationShown()
98+
}
99+
93100
configureViews()
94101
observePurchaseState()
95102
}
@@ -127,6 +134,10 @@ class SwitchPlanBottomSheetDialog @AssistedInject constructor(
127134
dismiss()
128135
}
129136
binding.switchBottomSheetDialogSecondaryButton.setOnClickListener {
137+
// User cancelled the switch by keeping monthly plan
138+
lifecycleOwner.lifecycleScope.launch(dispatcherProvider.io()) {
139+
subscriptionSwitchWideEvent.onUserCancelled()
140+
}
130141
dismiss()
131142
}
132143
}
@@ -149,6 +160,10 @@ class SwitchPlanBottomSheetDialog @AssistedInject constructor(
149160
)
150161

151162
binding.switchBottomSheetDialogPrimaryButton.setOnClickListener {
163+
// User cancelled the switch by keeping yearly plan
164+
lifecycleOwner.lifecycleScope.launch(dispatcherProvider.io()) {
165+
subscriptionSwitchWideEvent.onUserCancelled()
166+
}
152167
dismiss()
153168
}
154169
binding.switchBottomSheetDialogSecondaryButton.setOnClickListener {
@@ -167,6 +182,9 @@ class SwitchPlanBottomSheetDialog @AssistedInject constructor(
167182
when (it) {
168183
is CurrentPurchase.Success -> {
169184
logcat { "Switch flow: Successfully switched plans" }
185+
lifecycleOwner.lifecycleScope.launch(dispatcherProvider.io()) {
186+
subscriptionSwitchWideEvent.onUIRefreshed()
187+
}
170188
onSwitchSuccess.invoke()
171189
}
172190

0 commit comments

Comments
 (0)