mirror of
https://github.com/bitwarden/android.git
synced 2026-10-22 02:53:19 -05:00
[PM-33999] chore: Standardize casing of Premium account status references (#6707)
This commit is contained in:
1 parent
078b4e6f1b
commit
c786756f5b
55 files changed
+106
-106
No files matched your search
+1
-1
@@ -8,7 +8,7 @@ import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
/**
|
||||
* F-Droid implementation of [PlayBillingManager]. Always returns `true` since
|
||||
* F-Droid users are eligible for the premium upgrade flow.
|
||||
* F-Droid users are eligible for the Premium upgrade flow.
|
||||
*/
|
||||
@OmitFromCoverage
|
||||
@Suppress("UnusedParameter")
|
||||
|
||||
+1
-1
@@ -42,7 +42,7 @@ data class AccountJson(
|
||||
* @property name The user's name (if applicable).
|
||||
* @property stamp The account's security stamp (if applicable).
|
||||
* @property organizationId The ID of the associated organization (if applicable).
|
||||
* @property hasPremium True if the user has a premium account.
|
||||
* @property hasPremium True if the user has a Premium account.
|
||||
* @property avatarColorHex Hex color value for a user's avatar in the "#AARRGGBB" format.
|
||||
* @property forcePasswordResetReason Describes the reason for a forced password reset.
|
||||
* @property kdfType The KDF type.
|
||||
|
||||
@@ -42,7 +42,7 @@ data class UserState(
|
||||
* @property name The user's name (if applicable).
|
||||
* @property avatarColorHex Hex color value for a user's avatar in the "#AARRGGBB" format.
|
||||
* @property environment The [Environment] associated with the user's account.
|
||||
* @property isPremium `true` if the account has a premium membership.
|
||||
* @property isPremium `true` if the account has a Premium membership.
|
||||
* @property isLoggedIn `true` if the account is logged in, or `false` if it requires additional
|
||||
* authentication to view their vault.
|
||||
* @property isVaultUnlocked Whether the user's vault is currently unlocked.
|
||||
|
||||
@@ -3,21 +3,21 @@ package com.x8bit.bitwarden.data.billing.manager
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
/**
|
||||
* Manages the consolidated eligibility state for the premium upgrade banner.
|
||||
* Manages the consolidated eligibility state for the Premium upgrade banner.
|
||||
*
|
||||
* Combines multiple upstream signals (premium status, billing support, feature flag,
|
||||
* Combines multiple upstream signals (Premium status, billing support, feature flag,
|
||||
* banner dismissal, account age, and vault item count) into a single observable flow.
|
||||
*/
|
||||
interface PremiumStateManager {
|
||||
|
||||
/**
|
||||
* Emits `true` when the current user is eligible to see the premium upgrade banner,
|
||||
* Emits `true` when the current user is eligible to see the Premium upgrade banner,
|
||||
* or `false` otherwise.
|
||||
*/
|
||||
val isPremiumUpgradeBannerEligibleFlow: StateFlow<Boolean>
|
||||
|
||||
/**
|
||||
* Marks the premium upgrade banner as dismissed for the current user.
|
||||
* Marks the Premium upgrade banner as dismissed for the current user.
|
||||
*/
|
||||
fun dismissPremiumUpgradeBanner()
|
||||
}
|
||||
+1
-1
@@ -20,7 +20,7 @@ interface BillingRepository {
|
||||
suspend fun getCheckoutSessionUrl(): CheckoutSessionResult
|
||||
|
||||
/**
|
||||
* Retrieves the Stripe customer portal URL for managing the premium subscription.
|
||||
* Retrieves the Stripe customer portal URL for managing the Premium subscription.
|
||||
*/
|
||||
suspend fun getPortalUrl(): CustomerPortalResult
|
||||
}
|
||||
+2
-2
@@ -124,12 +124,12 @@ interface SettingsDiskSource : FlightRecorderDiskSource {
|
||||
fun getIntroducingArchiveActionCardDismissedFlow(userId: String): Flow<Boolean?>
|
||||
|
||||
/**
|
||||
* Retrieves the stored value of whether the premium upgrade banner has been dismissed.
|
||||
* Retrieves the stored value of whether the Premium upgrade banner has been dismissed.
|
||||
*/
|
||||
fun getPremiumUpgradeBannerDismissed(userId: String): Boolean?
|
||||
|
||||
/**
|
||||
* Stores whether the premium upgrade banner has been dismissed.
|
||||
* Stores whether the Premium upgrade banner has been dismissed.
|
||||
*/
|
||||
fun storePremiumUpgradeBannerDismissed(
|
||||
userId: String,
|
||||
|
||||
+1
-1
@@ -251,7 +251,7 @@ class SettingsDiskSourceImpl(
|
||||
// - should show add login coach mark
|
||||
// - should show generator coach mark
|
||||
// - should show introducing archive action card dismissed
|
||||
// - premium upgrade banner dismissed
|
||||
// - Premium upgrade banner dismissed
|
||||
}
|
||||
|
||||
override fun getIntroducingArchiveActionCardDismissed(userId: String): Boolean? =
|
||||
|
||||
@@ -31,7 +31,7 @@ interface PushManager {
|
||||
val passwordlessRequestFlow: Flow<PasswordlessRequestData>
|
||||
|
||||
/**
|
||||
* Flow that represents premium status change notifications.
|
||||
* Flow that represents Premium status change notifications.
|
||||
*/
|
||||
val premiumStatusChangedFlow: Flow<PremiumStatusChangedData>
|
||||
|
||||
|
||||
+1
-1
@@ -99,7 +99,7 @@ sealed class NotificationPayload {
|
||||
) : NotificationPayload()
|
||||
|
||||
/**
|
||||
* A notification payload for premium status changes.
|
||||
* A notification payload for Premium status changes.
|
||||
*/
|
||||
@Serializable
|
||||
data class PremiumStatusChangedNotification(
|
||||
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
package com.x8bit.bitwarden.data.platform.manager.model
|
||||
|
||||
/**
|
||||
* Data class representing a premium status changed push notification.
|
||||
* Data class representing a Premium status changed push notification.
|
||||
*
|
||||
* @property userId The user ID associated with the status change.
|
||||
* @property isPremium Whether premium is now enabled.
|
||||
* @property isPremium Whether Premium is now enabled.
|
||||
*/
|
||||
data class PremiumStatusChangedData(
|
||||
val userId: String,
|
||||
|
||||
+1
-1
@@ -135,7 +135,7 @@ sealed class SpecialCircumstance : Parcelable {
|
||||
data object VerificationCodeShortcut : SpecialCircumstance()
|
||||
|
||||
/**
|
||||
* The app was launched via a premium checkout callback deep link,
|
||||
* The app was launched via a Premium checkout callback deep link,
|
||||
* indicating the user is returning from a Stripe checkout session.
|
||||
*/
|
||||
@Parcelize
|
||||
|
||||
+1
-1
@@ -56,7 +56,7 @@ interface DebugMenuRepository {
|
||||
fun clearSsoCookies()
|
||||
|
||||
/**
|
||||
* Resets the premium upgrade banner dismiss status for the current user.
|
||||
* Resets the Premium upgrade banner dismiss status for the current user.
|
||||
*/
|
||||
fun resetPremiumUpgradeBannerDismiss()
|
||||
}
|
||||
+2
-2
@@ -253,12 +253,12 @@ interface SettingsRepository : FlightRecorderManager {
|
||||
fun dismissIntroducingArchiveActionCard()
|
||||
|
||||
/**
|
||||
* Gets updates for whether the premium upgrade banner is dismissed.
|
||||
* Gets updates for whether the Premium upgrade banner is dismissed.
|
||||
*/
|
||||
fun getPremiumUpgradeBannerDismissedFlow(): StateFlow<Boolean>
|
||||
|
||||
/**
|
||||
* Stores that the premium upgrade banner has been dismissed for the active user.
|
||||
* Stores that the Premium upgrade banner has been dismissed for the active user.
|
||||
*/
|
||||
fun dismissPremiumUpgradeBanner()
|
||||
|
||||
|
||||
+1
-1
@@ -212,7 +212,7 @@ sealed class DebugMenuAction {
|
||||
data object ClearSsoCookies : DebugMenuAction()
|
||||
|
||||
/**
|
||||
* User has clicked to reset the premium upgrade banner dismiss status.
|
||||
* User has clicked to reset the Premium upgrade banner dismiss status.
|
||||
*/
|
||||
data object ResetPremiumUpgradeBanner : DebugMenuAction()
|
||||
|
||||
|
||||
+2
-2
@@ -1169,7 +1169,7 @@ data class SearchState(
|
||||
) : DialogState()
|
||||
|
||||
/**
|
||||
* Displays a dialog to the user indicating that archiving requires a premium account.
|
||||
* Displays a dialog to the user indicating that archiving requires a Premium account.
|
||||
*/
|
||||
@Parcelize
|
||||
data object ArchiveRequiresPremium : DialogState()
|
||||
@@ -1443,7 +1443,7 @@ sealed class SearchAction {
|
||||
) : SearchAction()
|
||||
|
||||
/**
|
||||
* User clicked the upgrade to premium button.
|
||||
* User clicked the upgrade to Premium button.
|
||||
*/
|
||||
data object UpgradeToPremiumClick : SearchAction()
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ val Intent.isAccountSecurityShortcut: Boolean
|
||||
get() = dataString?.equals("bitwarden://settings/account_security") == true
|
||||
|
||||
/**
|
||||
* Returns `true` if the [Intent] is a deep link callback from a premium
|
||||
* Returns `true` if the [Intent] is a deep link callback from a Premium
|
||||
* checkout session, `false` otherwise.
|
||||
*/
|
||||
val Intent.isPremiumCheckoutCallback: Boolean
|
||||
|
||||
+5
-5
@@ -564,7 +564,7 @@ class AddEditSendViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
private fun handleAuthTypeSelect(action: AddEditSendAction.AuthTypeSelect) {
|
||||
// Check if user is trying to select Email auth without premium
|
||||
// Check if user is trying to select Email auth without Premium
|
||||
if (action.sendAuth is SendAuth.Email && !state.isPremium) {
|
||||
mutableStateFlow.update {
|
||||
it.copy(dialogState = AddEditSendState.DialogState.EmailAuthRequiresPremium)
|
||||
@@ -712,7 +712,7 @@ class AddEditSendViewModel @Inject constructor(
|
||||
(content.selectedType as? AddEditSendState.ViewState.Content.SendType.File)
|
||||
?.let { fileType ->
|
||||
if (!state.isPremium) {
|
||||
// We should never get here without a premium account, but we do one last
|
||||
// We should never get here without a Premium account, but we do one last
|
||||
// check just in case.
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
@@ -1057,7 +1057,7 @@ data class AddEditSendState(
|
||||
|
||||
/**
|
||||
* Displays a dialog to the user indicating that email authentication requires
|
||||
* a premium account.
|
||||
* a Premium account.
|
||||
*/
|
||||
@Parcelize
|
||||
data object EmailAuthRequiresPremium : DialogState()
|
||||
@@ -1110,7 +1110,7 @@ sealed class AddEditSendEvent {
|
||||
) : AddEditSendEvent()
|
||||
|
||||
/**
|
||||
* Navigate to the premium upgrade page.
|
||||
* Navigate to the Premium upgrade page.
|
||||
*/
|
||||
data class NavigateToPremium(val uri: String) : AddEditSendEvent()
|
||||
}
|
||||
@@ -1250,7 +1250,7 @@ sealed class AddEditSendAction {
|
||||
data class AuthEmailRemove(val authEmail: AuthEmail) : AddEditSendAction()
|
||||
|
||||
/**
|
||||
* User clicked upgrade to premium from the email auth premium dialog.
|
||||
* User clicked upgrade to Premium from the email auth Premium dialog.
|
||||
*/
|
||||
data object UpgradeToPremiumClick : AddEditSendAction()
|
||||
|
||||
|
||||
+3
-3
@@ -2943,7 +2943,7 @@ data class VaultAddEditState(
|
||||
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 archiving requires a Premium account.
|
||||
*/
|
||||
data object ArchiveRequiresPremium : DialogState()
|
||||
|
||||
@@ -3075,7 +3075,7 @@ sealed class VaultAddEditEvent {
|
||||
) : VaultAddEditEvent()
|
||||
|
||||
/**
|
||||
* Navigates to the upgrade-to-premium url.
|
||||
* Navigates to the upgrade-to-Premium url.
|
||||
*/
|
||||
data class NavigateToPremium(
|
||||
val uri: String,
|
||||
@@ -3200,7 +3200,7 @@ sealed class VaultAddEditAction {
|
||||
data object UnarchiveClick : Common()
|
||||
|
||||
/**
|
||||
* The user has clicked the upgrade to premium dialog.
|
||||
* The user has clicked the upgrade to Premium dialog.
|
||||
*/
|
||||
data object UpgradeToPremiumClick : Common()
|
||||
|
||||
|
||||
@@ -1698,14 +1698,14 @@ data class VaultItemState(
|
||||
* @property passwordRevisionDate An optional string indicating the last time the
|
||||
* password was changed.
|
||||
* @property totpCodeItemData The optional data related the TOTP code.
|
||||
* @property isPremiumUser Indicates if the user has subscribed to a premium
|
||||
* @property isPremiumUser Indicates if the user has subscribed to a Premium
|
||||
* account.
|
||||
* @property canViewTotpCode Indicates if the user can view an associated TOTP code.
|
||||
* @property fido2CredentialCreationDateText Optional creation date and time of the
|
||||
* FIDO2 credential associated with the login item.
|
||||
*
|
||||
* **NOTE** [canViewTotpCode] currently supports a deprecated edge case where an
|
||||
* organization supports TOTP but not through the current premium model.
|
||||
* organization supports TOTP but not through the current Premium model.
|
||||
* This additional field is added to allow for [isPremiumUser] to be an independent
|
||||
* value.
|
||||
* @see [CipherView.organizationUseTotp]
|
||||
@@ -1891,7 +1891,7 @@ 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 archiving requires a Premium account.
|
||||
*/
|
||||
@Parcelize
|
||||
data object ArchiveRequiresPremium : DialogState()
|
||||
@@ -2050,7 +2050,7 @@ sealed class VaultItemAction {
|
||||
data object UnarchiveClick : Common()
|
||||
|
||||
/**
|
||||
* The user has clicked the upgrade to premium button.
|
||||
* The user has clicked the upgrade to Premium button.
|
||||
*/
|
||||
data object UpgradeToPremiumClick : Common()
|
||||
|
||||
|
||||
+3
-3
@@ -3112,7 +3112,7 @@ data class VaultItemListingState(
|
||||
) : DialogState()
|
||||
|
||||
/**
|
||||
* Displays a dialog to the user indicating that archiving requires a premium account.
|
||||
* Displays a dialog to the user indicating that archiving requires a Premium account.
|
||||
*/
|
||||
@Parcelize
|
||||
data object ArchiveRequiresPremium : DialogState()
|
||||
@@ -3123,7 +3123,7 @@ data class VaultItemListingState(
|
||||
*/
|
||||
sealed class ActionCardState {
|
||||
/**
|
||||
* Indicates that your premium subscription has lapsed.
|
||||
* Indicates that your Premium subscription has lapsed.
|
||||
*/
|
||||
data object PremiumSubscription : ActionCardState()
|
||||
}
|
||||
@@ -3682,7 +3682,7 @@ sealed class VaultItemListingsAction {
|
||||
) : VaultItemListingsAction()
|
||||
|
||||
/**
|
||||
* Click the upgrade to premium button.
|
||||
* Click the upgrade to Premium button.
|
||||
*/
|
||||
data object UpgradeToPremiumClick : VaultItemListingsAction()
|
||||
|
||||
|
||||
@@ -621,7 +621,7 @@ class VaultViewModel @Inject constructor(
|
||||
?.archivedItemsCount
|
||||
?: 0
|
||||
if (state.isPremium || archivedItemsCount > 0) {
|
||||
// We still navigate even if the user does not have premium, since they have previously
|
||||
// We still navigate even if the user does not have Premium, since they have previously
|
||||
// archived ciphers to view.
|
||||
sendEvent(VaultEvent.NavigateToItemListing(VaultItemListingType.Archive))
|
||||
} else {
|
||||
@@ -1489,7 +1489,7 @@ class VaultViewModel @Inject constructor(
|
||||
* @property viewState The specific view state representing loading, no items, or content state.
|
||||
* @property dialog Information about any dialogs that may need to be displayed.
|
||||
* @property isSwitchingAccounts Whether we are actively switching accounts.
|
||||
* @property isPremium Whether the user is a premium user.
|
||||
* @property isPremium Whether the user is a Premium user.
|
||||
*/
|
||||
@Parcelize
|
||||
data class VaultState(
|
||||
@@ -1847,7 +1847,7 @@ data class VaultState(
|
||||
*/
|
||||
sealed class ActionCardState {
|
||||
/**
|
||||
* Indicates that the user is eligible for a premium upgrade.
|
||||
* Indicates that the user is eligible for a Premium upgrade.
|
||||
*/
|
||||
data object UpgradePremium : ActionCardState()
|
||||
|
||||
@@ -1863,7 +1863,7 @@ data class VaultState(
|
||||
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 archiving requires a Premium account.
|
||||
*/
|
||||
@Parcelize
|
||||
data object ArchiveRequiresPremium : DialogState()
|
||||
@@ -2260,7 +2260,7 @@ sealed class VaultAction {
|
||||
data object SelectAddItemType : VaultAction()
|
||||
|
||||
/**
|
||||
* User clicked the upgrade to premium button.
|
||||
* User clicked the upgrade to Premium button.
|
||||
*/
|
||||
data object UpgradeToPremiumClick : VaultAction()
|
||||
|
||||
@@ -2410,7 +2410,7 @@ sealed class VaultAction {
|
||||
) : Internal()
|
||||
|
||||
/**
|
||||
* Indicates that the premium upgrade banner eligibility has been
|
||||
* Indicates that the Premium upgrade banner eligibility has been
|
||||
* updated.
|
||||
*/
|
||||
data class PremiumUpgradeBannerEligibilityReceive(
|
||||
|
||||
+1
-1
@@ -374,7 +374,7 @@ class VerificationCodeViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter verification codes in the event that the user is not a "premium" account but
|
||||
* Filter verification codes in the event that the user is not a "Premium" account but
|
||||
* has TOTP codes associated with a legacy organization.
|
||||
*/
|
||||
private fun filterAuthCodesForDataState(
|
||||
|
||||
@@ -864,7 +864,7 @@ class MainViewModelTest : BaseViewModelTest() {
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `on ReceiveFirstIntent with premium checkout callback should set special circumstance to PremiumCheckoutResult`() {
|
||||
fun `on ReceiveFirstIntent with Premium checkout callback should set special circumstance to PremiumCheckoutResult`() {
|
||||
val viewModel = createViewModel()
|
||||
val mockIntent = createMockIntent(
|
||||
mockIsPremiumCheckoutCallback = true,
|
||||
@@ -881,7 +881,7 @@ class MainViewModelTest : BaseViewModelTest() {
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `on ReceiveNewIntent with premium checkout callback should set special circumstance to PremiumCheckoutResult`() {
|
||||
fun `on ReceiveNewIntent with Premium checkout callback should set special circumstance to PremiumCheckoutResult`() {
|
||||
val viewModel = createViewModel()
|
||||
val mockIntent = createMockIntent(
|
||||
mockIsPremiumCheckoutCallback = true,
|
||||
|
||||
+3
-3
@@ -68,7 +68,7 @@ class AutofillTotpManagerTest {
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `tryCopyTotpToClipboard when isAutoCopyTotpDisabled is false, user not premium and the organization does not use totp should do nothing`() =
|
||||
fun `tryCopyTotpToClipboard when isAutoCopyTotpDisabled is false, user not Premium and the organization does not use totp should do nothing`() =
|
||||
runTest {
|
||||
every { settingsRepository.isAutoCopyTotpDisabled } returns false
|
||||
every { cipherView.organizationUseTotp } returns false
|
||||
@@ -91,7 +91,7 @@ class AutofillTotpManagerTest {
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `tryCopyTotpToClipboard when isAutoCopyTotpDisabled is false, has premium and does not have totp should do nothing`() =
|
||||
fun `tryCopyTotpToClipboard when isAutoCopyTotpDisabled is false, has Premium and does not have totp should do nothing`() =
|
||||
runTest {
|
||||
every { settingsRepository.isAutoCopyTotpDisabled } returns false
|
||||
every { cipherView.organizationUseTotp } returns true
|
||||
@@ -115,7 +115,7 @@ class AutofillTotpManagerTest {
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `tryCopyTotpToClipboard when isAutoCopyTotpDisabled is false, has premium and has totp should set the clipboard`() =
|
||||
fun `tryCopyTotpToClipboard when isAutoCopyTotpDisabled is false, has Premium and has totp should set the clipboard`() =
|
||||
runTest {
|
||||
val generateTotpResult = GenerateTotpResult.Success(
|
||||
code = TOTP_RESULT_VALUE,
|
||||
|
||||
+1
-1
@@ -90,7 +90,7 @@ class PremiumStateManagerImplTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ineligible when user is premium should emit false`() = runTest {
|
||||
fun `ineligible when user is Premium should emit false`() = runTest {
|
||||
mutableUserStateFlow.value = DEFAULT_USER_STATE.copy(
|
||||
accounts = listOf(DEFAULT_ACTIVE_ACCOUNT.copy(isPremium = true)),
|
||||
)
|
||||
|
||||
+1
-1
@@ -515,7 +515,7 @@ class FakeSettingsDiskSource(
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the stored premium upgrade banner dismissed matches the [expected] one.
|
||||
* Asserts that the stored Premium upgrade banner dismissed matches the [expected] one.
|
||||
*/
|
||||
fun assertPremiumUpgradeBannerDismissed(userId: String, expected: Boolean?) {
|
||||
assertEquals(expected, storedPremiumUpgradeBannerDismissed[userId])
|
||||
|
||||
@@ -165,7 +165,7 @@ class PushManagerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `onMessageReceived with premium status changed emits to premiumStatusChangedFlow`() =
|
||||
fun `onMessageReceived with Premium status changed emits to premiumStatusChangedFlow`() =
|
||||
runTest {
|
||||
pushManager.premiumStatusChangedFlow.test {
|
||||
pushManager.onMessageReceived(
|
||||
@@ -182,7 +182,7 @@ class PushManagerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `onMessageReceived with premium status changed also emits to fullSyncFlow`() =
|
||||
fun `onMessageReceived with Premium status changed also emits to fullSyncFlow`() =
|
||||
runTest {
|
||||
pushManager.fullSyncFlow.test {
|
||||
pushManager.onMessageReceived(
|
||||
@@ -196,7 +196,7 @@ class PushManagerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `onMessageReceived with premium status changed with null fields does not emit`() =
|
||||
fun `onMessageReceived with Premium status changed with null fields does not emit`() =
|
||||
runTest {
|
||||
pushManager.premiumStatusChangedFlow.test {
|
||||
pushManager.onMessageReceived(
|
||||
|
||||
+2
-2
@@ -160,9 +160,9 @@ class DebugMenuScreenTest : BitwardenComposeTest() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `reset premium upgrade banner should send ResetPremiumUpgradeBanner action`() {
|
||||
fun `reset Premium upgrade banner should send ResetPremiumUpgradeBanner action`() {
|
||||
composeTestRule
|
||||
.onNodeWithText("Reset premium upgrade banner")
|
||||
.onNodeWithText("Reset Premium upgrade banner")
|
||||
.performScrollTo()
|
||||
.performClick()
|
||||
|
||||
|
||||
+1
-1
@@ -317,7 +317,7 @@ class SearchViewModelTest : BaseViewModelTest() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ArchiveClick without premium should show ArchiveRequiresPremium dialog`() = runTest {
|
||||
fun `ArchiveClick without Premium should show ArchiveRequiresPremium dialog`() = runTest {
|
||||
mutableUserStateFlow.update {
|
||||
it?.copy(accounts = listOf(DEFAULT_ACCOUNT.copy(isPremium = false)))
|
||||
}
|
||||
|
||||
@@ -147,7 +147,7 @@ class SendViewModelTest : BaseViewModelTest() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `AddSendSelected with file type and non premium user should display dialog`() {
|
||||
fun `AddSendSelected with file type and non Premium user should display dialog`() {
|
||||
val state = DEFAULT_STATE.copy(isPremiumUser = false, policyDisablesSend = false)
|
||||
val viewModel = createViewModel(state = state)
|
||||
viewModel.trySendAction(SendAction.AddSendSelected(sendType = SendItemType.FILE))
|
||||
|
||||
+1
-1
@@ -1317,7 +1317,7 @@ class AddEditSendScreenTest : BitwardenComposeTest() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `auth type chooser should show EMAIL option for premium users`() {
|
||||
fun `auth type chooser should show EMAIL option for Premium users`() {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
isPremium = true,
|
||||
|
||||
+3
-3
@@ -480,7 +480,7 @@ class AddEditSendViewModelTest : BaseViewModelTest() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `SaveClick for file without premium show error dialog`() {
|
||||
fun `SaveClick for file without Premium show error dialog`() {
|
||||
mutableUserStateFlow.value = DEFAULT_USER_STATE.copy(
|
||||
accounts = listOf(DEFAULT_ACCOUNT.copy(isPremium = false)),
|
||||
)
|
||||
@@ -1412,7 +1412,7 @@ class AddEditSendViewModelTest : BaseViewModelTest() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `AuthTypeSelect with Email auth without premium should show premium dialog`() = runTest {
|
||||
fun `AuthTypeSelect with Email auth without Premium should show Premium dialog`() = runTest {
|
||||
val nonPremiumState = DEFAULT_STATE.copy(isPremium = false)
|
||||
val viewModel = createViewModel(nonPremiumState)
|
||||
|
||||
@@ -1430,7 +1430,7 @@ class AddEditSendViewModelTest : BaseViewModelTest() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `AuthTypeSelect with Email auth with premium should allow selection`() = runTest {
|
||||
fun `AuthTypeSelect with Email auth with Premium should allow selection`() = runTest {
|
||||
every { UUID.randomUUID().toString() } returns "uuid"
|
||||
val premiumState = DEFAULT_STATE.copy(isPremium = true)
|
||||
val viewModel = createViewModel(premiumState)
|
||||
|
||||
+1
-1
@@ -268,7 +268,7 @@ class VaultAddEditScreenTest : BitwardenComposeTest() {
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `ArchiveRequiresPremium dialog on upgrade to premium click should emit UpgradeToPremiumClick`() {
|
||||
fun `ArchiveRequiresPremium dialog on upgrade to Premium click should emit UpgradeToPremiumClick`() {
|
||||
composeTestRule.assertNoDialogExists()
|
||||
mutableStateFlow.value = DEFAULT_STATE_LOGIN.copy(
|
||||
dialog = VaultAddEditState.DialogState.ArchiveRequiresPremium,
|
||||
|
||||
+1
-1
@@ -2378,7 +2378,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ArchiveClick without premium should show ArchiveRequiresPremium dialog`() = runTest {
|
||||
fun `ArchiveClick without Premium should show ArchiveRequiresPremium dialog`() = runTest {
|
||||
val cipherListView = createMockCipherListView(number = 1, isArchived = false)
|
||||
val cipherView = createMockCipherView(number = 1, isArchived = false)
|
||||
val vaultAddEditType = VaultAddEditType.EditItem(DEFAULT_EDIT_ITEM_ID)
|
||||
|
||||
+1
-1
@@ -454,7 +454,7 @@ class CipherViewExtensionsTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `toViewState with archived cipher and no premium account should set archiveCalloutText`() {
|
||||
fun `toViewState with archived cipher and no Premium account should set archiveCalloutText`() {
|
||||
val cipherView = DEFAULT_SECURE_NOTES_CIPHER_VIEW.copy(
|
||||
deletedDate = FIXED_CLOCK.instant(),
|
||||
archivedDate = FIXED_CLOCK.instant(),
|
||||
|
||||
+1
-1
@@ -86,7 +86,7 @@ class AttachmentsViewModelTest : BaseViewModelTest() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `SaveClick should display error dialog when user is not premium`() = runTest {
|
||||
fun `SaveClick should display error dialog when user is not Premium`() = runTest {
|
||||
val cipherView = createMockCipherView(number = 1)
|
||||
val state = DEFAULT_STATE.copy(
|
||||
viewState = DEFAULT_CONTENT_WITH_ATTACHMENTS,
|
||||
|
||||
+2
-2
@@ -263,7 +263,7 @@ class VaultItemScreenTest : BitwardenComposeTest() {
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `ArchiveRequiresPremium dialog on upgrade to premium click should emit UpgradeToPremiumClick`() {
|
||||
fun `ArchiveRequiresPremium dialog on upgrade to Premium click should emit UpgradeToPremiumClick`() {
|
||||
composeTestRule.assertNoDialogExists()
|
||||
mutableStateFlow.update {
|
||||
it.copy(dialog = VaultItemState.DialogState.ArchiveRequiresPremium)
|
||||
@@ -759,7 +759,7 @@ class VaultItemScreenTest : BitwardenComposeTest() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `attachment download click for non-premium users should show an error dialog`() {
|
||||
fun `attachment download click for non-Premium users should show an error dialog`() {
|
||||
mutableStateFlow.update { currentState ->
|
||||
currentState.copy(
|
||||
viewState = EMPTY_LOGIN_VIEW_STATE.copy(
|
||||
|
||||
+1
-1
@@ -248,7 +248,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ArchiveClick without premium should show ArchiveRequiresPremium dialog`() = runTest {
|
||||
fun `ArchiveClick without Premium should show ArchiveRequiresPremium dialog`() = runTest {
|
||||
mutableUserStateFlow.update {
|
||||
it?.copy(accounts = listOf(DEFAULT_USER_ACCOUNT.copy(isPremium = false)))
|
||||
}
|
||||
|
||||
+3
-3
@@ -39,7 +39,7 @@ class CipherViewExtensionsTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `toViewState should transform full CipherView into ViewState Login Content with premium`() {
|
||||
fun `toViewState should transform full CipherView into ViewState Login Content with Premium`() {
|
||||
val cipherView = createCipherView(type = CipherType.LOGIN, isEmpty = false)
|
||||
val viewState = cipherView.toViewState(
|
||||
previousState = null,
|
||||
@@ -72,7 +72,7 @@ class CipherViewExtensionsTest {
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `toViewState should transform full CipherView into ViewState Login Content without premium`() {
|
||||
fun `toViewState should transform full CipherView into ViewState Login Content without Premium`() {
|
||||
val isPremiumUser = false
|
||||
val cipherView = createCipherView(type = CipherType.LOGIN, isEmpty = false)
|
||||
val viewState = cipherView.toViewState(
|
||||
@@ -109,7 +109,7 @@ class CipherViewExtensionsTest {
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `toViewState should transform full CipherView into ViewState Login Content without premium but with org totp access`() {
|
||||
fun `toViewState should transform full CipherView into ViewState Login Content without Premium but with org totp access`() {
|
||||
val isPremiumUser = false
|
||||
val cipherView = createCipherView(
|
||||
type = CipherType.LOGIN,
|
||||
|
||||
+1
-1
@@ -222,7 +222,7 @@ class VaultItemListingScreenTest : BitwardenComposeTest() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `premium action card restart premium button click should send UpgradeToPremiumClick`() {
|
||||
fun `Premium action card restart Premium button click should send UpgradeToPremiumClick`() {
|
||||
composeTestRule
|
||||
.onNodeWithText(text = "Your Premium subscription ended")
|
||||
.assertDoesNotExist()
|
||||
|
||||
+3
-3
@@ -1073,7 +1073,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ArchiveClick without premium should show ArchiveRequiresPremium dialog`() = runTest {
|
||||
fun `ArchiveClick without Premium should show ArchiveRequiresPremium dialog`() = runTest {
|
||||
mutableUserStateFlow.update {
|
||||
it?.copy(accounts = listOf(DEFAULT_ACCOUNT.copy(isPremium = false)))
|
||||
}
|
||||
@@ -1746,7 +1746,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `AddVaultItemClick for file send item with premium should emit NavigateToAddVaultItem`() =
|
||||
fun `AddVaultItemClick for file send item with Premium should emit NavigateToAddVaultItem`() =
|
||||
runTest {
|
||||
val viewModel = createVaultItemListingViewModel(
|
||||
createSavedStateHandleWithVaultItemListingType(VaultItemListingType.SendFile),
|
||||
@@ -1761,7 +1761,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `AddVaultItemClick for file send item without premium should display error dialog`() =
|
||||
fun `AddVaultItemClick for file send item without Premium should display error dialog`() =
|
||||
runTest {
|
||||
mutableUserStateFlow.value = DEFAULT_USER_STATE.copy(
|
||||
accounts = listOf(DEFAULT_ACCOUNT.copy(isPremium = false)),
|
||||
|
||||
+2
-2
@@ -19,7 +19,7 @@ import org.junit.jupiter.api.Test
|
||||
class CipherListViewExtensionsTest {
|
||||
|
||||
@Test
|
||||
fun `toOverflowActions should return all actions for a login cipher when a user has premium`() {
|
||||
fun `toOverflowActions should return all actions for a login cipher when a user has Premium`() {
|
||||
val loginListView = createMockLoginListView(
|
||||
number = 1,
|
||||
username = username,
|
||||
@@ -72,7 +72,7 @@ class CipherListViewExtensionsTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `toOverflowActions should not return TOTP action when a user does not have premium`() {
|
||||
fun `toOverflowActions should not return TOTP action when a user does not have Premium`() {
|
||||
val type = CipherListViewType.Login(
|
||||
createMockLoginListView(
|
||||
number = 1,
|
||||
|
||||
+5
-5
@@ -371,7 +371,7 @@ class VaultViewModelTest : BaseViewModelTest() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `actionCard should return IntroducingArchive when not eligible for premium upgrade`() {
|
||||
fun `actionCard should return IntroducingArchive when not eligible for Premium upgrade`() {
|
||||
val contentViewState = DEFAULT_CONTENT_VIEW_STATE
|
||||
val state = createMockVaultState(viewState = contentViewState).copy(
|
||||
isPremiumUpgradeBannerEligible = false,
|
||||
@@ -760,7 +760,7 @@ class VaultViewModelTest : BaseViewModelTest() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ArchiveClick without premium should show ArchiveRequiresPremium dialog`() = runTest {
|
||||
fun `ArchiveClick without Premium should show ArchiveRequiresPremium dialog`() = runTest {
|
||||
mutableUserStateFlow.update { userState ->
|
||||
userState?.copy(
|
||||
accounts = DEFAULT_USER_STATE.accounts.map { it.copy(isPremium = false) },
|
||||
@@ -2067,7 +2067,7 @@ class VaultViewModelTest : BaseViewModelTest() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ArchiveClick with premium should emit NavigateToItemListing event with Archive type`() =
|
||||
fun `ArchiveClick with Premium should emit NavigateToItemListing event with Archive type`() =
|
||||
runTest {
|
||||
val viewModel = createViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
@@ -2081,7 +2081,7 @@ class VaultViewModelTest : BaseViewModelTest() {
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `ArchiveClick without premium and with archived ciphers should emit NavigateToItemListing event with Archive type`() =
|
||||
fun `ArchiveClick without Premium and with archived ciphers should emit NavigateToItemListing event with Archive type`() =
|
||||
runTest {
|
||||
mutableUserStateFlow.update {
|
||||
DEFAULT_USER_STATE.copy(
|
||||
@@ -2119,7 +2119,7 @@ class VaultViewModelTest : BaseViewModelTest() {
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `ArchiveClick without premium and archived ciphers should display the ArchiveRequiresPremium dialog`() =
|
||||
fun `ArchiveClick without Premium and archived ciphers should display the ArchiveRequiresPremium dialog`() =
|
||||
runTest {
|
||||
mutableUserStateFlow.update {
|
||||
DEFAULT_USER_STATE.copy(
|
||||
|
||||
+1
-1
@@ -758,7 +758,7 @@ class VaultAddItemStateExtensionsTest {
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `toCipherView without premium should delete the archive date from the original cipher`() {
|
||||
fun `toCipherView without Premium should delete the archive date from the original cipher`() {
|
||||
val cipherView = DEFAULT_BASE_CIPHER_VIEW.copy(
|
||||
notes = null,
|
||||
fields = emptyList(),
|
||||
|
||||
+4
-4
@@ -343,7 +343,7 @@ class VaultDataExtensionsTest {
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `toViewState should return 1 for totpItemsCount if user has premium and has one totp item`() {
|
||||
fun `toViewState should return 1 for totpItemsCount if user has Premium and has one totp item`() {
|
||||
val vaultData = VaultData(
|
||||
decryptCipherListResult = createMockDecryptCipherListResult(
|
||||
number = 1,
|
||||
@@ -390,7 +390,7 @@ class VaultDataExtensionsTest {
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `toViewState should return 0 for totpItemsCount if user does not have premium and has any totp items`() {
|
||||
fun `toViewState should return 0 for totpItemsCount if user does not have Premium and has any totp items`() {
|
||||
val vaultData = VaultData(
|
||||
decryptCipherListResult = createMockDecryptCipherListResult(
|
||||
number = 1,
|
||||
@@ -437,7 +437,7 @@ class VaultDataExtensionsTest {
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `toViewState should return 1 for totpItemsCount if user does not have premium and has at least 1 totp items with org TOTP true`() {
|
||||
fun `toViewState should return 1 for totpItemsCount if user does not have Premium and has at least 1 totp items with org TOTP true`() {
|
||||
val vaultData = VaultData(
|
||||
decryptCipherListResult = createMockDecryptCipherListResult(
|
||||
number = 1,
|
||||
@@ -485,7 +485,7 @@ class VaultDataExtensionsTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `toViewState should omit non org related totp codes when user does not have premium`() {
|
||||
fun `toViewState should omit non org related totp codes when user does not have Premium`() {
|
||||
val vaultData = VaultData(
|
||||
decryptCipherListResult = createMockDecryptCipherListResult(
|
||||
number = 1,
|
||||
|
||||
+2
-2
@@ -540,7 +540,7 @@ class VerificationCodeViewModelTest : BaseViewModelTest() {
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `AuthCodeState Loaded with non premium user and no org TOTP enabled should cause navigate back`() =
|
||||
fun `AuthCodeState Loaded with non Premium user and no org TOTP enabled should cause navigate back`() =
|
||||
runTest {
|
||||
setupMockUri()
|
||||
every { mockUserAccount.isPremium } returns false
|
||||
@@ -561,7 +561,7 @@ class VerificationCodeViewModelTest : BaseViewModelTest() {
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `AuthCodeState Loaded with non premium user and one org TOTP enabled should return correct state`() =
|
||||
fun `AuthCodeState Loaded with non Premium user and one org TOTP enabled should return correct state`() =
|
||||
runTest {
|
||||
setupMockUri()
|
||||
every { mockUserAccount.isPremium } returns false
|
||||
|
||||
@@ -109,7 +109,7 @@ sealed class FlagKey<out T : Any> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Data object holding the feature flag key for the mobile premium upgrade feature.
|
||||
* Data object holding the feature flag key for the mobile Premium upgrade feature.
|
||||
*/
|
||||
data object MobilePremiumUpgrade : FlagKey<Boolean>() {
|
||||
override val keyName: String = "PM-31697-premium-upgrade-path"
|
||||
|
||||
@@ -13,7 +13,7 @@ import retrofit2.http.POST
|
||||
internal interface AuthenticatedBillingApi {
|
||||
|
||||
/**
|
||||
* Creates a Stripe checkout session for premium upgrade.
|
||||
* Creates a Stripe checkout session for Premium upgrade.
|
||||
*/
|
||||
@POST("/account/billing/vnext/premium/checkout")
|
||||
suspend fun createCheckoutSession(
|
||||
@@ -21,7 +21,7 @@ internal interface AuthenticatedBillingApi {
|
||||
): NetworkResult<CheckoutSessionResponseJson>
|
||||
|
||||
/**
|
||||
* Creates a Stripe customer portal session for managing the premium subscription.
|
||||
* Creates a Stripe customer portal session for managing the Premium subscription.
|
||||
*/
|
||||
@POST("/account/billing/vnext/portal-session")
|
||||
suspend fun getPortalUrl(): NetworkResult<PortalUrlResponseJson>
|
||||
|
||||
@@ -4,7 +4,7 @@ import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Request object for creating a Stripe checkout session for premium upgrade.
|
||||
* Request object for creating a Stripe checkout session for Premium upgrade.
|
||||
*
|
||||
* @property platform The platform identifier (e.g., "android" or "ios").
|
||||
*/
|
||||
|
||||
@@ -4,9 +4,9 @@ import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Response object returned when creating a premium checkout session.
|
||||
* Response object returned when creating a Premium checkout session.
|
||||
*
|
||||
* @property checkoutSessionUrl The Stripe checkout URL for premium upgrade.
|
||||
* @property checkoutSessionUrl The Stripe checkout URL for Premium upgrade.
|
||||
*/
|
||||
@Serializable
|
||||
data class CheckoutSessionResponseJson(
|
||||
|
||||
@@ -11,7 +11,7 @@ import kotlinx.serialization.Serializable
|
||||
* @property isEmailVerified Whether the user's email is verified.
|
||||
* @property name The user's name.
|
||||
* @property expirationAsEpochTime The expiration time measured as an epoch time in seconds.
|
||||
* @property hasPremium True if the user has a premium account.
|
||||
* @property hasPremium True if the user has a Premium account.
|
||||
* @property authenticationMethodsReference A list of the authentication methods used during
|
||||
* authentication.
|
||||
*/
|
||||
|
||||
@@ -144,7 +144,7 @@ data class SyncResponseJson(
|
||||
*
|
||||
* @property providerOrganizations A list of provider organizations
|
||||
* associated with the profile (nullable).
|
||||
* @property isPremiumFromOrganization If the profile is premium from organization.
|
||||
* @property isPremiumFromOrganization If the profile is Premium from organization.
|
||||
* @property shouldForcePasswordReset If the profile should force password reset.
|
||||
* @property avatarColor The avatar color of the profile (nullable).
|
||||
* @property isEmailVerified If the profile has a verified email.
|
||||
@@ -152,7 +152,7 @@ data class SyncResponseJson(
|
||||
* @property privateKey The private key of the profile (nullable).
|
||||
* @property accountKeys The account keys associated with the profile. This is temporarily
|
||||
* nullable to maintain backwards compatibility.
|
||||
* @property isPremium If the profile is premium.
|
||||
* @property isPremium If the profile is Premium.
|
||||
* @property culture The culture of the profile (nullable).
|
||||
* @property name The name of the profile (nullable).
|
||||
* @property organizations A list of organizations associated with the profile (nullable).
|
||||
@@ -250,7 +250,7 @@ data class SyncResponseJson(
|
||||
* @property shouldUseDirectory If the organization should use a directory.
|
||||
* @property key The key of the organization (nullable).
|
||||
* @property providerName The provider name of the organization (nullable).
|
||||
* @property shouldUsersGetPremium If users of the organization get premium.
|
||||
* @property shouldUsersGetPremium If users of the organization get Premium.
|
||||
* @property maxStorageGb The max storage in Gb of the organization (nullable).
|
||||
* @property identifier The identifier of the organization (nullable).
|
||||
* @property use2fa If the organization uses 2FA.
|
||||
|
||||
@@ -9,12 +9,12 @@ import com.bitwarden.network.model.PortalUrlResponseJson
|
||||
interface BillingService {
|
||||
|
||||
/**
|
||||
* Creates a Stripe checkout session for premium upgrade.
|
||||
* Creates a Stripe checkout session for Premium upgrade.
|
||||
*/
|
||||
suspend fun createCheckoutSession(): Result<CheckoutSessionResponseJson>
|
||||
|
||||
/**
|
||||
* Creates a Stripe customer portal session for managing the premium subscription.
|
||||
* Creates a Stripe customer portal session for managing the Premium subscription.
|
||||
*/
|
||||
suspend fun getPortalUrl(): Result<PortalUrlResponseJson>
|
||||
}
|
||||
@@ -199,7 +199,7 @@ Scanning will happen automatically.</string>
|
||||
<string name="copy_totp">Copy TOTP</string>
|
||||
<string name="copy_totp_automatically_description">If a login has an authenticator key, copy the TOTP verification code to your clipboard when you autofill the login.</string>
|
||||
<string name="copy_totp_automatically">Copy TOTP automatically</string>
|
||||
<string name="premium_required">A premium membership is required to use this feature.</string>
|
||||
<string name="premium_required">A Premium membership is required to use this feature.</string>
|
||||
<string name="attachment_deleted">Attachment deleted</string>
|
||||
<string name="attachments_unavailable">Attachments unavailable</string>
|
||||
<string name="attachments_are_a_premium_feature">Attachments are a Premium feature. Your current plan does not include access to this feature.</string>
|
||||
@@ -499,7 +499,7 @@ Scanning will happen automatically.</string>
|
||||
<string name="about_send">About Send</string>
|
||||
<string name="hide_email">Hide my email address from recipients</string>
|
||||
<string name="send_options_policy_in_effect">One or more organization policies are affecting your Send options.</string>
|
||||
<string name="send_file_premium_required">Free accounts are restricted to sharing text only. A premium membership is required to use files with Send.</string>
|
||||
<string name="send_file_premium_required">Free accounts are restricted to sharing text only. A Premium membership is required to use files with Send.</string>
|
||||
<string name="sharing_with_specific_people_is_a_premium_feature">Sharing with specific people is a Premium feature. Your current plan does not include access to this feature.</string>
|
||||
<string name="password_prompt">Master password re-prompt</string>
|
||||
<string name="password_confirmation">Master password confirmation</string>
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
<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="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