@@ -5,8 +5,10 @@ import com.bitwarden.core.data.manager.model.FlagKey
55import com.bitwarden.core.data.repository.model.DataState
66import com.bitwarden.data.repository.model.Environment
77import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
8+ import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountJson
89import com.x8bit.bitwarden.data.auth.datasource.disk.model.UserStateJson
910import com.x8bit.bitwarden.data.auth.repository.util.activeUserIdChangesFlow
11+ import com.x8bit.bitwarden.data.billing.model.PremiumCard
1012import com.x8bit.bitwarden.data.billing.repository.BillingRepository
1113import com.x8bit.bitwarden.data.billing.repository.model.PremiumSubscriptionStatus
1214import com.x8bit.bitwarden.data.billing.repository.model.SubscriptionResult
@@ -50,7 +52,7 @@ class PremiumStateManagerImpl(
5052 private val settingsDiskSource : SettingsDiskSource ,
5153 vaultRepository : VaultRepository ,
5254 private val featureFlagManager : FeatureFlagManager ,
53- private val environmentRepository : EnvironmentRepository ,
55+ environmentRepository : EnvironmentRepository ,
5456 pushManager : PushManager ,
5557 private val clock : Clock ,
5658 dispatcherManager : DispatcherManager ,
@@ -123,62 +125,69 @@ class PremiumStateManagerImpl(
123125 )
124126
125127 @OptIn(ExperimentalCoroutinesApi ::class )
126- override val isPremiumUpgradeBannerEligibleFlow : StateFlow <Boolean > =
128+ override val premiumCardStateFlow : StateFlow <PremiumCard > =
127129 combine(
128- authDiskSource.userStateFlow,
130+ authDiskSource.userStateFlow.map { it?.activeAccount } ,
129131 billingRepository.isInAppBillingSupportedFlow,
130132 featureFlagManager.getFeatureFlagFlow(FlagKey .MobilePremiumUpgrade ),
131- authDiskSource.activeUserIdChangesFlow
132- .flatMapLatest { userId ->
133- userId
134- ?.let { id ->
135- settingsDiskSource
136- .getPremiumUpgradeBannerDismissedFlow(id)
137- .map { it ? : false }
138- }
139- ? : flowOf(false )
140- },
133+ authDiskSource.activeUserIdChangesFlow.flatMapLatest { userId ->
134+ userId
135+ ?.let { id ->
136+ settingsDiskSource
137+ .getPremiumUpgradeBannerDismissedFlow(id)
138+ .map { it ? : false }
139+ }
140+ ? : flowOf(false )
141+ },
141142 vaultRepository.vaultDataStateFlow,
142143 ) {
143- userState ,
144+ account ,
144145 isInAppBillingSupported,
145146 featureFlagEnabled,
146- isDismissed ,
147+ isUpgradeCardDismissed ,
147148 vaultDataState,
148149 ->
149150 BannerInputs (
150- userState = userState ,
151+ account = account ,
151152 isInAppBillingSupported = isInAppBillingSupported,
152153 featureFlagEnabled = featureFlagEnabled,
153- isDismissed = isDismissed ,
154+ isUpgradeCardDismissed = isUpgradeCardDismissed ,
154155 vaultDataState = vaultDataState,
155156 )
156157 }
157158 .combine(upgradeLifecycleStateFlow) { inputs, lifecycle ->
158- val profile = inputs.userState?.activeAccount?.profile
159- ? : return @combine false
160- val isAccountOldEnough = profile.creationDate.isOlderThanDays(
161- days = PREMIUM_UPGRADE_MINIMUM_ACCOUNT_AGE_DAYS ,
162- clock = clock,
163- )
164- val itemCount = inputs.vaultDataState.activeVaultItemCount()
165- val lifecycleAllowsBanner = lifecycle is UpgradeLifecycleState .Free ||
166- (
167- lifecycle is UpgradeLifecycleState .Premium &&
168- lifecycle.subscriptionStatus.isInTroubleState()
159+ val profile = inputs.account?.profile ? : return @combine PremiumCard .NONE
160+ if (! inputs.featureFlagEnabled) return @combine PremiumCard .NONE
161+ val initialCard = when (lifecycle) {
162+ UpgradeLifecycleState .Free -> PremiumCard .UPGRADE
163+ UpgradeLifecycleState .UpgradePending -> PremiumCard .NONE
164+ is UpgradeLifecycleState .Premium -> {
165+ lifecycle.subscriptionStatus.premiumCardState()
166+ }
167+ }
168+ when (initialCard) {
169+ PremiumCard .UPGRADE -> {
170+ val isAccountOldEnough = profile.creationDate.isOlderThanDays(
171+ days = PREMIUM_UPGRADE_MINIMUM_ACCOUNT_AGE_DAYS ,
172+ clock = clock,
169173 )
174+ val itemCount = inputs.vaultDataState.activeVaultItemCount()
175+ val showCard = inputs.isInAppBillingSupported &&
176+ isAccountOldEnough &&
177+ itemCount >= PREMIUM_UPGRADE_MINIMUM_VAULT_ITEMS &&
178+ ! inputs.isUpgradeCardDismissed
179+ initialCard.takeIf { showCard } ? : PremiumCard .NONE
180+ }
170181
171- lifecycleAllowsBanner &&
172- inputs.isInAppBillingSupported &&
173- inputs.featureFlagEnabled &&
174- ! inputs.isDismissed &&
175- isAccountOldEnough &&
176- itemCount >= PREMIUM_UPGRADE_MINIMUM_VAULT_ITEMS
182+ PremiumCard .NEEDS_ATTENTION ,
183+ PremiumCard .NONE ,
184+ -> initialCard
185+ }
177186 }
178187 .stateIn(
179188 scope = unconfinedScope,
180189 started = SharingStarted .Eagerly ,
181- initialValue = false ,
190+ initialValue = PremiumCard . NONE ,
182191 )
183192
184193 override val isSelfHostedFlow: StateFlow <Boolean > =
@@ -389,32 +398,41 @@ class PremiumStateManagerImpl(
389398}
390399
391400private data class BannerInputs (
392- val userState : UserStateJson ? ,
401+ val account : AccountJson ? ,
393402 val isInAppBillingSupported : Boolean ,
394403 val featureFlagEnabled : Boolean ,
395- val isDismissed : Boolean ,
404+ val isUpgradeCardDismissed : Boolean ,
396405 val vaultDataState : DataState <VaultData >,
397406)
398407
399408/* *
400- * Returns `true` when the given [SubscriptionStatusState] represents a subscription substate
401- * that should disqualify a user from being treated as effectively premium.
409+ * Returns a [PremiumCard] for the given [SubscriptionStatusState] and subscription substate.
402410 */
403- private fun SubscriptionStatusState.isInTroubleState (): Boolean =
404- this is SubscriptionStatusState .Available &&
405- when (this .status) {
406- PremiumSubscriptionStatus .CANCELED ,
407- PremiumSubscriptionStatus .EXPIRED ,
408- PremiumSubscriptionStatus .PAST_DUE ,
409- PremiumSubscriptionStatus .PAUSED ,
410- PremiumSubscriptionStatus .UPDATE_PAYMENT ,
411- -> true
412-
413- PremiumSubscriptionStatus .ACTIVE ,
414- PremiumSubscriptionStatus .PENDING_CANCELLATION ,
415- -> false
411+ private fun SubscriptionStatusState.premiumCardState (): PremiumCard =
412+ when (this ) {
413+ is SubscriptionStatusState .Available -> {
414+ when (this .status) {
415+ PremiumSubscriptionStatus .PAST_DUE ,
416+ PremiumSubscriptionStatus .UPDATE_PAYMENT ,
417+ -> PremiumCard .NEEDS_ATTENTION
418+
419+ PremiumSubscriptionStatus .EXPIRED ,
420+ PremiumSubscriptionStatus .PAUSED ,
421+ -> PremiumCard .UPGRADE
422+
423+ PremiumSubscriptionStatus .ACTIVE ,
424+ PremiumSubscriptionStatus .CANCELED ,
425+ PremiumSubscriptionStatus .PENDING_CANCELLATION ,
426+ -> PremiumCard .NONE
427+ }
416428 }
417429
430+ is SubscriptionStatusState .Error ,
431+ SubscriptionStatusState .Loading ,
432+ SubscriptionStatusState .NoSubscription ,
433+ -> PremiumCard .NONE
434+ }
435+
418436/* *
419437 * Returns `true` if this [Instant] is older than the given number of [days] based on
420438 * the provided [clock]. Returns `false` if the receiver is `null`.
0 commit comments