Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ fun VaultItemLoginContent(
onCopyTotpClick = vaultLoginItemTypeHandlers.onCopyTotpCodeClick,
onAuthenticatorHelpToolTipClick = vaultLoginItemTypeHandlers
.onAuthenticatorHelpToolTipClick,
onPremiumRequiredClick = vaultCommonItemTypeHandlers.onPremiumRequiredClick,
modifier = Modifier
.standardHorizontalMargin()
.fillMaxWidth()
Expand Down Expand Up @@ -285,12 +286,14 @@ private fun PasswordField(
}
}

@Suppress("LongMethod")
@Composable
private fun TotpField(
totpCodeItemData: TotpCodeItemData,
enabled: Boolean,
onCopyTotpClick: () -> Unit,
onAuthenticatorHelpToolTipClick: () -> Unit,
onPremiumRequiredClick: () -> Unit,
modifier: Modifier = Modifier,
) {
if (enabled) {
Expand Down Expand Up @@ -333,7 +336,19 @@ private fun TotpField(
contentDescription = stringResource(id = BitwardenString.authenticator_key_help),
isExternalLink = true,
),
supportingText = stringResource(id = BitwardenString.premium_subscription_required),
supportingContentPadding = PaddingValues(),
supportingContent = {
BitwardenClickableText(
label = stringResource(id = BitwardenString.premium_subscription_required),
onClick = onPremiumRequiredClick,
style = BitwardenTheme.typography.labelMedium,
innerPadding = PaddingValues(all = 16.dp),
cornerSize = 0.dp,
modifier = Modifier
.fillMaxWidth()
.testTag(tag = "TotpPremiumRequiredButton"),
)
},
enabled = false,
singleLine = false,
onValueChange = { },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,7 @@ private val PREVIEW_COMMON_HANDLERS: VaultCommonItemTypeHandlers =
onAttachmentPreviewClick = {},
onCopyNotesClick = {},
onPasswordHistoryClick = {},
onPremiumRequiredClick = {},
onUpgradeToPremiumClick = {},
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -311,10 +311,10 @@ private fun VaultItemDialogs(
onUpgradeToPremiumClick: () -> Unit,
) {
when (dialog) {
is VaultItemState.DialogState.ArchiveRequiresPremium -> {
is VaultItemState.DialogState.RequiresPremium -> {
BitwardenTwoButtonDialog(
title = stringResource(id = BitwardenString.premium_subscription_required),
message = stringResource(id = BitwardenString.archiving_items_is_a_premium_feature),
message = dialog.message(),
confirmButtonText = stringResource(id = BitwardenString.upgrade_to_premium),
dismissButtonText = stringResource(id = BitwardenString.cancel),
onConfirmClick = onUpgradeToPremiumClick,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,7 @@ class VaultItemViewModel @Inject constructor(
is VaultItemAction.Common.PasswordHistoryClick -> handlePasswordHistoryClick()
VaultItemAction.Common.ArchiveClick -> handleArchiveClick()
VaultItemAction.Common.UnarchiveClick -> handleUnarchiveClick()
VaultItemAction.Common.PremiumRequiredClick -> handlePremiumRequiredClick()
VaultItemAction.Common.UpgradeToPremiumClick -> handleUpgradeToPremiumClick()
}
}
Expand Down Expand Up @@ -690,7 +691,11 @@ class VaultItemViewModel @Inject constructor(
private fun handleArchiveClick() {
if (!state.hasPremium) {
mutableStateFlow.update {
it.copy(dialog = VaultItemState.DialogState.ArchiveRequiresPremium)
it.copy(
dialog = VaultItemState.DialogState.RequiresPremium(
message = BitwardenString.archiving_items_is_a_premium_feature.asText(),
),
)
}
return
}
Expand Down Expand Up @@ -741,6 +746,16 @@ class VaultItemViewModel @Inject constructor(
}
}

private fun handlePremiumRequiredClick() {
mutableStateFlow.update {
it.copy(
dialog = VaultItemState.DialogState.RequiresPremium(
message = BitwardenString.totp_is_a_premium_feature.asText(),
),
)
}
}

private fun handleUpgradeToPremiumClick() {
updateDialogState(dialog = null)
if (premiumStateManager.isInAppUpgradeAvailable()) {
Expand Down Expand Up @@ -2335,10 +2350,13 @@ data class VaultItemState(
sealed class DialogState : Parcelable {

/**
* Displays a dialog to the user indicating that archiving requires a Premium account.
* Displays a dialog to the user indicating that the feature they are interacting with
* requires a Premium account.
*/
@Parcelize
data object ArchiveRequiresPremium : DialogState()
data class RequiresPremium(
val message: Text,
) : DialogState()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

๐ŸŽจ SUGGESTED: The KDoc above this state is now stale after the rename โ€” it still describes only the archive case.

Details

RequiresPremium is now a generic, message-parameterized dialog used for both archiving and TOTP (and potentially future premium prompts), but the KDoc still reads "indicating that archiving requires a Premium account." Consider updating it to reflect that the message is supplied per use case, e.g. "Displays a dialog indicating the requested action requires a Premium account, using the provided message."

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@claude This has been addressed.

@claude claude Bot Jun 23, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude finished @david-livefront's task in 1s โ€”โ€” View job


I'll analyze this and get back to you.


/**
* Displays a generic dialog to the user.
Expand Down Expand Up @@ -2500,6 +2518,11 @@ sealed class VaultItemAction {
*/
data object UnarchiveClick : Common()

/**
* The user has clicked the Premium subscription required button.
*/
data object PremiumRequiredClick : Common()

/**
* The user has clicked the upgrade to Premium button.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ data class VaultCommonItemTypeHandlers(
val onAttachmentPreviewClick: (VaultItemState.ViewState.Content.Common.AttachmentItem) -> Unit,
val onCopyNotesClick: () -> Unit,
val onPasswordHistoryClick: () -> Unit,
val onPremiumRequiredClick: () -> Unit,
val onUpgradeToPremiumClick: () -> Unit,
) {
@Suppress("UndocumentedPublicClass")
Expand Down Expand Up @@ -61,6 +62,9 @@ data class VaultCommonItemTypeHandlers(
onPasswordHistoryClick = {
viewModel.trySendAction(VaultItemAction.Common.PasswordHistoryClick)
},
onPremiumRequiredClick = {
viewModel.trySendAction(VaultItemAction.Common.PremiumRequiredClick)
},
onUpgradeToPremiumClick = {
viewModel.trySendAction(VaultItemAction.Common.UpgradeToPremiumClick)
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -268,24 +268,35 @@ class VaultItemScreenTest : BitwardenComposeTest() {
}

@Test
fun `ArchiveRequiresPremium dialog should display based on state`() {
fun `RequiresPremium dialog should display based on state`() {
composeTestRule.assertNoDialogExists()
mutableStateFlow.update {
it.copy(dialog = VaultItemState.DialogState.ArchiveRequiresPremium)
it.copy(
dialog = VaultItemState.DialogState.RequiresPremium(
message = "You need Premium".asText(),
),
)
}

composeTestRule
.onNodeWithText(text = "Premium subscription required")
.assert(hasAnyAncestor(isDialog()))
.assertIsDisplayed()
composeTestRule
.onNodeWithText(text = "You need Premium")
.assert(hasAnyAncestor(isDialog()))
.assertIsDisplayed()
}

@Suppress("MaxLineLength")
@Test
fun `ArchiveRequiresPremium dialog on upgrade to Premium click should emit UpgradeToPremiumClick`() {
fun `RequiresPremium dialog on upgrade to Premium click should emit UpgradeToPremiumClick`() {
composeTestRule.assertNoDialogExists()
mutableStateFlow.update {
it.copy(dialog = VaultItemState.DialogState.ArchiveRequiresPremium)
it.copy(
dialog = VaultItemState.DialogState.RequiresPremium(
message = "You need Premium".asText(),
),
)
}

composeTestRule
Expand Down Expand Up @@ -2374,6 +2385,28 @@ class VaultItemScreenTest : BitwardenComposeTest() {
}
}

@Test
fun `in login state, on premium required click should send PremiumRequiredClick`() {
mutableStateFlow.update { currentState ->
currentState.copy(
viewState = DEFAULT_LOGIN_VIEW_STATE.copy(
type = DEFAULT_LOGIN.copy(
canViewTotpCode = false,
),
),
)
}

composeTestRule.onNodeWithTextAfterScroll(text = "Authenticator key")
composeTestRule
.onNodeWithText(text = "Premium subscription required")
.performClick()

verify {
viewModel.trySendAction(VaultItemAction.Common.PremiumRequiredClick)
}
}

@Test
fun `in login state, on totp help tooltip click should send AuthenticatorHelpToolTipClick`() {
mutableStateFlow.update { currentState ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
}

@Test
fun `ArchiveClick without Premium should show ArchiveRequiresPremium dialog`() = runTest {
fun `ArchiveClick without Premium should show RequiresPremium dialog`() = runTest {
mutableUserStateFlow.update {
it?.copy(accounts = listOf(DEFAULT_USER_ACCOUNT.copy(isPremium = false)))
}
Expand All @@ -277,7 +277,25 @@ class VaultItemViewModelTest : BaseViewModelTest() {
assertEquals(
DEFAULT_STATE.copy(
hasPremium = false,
dialog = VaultItemState.DialogState.ArchiveRequiresPremium,
dialog = VaultItemState.DialogState.RequiresPremium(
message = BitwardenString.archiving_items_is_a_premium_feature.asText(),
),
),
viewModel.stateFlow.value,
)
}

@Test
fun `PremiumRequiredClick should show RequiresPremium dialog`() = runTest {
val viewModel = createViewModel(state = null)

viewModel.trySendAction(VaultItemAction.Common.PremiumRequiredClick)

assertEquals(
DEFAULT_STATE.copy(
dialog = VaultItemState.DialogState.RequiresPremium(
message = BitwardenString.totp_is_a_premium_feature.asText(),
),
),
viewModel.stateFlow.value,
)
Expand Down
1 change: 1 addition & 0 deletions ui/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1210,6 +1210,7 @@ Do you want to switch to this account?</string>
<string name="item_moved_to_archived">Item moved to archive</string>
<string name="item_moved_to_vault">Item moved to vault</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="totp_is_a_premium_feature">Authenticator key (TOTP) 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="plan">Plan</string>
<string name="to_manage_your_premium_subscription_youll_need_to_login_to_your_web_vault_on_a_computer">To manage your Premium subscription, youโ€™ll need to login to your web vault on a computer.</string>
Expand Down
Loading