diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/billing/manager/PremiumStateManagerImpl.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/billing/manager/PremiumStateManagerImpl.kt index 58170b2ae5..2448dafb52 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/billing/manager/PremiumStateManagerImpl.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/billing/manager/PremiumStateManagerImpl.kt @@ -5,7 +5,6 @@ import com.bitwarden.core.data.manager.model.FlagKey import com.bitwarden.core.data.repository.model.DataState import com.bitwarden.data.repository.model.Environment import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource -import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountJson import com.x8bit.bitwarden.data.auth.datasource.disk.model.UserStateJson import com.x8bit.bitwarden.data.auth.repository.util.activeUserIdChangesFlow import com.x8bit.bitwarden.data.billing.model.PremiumCard @@ -52,7 +51,7 @@ class PremiumStateManagerImpl( private val billingRepository: BillingRepository, private val settingsDiskSource: SettingsDiskSource, vaultRepository: VaultRepository, - private val featureFlagManager: FeatureFlagManager, + featureFlagManager: FeatureFlagManager, environmentRepository: EnvironmentRepository, pushManager: PushManager, private val clock: Clock, @@ -130,7 +129,7 @@ class PremiumStateManagerImpl( combine( authDiskSource.userStateFlow.map { it?.activeAccount }, billingRepository.isInAppBillingSupportedFlow, - featureFlagManager.getFeatureFlagFlow(FlagKey.MobilePremiumUpgrade), + upgradeLifecycleStateFlow, authDiskSource.activeUserIdChangesFlow.flatMapLatest { userId -> userId ?.let { id -> @@ -144,47 +143,35 @@ class PremiumStateManagerImpl( ) { account, isInAppBillingSupported, - featureFlagEnabled, + lifecycle, isUpgradeCardDismissed, vaultDataState, -> - BannerInputs( - account = account, - isInAppBillingSupported = isInAppBillingSupported, - featureFlagEnabled = featureFlagEnabled, - isUpgradeCardDismissed = isUpgradeCardDismissed, - vaultDataState = vaultDataState, - ) - } - .combine(upgradeLifecycleStateFlow) { inputs, lifecycle -> - val profile = inputs.account?.profile ?: return@combine PremiumCard.NONE - if (!inputs.featureFlagEnabled) return@combine PremiumCard.NONE - val initialCard = when (lifecycle) { - UpgradeLifecycleState.Free -> PremiumCard.UPGRADE - UpgradeLifecycleState.UpgradePending -> PremiumCard.NONE - is UpgradeLifecycleState.Premium -> { - lifecycle.subscriptionStatus.premiumCardState() - } - } - when (initialCard) { - PremiumCard.UPGRADE -> { - val isAccountOldEnough = profile.creationDate.isOlderThanDays( - days = PREMIUM_UPGRADE_MINIMUM_ACCOUNT_AGE_DAYS, - clock = clock, - ) - val itemCount = inputs.vaultDataState.activeVaultItemCount() - val showCard = inputs.isInAppBillingSupported && - isAccountOldEnough && - itemCount >= PREMIUM_UPGRADE_MINIMUM_VAULT_ITEMS && - !inputs.isUpgradeCardDismissed - initialCard.takeIf { showCard } ?: PremiumCard.NONE - } - - PremiumCard.NEEDS_ATTENTION, - PremiumCard.NONE, - -> initialCard - } + val profile = account?.profile ?: return@combine PremiumCard.NONE + val initialCard = when (lifecycle) { + UpgradeLifecycleState.Free -> PremiumCard.UPGRADE + UpgradeLifecycleState.UpgradePending -> PremiumCard.NONE + is UpgradeLifecycleState.Premium -> lifecycle.subscriptionStatus.premiumCardState() } + when (initialCard) { + PremiumCard.UPGRADE -> { + val isAccountOldEnough = profile.creationDate.isOlderThanDays( + days = PREMIUM_UPGRADE_MINIMUM_ACCOUNT_AGE_DAYS, + clock = clock, + ) + val itemCount = vaultDataState.activeVaultItemCount() + val showCard = isInAppBillingSupported && + isAccountOldEnough && + itemCount >= PREMIUM_UPGRADE_MINIMUM_VAULT_ITEMS && + !isUpgradeCardDismissed + initialCard.takeIf { showCard } ?: PremiumCard.NONE + } + + PremiumCard.NEEDS_ATTENTION, + PremiumCard.NONE, + -> initialCard + } + } .stateIn( scope = unconfinedScope, started = SharingStarted.Eagerly, @@ -211,17 +198,16 @@ class PremiumStateManagerImpl( * personal subscription to manage. */ override val isPlanRowEligibleFlow: StateFlow = - combine( - authDiskSource.userStateFlow, - featureFlagManager.getFeatureFlagFlow(FlagKey.MobilePremiumUpgrade), - ) { userState, featureFlagEnabled -> - val profile = userState?.activeAccount?.profile ?: return@combine false - val hasPremium = profile.hasPremiumPersonally == true || - profile.hasPremiumFromOrganization == true - val isPremiumFromSelf = profile.hasPremiumPersonally == true - val isOrgOnlyPremium = hasPremium && !isPremiumFromSelf - featureFlagEnabled && !isOrgOnlyPremium - } + authDiskSource + .userStateFlow + .map { userState -> + val profile = userState?.activeAccount?.profile ?: return@map false + val hasPremium = profile.hasPremiumPersonally == true || + profile.hasPremiumFromOrganization == true + val isPremiumFromSelf = profile.hasPremiumPersonally == true + val isOrgOnlyPremium = hasPremium && !isPremiumFromSelf + !isOrgOnlyPremium + } .stateIn( scope = unconfinedScope, started = SharingStarted.Eagerly, @@ -311,8 +297,7 @@ class PremiumStateManagerImpl( override val isSelfHosted: Boolean get() = isSelfHostedFlow.value override fun isInAppUpgradeAvailable(): Boolean = - billingRepository.isInAppBillingSupportedFlow.value && - featureFlagManager.getFeatureFlag(FlagKey.MobilePremiumUpgrade) + billingRepository.isInAppBillingSupportedFlow.value override fun dismissPremiumUpgradeBanner() { val activeUserId = authDiskSource.userState?.activeUserId ?: return @@ -406,14 +391,6 @@ class PremiumStateManagerImpl( } } -private data class BannerInputs( - val account: AccountJson?, - val isInAppBillingSupported: Boolean, - val featureFlagEnabled: Boolean, - val isUpgradeCardDismissed: Boolean, - val vaultDataState: DataState, -) - /** * Returns a [PremiumCard] for the given [SubscriptionStatusState] and subscription substate. */ diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/billing/manager/PremiumStateManagerTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/billing/manager/PremiumStateManagerTest.kt index dd8c48bf5f..8121fcb9e2 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/data/billing/manager/PremiumStateManagerTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/billing/manager/PremiumStateManagerTest.kt @@ -73,15 +73,8 @@ class PremiumStateManagerTest { every { vaultDataStateFlow } returns mutableVaultDataStateFlow } - private val mutableMobilePremiumUpgradeFlagFlow = MutableStateFlow(true) private val mutableDebugDisableSelfHostPremiumCheckFlagFlow = MutableStateFlow(false) private val featureFlagManager: FeatureFlagManager = mockk { - every { - getFeatureFlagFlow(FlagKey.MobilePremiumUpgrade) - } returns mutableMobilePremiumUpgradeFlagFlow - every { - getFeatureFlag(FlagKey.MobilePremiumUpgrade) - } answers { mutableMobilePremiumUpgradeFlagFlow.value } every { getFeatureFlag(FlagKey.DebugDisableSelfHostPremiumCheck) } answers { mutableDebugDisableSelfHostPremiumCheckFlagFlow.value } @@ -141,15 +134,6 @@ class PremiumStateManagerTest { } } - @Test - fun `ineligible when feature flag is disabled should emit NONE`() = runTest { - mutableMobilePremiumUpgradeFlagFlow.value = false - val manager = createManager() - manager.premiumCardStateFlow.test { - assertEquals(PremiumCard.NONE, awaitItem()) - } - } - @Test fun `ineligible when banner is dismissed should emit NONE`() = runTest { fakeSettingsDiskSource.storePremiumUpgradeBannerDismissed( @@ -356,7 +340,7 @@ class PremiumStateManagerTest { } @Test - fun `isInAppUpgradeAvailable should return true when billing supported and flag enabled`() { + fun `isInAppUpgradeAvailable should return true when billing is supported`() { val manager = createManager() assertTrue(manager.isInAppUpgradeAvailable()) } @@ -368,21 +352,6 @@ class PremiumStateManagerTest { assertFalse(manager.isInAppUpgradeAvailable()) } - @Test - fun `isInAppUpgradeAvailable should return false when feature flag disabled`() { - mutableMobilePremiumUpgradeFlagFlow.value = false - val manager = createManager() - assertFalse(manager.isInAppUpgradeAvailable()) - } - - @Test - fun `isInAppUpgradeAvailable should return false when both conditions are false`() { - mutableIsInAppBillingSupportedFlow.value = false - mutableMobilePremiumUpgradeFlagFlow.value = false - val manager = createManager() - assertFalse(manager.isInAppUpgradeAvailable()) - } - @Test fun `isSelfHosted should return false on cloud environment regardless of flag`() { fakeEnvironmentRepository.environment = Environment.Prod.Us @@ -474,14 +443,13 @@ class PremiumStateManagerTest { } @Test - fun `isPlanRowEligibleFlow emits true for a free user when feature flag is enabled`() = - runTest { - fakeAuthDiskSource.userState = userStateJsonWith(account = createAccountJson()) - val manager = createManager() - manager.isPlanRowEligibleFlow.test { - assertTrue(awaitItem()) - } + fun `isPlanRowEligibleFlow emits true when user is a free user`() = runTest { + fakeAuthDiskSource.userState = userStateJsonWith(account = createAccountJson()) + val manager = createManager() + manager.isPlanRowEligibleFlow.test { + assertTrue(awaitItem()) } + } @Test fun `isPlanRowEligibleFlow emits true for a personal Premium user`() = runTest { @@ -506,15 +474,6 @@ class PremiumStateManagerTest { } } - @Test - fun `isPlanRowEligibleFlow emits false when feature flag is disabled`() = runTest { - mutableMobilePremiumUpgradeFlagFlow.value = false - val manager = createManager() - manager.isPlanRowEligibleFlow.test { - assertFalse(awaitItem()) - } - } - @Test fun `isPlanRowEligibleFlow emits false when userState is null`() = runTest { fakeAuthDiskSource.userState = null diff --git a/core/src/main/kotlin/com/bitwarden/core/data/manager/model/FlagKey.kt b/core/src/main/kotlin/com/bitwarden/core/data/manager/model/FlagKey.kt index 9ce83cd50f..479ca3fa44 100644 --- a/core/src/main/kotlin/com/bitwarden/core/data/manager/model/FlagKey.kt +++ b/core/src/main/kotlin/com/bitwarden/core/data/manager/model/FlagKey.kt @@ -34,7 +34,6 @@ sealed class FlagKey { NoLogoutOnKdfChange, MigrateMyVaultToMyItems, CardScanner, - MobilePremiumUpgrade, ManageDevices, AttachmentUpdates, V2EncryptionJitPassword, @@ -95,14 +94,6 @@ sealed class FlagKey { override val defaultValue: Boolean = false } - /** - * Data object holding the feature flag key for the mobile Premium upgrade feature. - */ - data object MobilePremiumUpgrade : FlagKey() { - override val keyName: String = "pm-31697-premium-upgrade-path" - override val defaultValue: Boolean = false - } - /** * Data object holding the feature flag key for the Attachment Feature Updates. */ diff --git a/core/src/test/kotlin/com/bitwarden/core/data/manager/model/FlagKeyTest.kt b/core/src/test/kotlin/com/bitwarden/core/data/manager/model/FlagKeyTest.kt index 3d22a41bd3..0d5b2df743 100644 --- a/core/src/test/kotlin/com/bitwarden/core/data/manager/model/FlagKeyTest.kt +++ b/core/src/test/kotlin/com/bitwarden/core/data/manager/model/FlagKeyTest.kt @@ -20,10 +20,6 @@ class FlagKeyTest { FlagKey.MigrateMyVaultToMyItems.keyName, "pm-20558-migrate-myvault-to-myitems", ) - assertEquals( - FlagKey.MobilePremiumUpgrade.keyName, - "pm-31697-premium-upgrade-path", - ) assertEquals( FlagKey.AttachmentUpdates.keyName, "pm-34224-mobile-attachment-updates", @@ -93,7 +89,6 @@ class FlagKeyTest { FlagKey.BitwardenAuthenticationEnabled, FlagKey.ForceUpdateKdfSettings, FlagKey.MigrateMyVaultToMyItems, - FlagKey.MobilePremiumUpgrade, FlagKey.AttachmentUpdates, FlagKey.V2EncryptionJitPassword, FlagKey.V2EncryptionKeyConnector, diff --git a/ui/src/main/kotlin/com/bitwarden/ui/platform/components/debug/FeatureFlagListItems.kt b/ui/src/main/kotlin/com/bitwarden/ui/platform/components/debug/FeatureFlagListItems.kt index 55daf176fe..13e667e100 100644 --- a/ui/src/main/kotlin/com/bitwarden/ui/platform/components/debug/FeatureFlagListItems.kt +++ b/ui/src/main/kotlin/com/bitwarden/ui/platform/components/debug/FeatureFlagListItems.kt @@ -28,7 +28,6 @@ fun FlagKey.ListItemContent( FlagKey.NoLogoutOnKdfChange, FlagKey.MigrateMyVaultToMyItems, FlagKey.CardScanner, - FlagKey.MobilePremiumUpgrade, FlagKey.ManageDevices, FlagKey.AttachmentUpdates, FlagKey.V2EncryptionJitPassword, @@ -94,7 +93,6 @@ private fun FlagKey.getDisplayLabel(): String = when (this) { FlagKey.MigrateMyVaultToMyItems -> stringResource(BitwardenString.migrate_my_vault_to_my_items) FlagKey.CardScanner -> stringResource(BitwardenString.scan_card) - FlagKey.MobilePremiumUpgrade -> stringResource(BitwardenString.mobile_premium_upgrade) FlagKey.ManageDevices -> stringResource(BitwardenString.manage_devices) FlagKey.AttachmentUpdates -> stringResource(BitwardenString.attachment_updates) FlagKey.V2EncryptionJitPassword -> stringResource(BitwardenString.v2_encryption_jit_password) diff --git a/ui/src/main/res/values/strings_non_localized.xml b/ui/src/main/res/values/strings_non_localized.xml index 20b1a29d6d..8e6836f620 100644 --- a/ui/src/main/res/values/strings_non_localized.xml +++ b/ui/src/main/res/values/strings_non_localized.xml @@ -48,7 +48,6 @@ Migrate My Vault to My Items Trigger cookie acquisition Clear SSO cookies - Mobile Premium Upgrade Attachment Updates V2 Encryption - TDE V2 Encryption - Key Connector