mirror of
https://github.com/bitwarden/android.git
synced 2026-08-24 14:28:58 -05:00
[PM-33515] feat: Render premium upgrade banner in Vault UI (#6698)
This commit is contained in:
+5
@@ -54,4 +54,9 @@ interface DebugMenuRepository {
|
||||
* Clears all stored SSO cookie configurations.
|
||||
*/
|
||||
fun clearSsoCookies()
|
||||
|
||||
/**
|
||||
* Resets the premium upgrade banner dismiss status for the current user.
|
||||
*/
|
||||
fun resetPremiumUpgradeBannerDismiss()
|
||||
}
|
||||
|
||||
+8
@@ -74,4 +74,12 @@ class DebugMenuRepositoryImpl(
|
||||
override fun clearSsoCookies() {
|
||||
cookieDiskSource.clearCookies()
|
||||
}
|
||||
|
||||
override fun resetPremiumUpgradeBannerDismiss() {
|
||||
val currentUserId = authDiskSource.userState?.activeUserId ?: return
|
||||
settingsDiskSource.storePremiumUpgradeBannerDismissed(
|
||||
userId = currentUserId,
|
||||
isDismissed = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+30
@@ -110,6 +110,15 @@ fun DebugMenuScreen(
|
||||
.fillMaxWidth()
|
||||
.standardHorizontalMargin(),
|
||||
)
|
||||
Spacer(Modifier.height(height = 16.dp))
|
||||
BitwardenHorizontalDivider()
|
||||
Spacer(Modifier.height(height = 16.dp))
|
||||
BitwardenListHeaderText(
|
||||
label = stringResource(BitwardenString.cookies),
|
||||
modifier = Modifier
|
||||
.standardHorizontalMargin()
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
Spacer(Modifier.height(height = 8.dp))
|
||||
BitwardenFilledButton(
|
||||
label = stringResource(BitwardenString.trigger_cookie_acquisition),
|
||||
@@ -135,6 +144,27 @@ fun DebugMenuScreen(
|
||||
Spacer(Modifier.height(height = 16.dp))
|
||||
BitwardenHorizontalDivider()
|
||||
Spacer(Modifier.height(height = 16.dp))
|
||||
BitwardenListHeaderText(
|
||||
label = stringResource(BitwardenString.premium),
|
||||
modifier = Modifier
|
||||
.standardHorizontalMargin()
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
Spacer(Modifier.height(height = 8.dp))
|
||||
BitwardenFilledButton(
|
||||
label = stringResource(BitwardenString.reset_premium_upgrade_banner),
|
||||
onClick = {
|
||||
viewModel.trySendAction(
|
||||
DebugMenuAction.ResetPremiumUpgradeBanner,
|
||||
)
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.standardHorizontalMargin(),
|
||||
)
|
||||
Spacer(Modifier.height(height = 16.dp))
|
||||
BitwardenHorizontalDivider()
|
||||
Spacer(Modifier.height(height = 16.dp))
|
||||
BitwardenListHeaderText(
|
||||
label = stringResource(BitwardenString.error_reports),
|
||||
modifier = Modifier
|
||||
|
||||
+10
@@ -65,6 +65,7 @@ class DebugMenuViewModel @Inject constructor(
|
||||
DebugMenuAction.GenerateErrorReportClick -> handleErrorReportClick()
|
||||
DebugMenuAction.TriggerCookieAcquisition -> handleTriggerCookieAcquisition()
|
||||
DebugMenuAction.ClearSsoCookies -> handleClearSsoCookies()
|
||||
DebugMenuAction.ResetPremiumUpgradeBanner -> handleResetPremiumUpgradeBanner()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,6 +106,10 @@ class DebugMenuViewModel @Inject constructor(
|
||||
debugMenuRepository.clearSsoCookies()
|
||||
}
|
||||
|
||||
private fun handleResetPremiumUpgradeBanner() {
|
||||
debugMenuRepository.resetPremiumUpgradeBannerDismiss()
|
||||
}
|
||||
|
||||
private fun handleTriggerCookieAcquisition() {
|
||||
cookieAcquisitionRequestManager.setPendingCookieAcquisition(
|
||||
data = CookieAcquisitionRequest(
|
||||
@@ -206,6 +211,11 @@ sealed class DebugMenuAction {
|
||||
*/
|
||||
data object ClearSsoCookies : DebugMenuAction()
|
||||
|
||||
/**
|
||||
* User has clicked to reset the premium upgrade banner dismiss status.
|
||||
*/
|
||||
data object ResetPremiumUpgradeBanner : DebugMenuAction()
|
||||
|
||||
/**
|
||||
* Internal actions not triggered from the UI.
|
||||
*/
|
||||
|
||||
@@ -488,7 +488,19 @@ private fun ActionCard(
|
||||
) {
|
||||
when (actionCardState) {
|
||||
VaultState.ActionCardState.UpgradePremium -> {
|
||||
// UI implementation in PM-33515.
|
||||
BitwardenActionCard(
|
||||
cardTitle = stringResource(
|
||||
id = BitwardenString.unlock_advanced_security_features,
|
||||
),
|
||||
cardSubtitle = stringResource(
|
||||
id = BitwardenString
|
||||
.a_premium_plan_gives_you_more_tools_to_stay_secure_and_in_control,
|
||||
),
|
||||
actionText = stringResource(id = BitwardenString.upgrade_to_premium),
|
||||
onActionClick = { vaultHandlers.actionCardClick(actionCardState) },
|
||||
onDismissClick = { vaultHandlers.dismissActionCardClick(actionCardState) },
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
VaultState.ActionCardState.IntroducingArchive -> {
|
||||
|
||||
+38
@@ -182,6 +182,44 @@ class DebugMenuRepositoryTest {
|
||||
mockCookieDiskSource.clearCookies()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `resetPremiumUpgradeBannerDismiss should store null for the current user`() {
|
||||
val userId = "testUserId"
|
||||
val mockUserStateJson = mockk<UserStateJson>(relaxed = true) {
|
||||
every { activeUserId } returns userId
|
||||
}
|
||||
every { mockAuthDiskSource.userState } returns mockUserStateJson
|
||||
every {
|
||||
mockSettingsDiskSource.storePremiumUpgradeBannerDismissed(
|
||||
userId = any(),
|
||||
isDismissed = any(),
|
||||
)
|
||||
} just runs
|
||||
|
||||
debugMenuRepository.resetPremiumUpgradeBannerDismiss()
|
||||
|
||||
verify(exactly = 1) {
|
||||
mockSettingsDiskSource.storePremiumUpgradeBannerDismissed(
|
||||
userId = userId,
|
||||
isDismissed = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `resetPremiumUpgradeBannerDismiss should do nothing if no active user`() {
|
||||
every { mockAuthDiskSource.userState } returns null
|
||||
|
||||
debugMenuRepository.resetPremiumUpgradeBannerDismiss()
|
||||
|
||||
verify(exactly = 0) {
|
||||
mockSettingsDiskSource.storePremiumUpgradeBannerDismissed(
|
||||
userId = any(),
|
||||
isDismissed = any(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private const val TEST_STRING_VALUE = "test"
|
||||
|
||||
+12
@@ -159,6 +159,18 @@ class DebugMenuScreenTest : BitwardenComposeTest() {
|
||||
verify(exactly = 1) { viewModel.trySendAction(DebugMenuAction.ClearSsoCookies) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `reset premium upgrade banner should send ResetPremiumUpgradeBanner action`() {
|
||||
composeTestRule
|
||||
.onNodeWithText("Reset premium upgrade banner")
|
||||
.performScrollTo()
|
||||
.performClick()
|
||||
|
||||
verify(exactly = 1) {
|
||||
viewModel.trySendAction(DebugMenuAction.ResetPremiumUpgradeBanner)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `reset all coach mark tours should send ResetCoachMarkTourStatuses action`() {
|
||||
composeTestRule
|
||||
|
||||
+10
@@ -152,6 +152,16 @@ class DebugMenuViewModelTest : BaseViewModelTest() {
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `ResetPremiumUpgradeBanner should call resetPremiumUpgradeBannerDismiss on DebugMenuRepository`() {
|
||||
val viewModel = createViewModel()
|
||||
viewModel.trySendAction(DebugMenuAction.ResetPremiumUpgradeBanner)
|
||||
verify(exactly = 1) {
|
||||
mockDebugMenuRepository.resetPremiumUpgradeBannerDismiss()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `TriggerCookieAcquisition should set pending cookie acquisition`() =
|
||||
runTest {
|
||||
|
||||
+1
-1
@@ -1047,7 +1047,7 @@ class SearchScreenTest : BitwardenComposeTest() {
|
||||
.assert(hasAnyAncestor(isDialog()))
|
||||
.assertIsDisplayed()
|
||||
composeTestRule
|
||||
.onNodeWithText(text = "Upgrade to premium")
|
||||
.onNodeWithText(text = "Upgrade to Premium")
|
||||
.assert(hasAnyAncestor(isDialog()))
|
||||
.performClick()
|
||||
|
||||
|
||||
+1
-1
@@ -978,7 +978,7 @@ class AddEditSendScreenTest : BitwardenComposeTest() {
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText("Upgrade to premium")
|
||||
.onNodeWithText("Upgrade to Premium")
|
||||
.performClick()
|
||||
|
||||
verify { viewModel.trySendAction(AddEditSendAction.UpgradeToPremiumClick) }
|
||||
|
||||
+1
-1
@@ -275,7 +275,7 @@ class VaultAddEditScreenTest : BitwardenComposeTest() {
|
||||
)
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText(text = "Upgrade to premium")
|
||||
.onNodeWithText(text = "Upgrade to Premium")
|
||||
.assert(hasAnyAncestor(isDialog()))
|
||||
.performClick()
|
||||
|
||||
|
||||
+2
-2
@@ -253,7 +253,7 @@ class VaultItemScreenTest : BitwardenComposeTest() {
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText(text = "Upgrade to premium")
|
||||
.onNodeWithText(text = "Upgrade to Premium")
|
||||
.assert(hasAnyAncestor(isDialog()))
|
||||
.performClick()
|
||||
|
||||
@@ -774,7 +774,7 @@ class VaultItemScreenTest : BitwardenComposeTest() {
|
||||
.assertIsDisplayed()
|
||||
|
||||
composeTestRule
|
||||
.onAllNodesWithText(text = "Upgrade to premium")
|
||||
.onAllNodesWithText(text = "Upgrade to Premium")
|
||||
.filterToOne(hasAnyAncestor(isDialog()))
|
||||
.performClick()
|
||||
|
||||
|
||||
+1
-1
@@ -2516,7 +2516,7 @@ class VaultItemListingScreenTest : BitwardenComposeTest() {
|
||||
.assert(hasAnyAncestor(isDialog()))
|
||||
.assertIsDisplayed()
|
||||
composeTestRule
|
||||
.onNodeWithText(text = "Upgrade to premium")
|
||||
.onNodeWithText(text = "Upgrade to Premium")
|
||||
.assert(hasAnyAncestor(isDialog()))
|
||||
.performClick()
|
||||
|
||||
|
||||
@@ -874,7 +874,7 @@ class VaultScreenTest : BitwardenComposeTest() {
|
||||
.assert(hasAnyAncestor(isDialog()))
|
||||
.assertIsDisplayed()
|
||||
composeTestRule
|
||||
.onNodeWithText(text = "Upgrade to premium")
|
||||
.onNodeWithText(text = "Upgrade to Premium")
|
||||
.assert(hasAnyAncestor(isDialog()))
|
||||
.performClick()
|
||||
|
||||
@@ -1554,6 +1554,61 @@ class VaultScreenTest : BitwardenComposeTest() {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `UpgradePremium action card should display when eligible`() {
|
||||
mutableStateFlow.value = DEFAULT_STATE.copy(
|
||||
isPremiumUpgradeBannerEligible = true,
|
||||
viewState = DEFAULT_CONTENT_VIEW_STATE,
|
||||
)
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText(text = "Unlock advanced security features")
|
||||
.assertIsDisplayed()
|
||||
composeTestRule
|
||||
.onNodeWithText(text = "Upgrade to Premium")
|
||||
.assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `UpgradePremium action card CTA click should send ActionCardClick`() {
|
||||
mutableStateFlow.value = DEFAULT_STATE.copy(
|
||||
isPremiumUpgradeBannerEligible = true,
|
||||
viewState = DEFAULT_CONTENT_VIEW_STATE,
|
||||
)
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText(text = "Upgrade to Premium")
|
||||
.assertIsDisplayed()
|
||||
.performClick()
|
||||
|
||||
verify(exactly = 1) {
|
||||
viewModel.trySendAction(
|
||||
VaultAction.ActionCardClick(
|
||||
actionCard = VaultState.ActionCardState.UpgradePremium,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `UpgradePremium action card dismiss click should send DismissActionCardClick`() {
|
||||
mutableStateFlow.value = DEFAULT_STATE.copy(
|
||||
isPremiumUpgradeBannerEligible = true,
|
||||
viewState = DEFAULT_CONTENT_VIEW_STATE,
|
||||
)
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithContentDescription(label = "Close")
|
||||
.assertIsDisplayed()
|
||||
.performClick()
|
||||
|
||||
verify(exactly = 1) {
|
||||
viewModel.trySendAction(
|
||||
VaultAction.DismissActionCardClick(VaultState.ActionCardState.UpgradePremium),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `collection data should update according to the state`() {
|
||||
val collectionsHeader = "COLLECTIONS (1)"
|
||||
|
||||
@@ -1204,7 +1204,9 @@ Do you want to switch to this account?</string>
|
||||
<string name="item_moved_to_vault">Item moved to vault</string>
|
||||
<string name="archive_unavailable">Archive unavailable</string>
|
||||
<string name="archiving_items_is_a_premium_feature">Archiving items is a Premium feature. Your current plan does not include access to this feature.</string>
|
||||
<string name="upgrade_to_premium">Upgrade to premium</string>
|
||||
<string name="upgrade_to_premium">Upgrade to Premium</string>
|
||||
<string name="unlock_advanced_security_features">Unlock advanced security features</string>
|
||||
<string name="a_premium_plan_gives_you_more_tools_to_stay_secure_and_in_control">A Premium plan gives you more tools to stay secure and in control.</string>
|
||||
<string name="this_item_is_archived">This item is archived.</string>
|
||||
<string name="introducing_archive">Introducing archive</string>
|
||||
<string name="keep_items_you_dont_need_right_now_safe_but_out_sight">Keep items you don’t need right now safe but out of sight.</string>
|
||||
|
||||
@@ -30,6 +30,9 @@
|
||||
<string name="generate_crash">Generate crash</string>
|
||||
<string name="generate_error_report">Generate error report</string>
|
||||
<string name="error_reports">Error reports</string>
|
||||
<string name="cookies">Cookies</string>
|
||||
<string name="premium">Premium</string>
|
||||
<string name="reset_premium_upgrade_banner">Reset premium upgrade banner</string>
|
||||
<string name="bitwarden_authentication_enabled">Bitwarden authentication enabled</string>
|
||||
<string name="import_format_label_bitwarden_json">Bitwarden (.json)</string>
|
||||
<string name="import_format_label_2fas_json">2FAS (no password)</string>
|
||||
|
||||
Reference in New Issue
Block a user